<|start_filename|>kstructs/testhelpers.go<|end_filename|>
package kstructs

import (
	"fmt"
	"reflect"

	"github.com/pkg/errors"
)

// FillStructWith is meant to be used on unit tests to mock
// the response from the database.
//
// The first argument is any struct you are passing to a ksql func,
// and the second is a map representing a database row you want
// to use to update this struct.
func FillStructWith(record interface{}, dbRow map[string]interface{}) error {
	v := reflect.ValueOf(record)
	t := v.Type()

	if t.Kind() != reflect.Ptr {
		return fmt.Errorf(
			"FillStructWith: expected input to be a pointer to struct but got %T",
			record,
		)
	}

	t = t.Elem()
	v = v.Elem()

	if t.Kind() != reflect.Struct {
		return fmt.Errorf(
			"FillStructWith: expected input kind to be a struct but got %T",
			record,
		)
	}

	info, err := GetTagInfo(t)
	if err != nil {
		return err
	}

	for colName, rawSrc := range dbRow {
		fieldInfo := info.ByName(colName)
		if !fieldInfo.Valid {
			// Ignore columns not tagged with `ksql:"..."`
			continue
		}

		src := NewPtrConverter(rawSrc)
		dest := v.Field(fieldInfo.Index)
		destType := t.Field(fieldInfo.Index).Type

		destValue, err := src.Convert(destType)
		if err != nil {
			return errors.Wrap(err, fmt.Sprintf("FillStructWith: error on field `%s`", colName))
		}

		dest.Set(destValue)
	}

	return nil
}

// FillSliceWith is meant to be used on unit tests to mock
// the response from the database.
//
// The first argument is any slice of structs you are passing to a ksql func,
// and the second is a slice of maps representing the database rows you want
// to use to update this struct.
func FillSliceWith(entities interface{}, dbRows []map[string]interface{}) error {
	sliceRef := reflect.ValueOf(entities)
	sliceType := sliceRef.Type()
	if sliceType.Kind() != reflect.Ptr {
		return fmt.Errorf(
			"FillSliceWith: expected input to be a pointer to a slice of structs but got %v",
			sliceType,
		)
	}

	structType, isSliceOfPtrs, err := DecodeAsSliceOfStructs(sliceType.Elem())
	if err != nil {
		return errors.Wrap(err, "FillSliceWith")
	}

	slice := sliceRef.Elem()
	for idx, row := range dbRows {
		if slice.Len() <= idx {
			var elemValue reflect.Value
			elemValue = reflect.New(structType)
			if !isSliceOfPtrs {
				elemValue = elemValue.Elem()
			}
			slice = reflect.Append(slice, elemValue)
		}

		err := FillStructWith(slice.Index(idx).Addr().Interface(), row)
		if err != nil {
			return errors.Wrap(err, "FillSliceWith")
		}
	}

	sliceRef.Elem().Set(slice)

	return nil
}

// CallFunctionWithRows was created for helping test the QueryChunks method
func CallFunctionWithRows(fn interface{}, rows []map[string]interface{}) error {
	fnValue := reflect.ValueOf(fn)
	chunkType, err := ParseInputFunc(fn)
	if err != nil {
		return err
	}

	chunk := reflect.MakeSlice(chunkType, 0, len(rows))

	// Create a pointer to a slice (required by FillSliceWith)
	chunkPtr := reflect.New(chunkType)
	chunkPtr.Elem().Set(chunk)

	err = FillSliceWith(chunkPtr.Interface(), rows)
	if err != nil {
		return err
	}

	err, _ = fnValue.Call([]reflect.Value{chunkPtr.Elem()})[0].Interface().(error)

	return err
}


<|start_filename|>ksql.go<|end_filename|>
package ksql

import (
	"context"
	"fmt"
	"reflect"
	"strings"
	"unicode"

	"github.com/pkg/errors"
	"github.com/vingarcia/ksql/kstructs"
)

var selectQueryCache = map[string]map[reflect.Type]string{}

func init() {
	for dname := range supportedDialects {
		selectQueryCache[dname] = map[reflect.Type]string{}
	}
}

// DB represents the ksql client responsible for
// interfacing with the "database/sql" package implementing
// the KissSQL interface `ksql.Provider`.
type DB struct {
	driver  string
	dialect Dialect
	db      DBAdapter
}

// DBAdapter is minimalistic interface to decouple our implementation
// from database/sql, i.e. if any struct implements the functions below
// with the exact same semantic as the sql package it will work with ksql.
//
// To create a new client using this adapter use ksql.NewWithAdapter()
type DBAdapter interface {
	ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
	QueryContext(ctx context.Context, query string, args ...interface{}) (Rows, error)
}

// TxBeginner needs to be implemented by the DBAdapter in order to make it possible
// to use the `ksql.Transaction()` function.
type TxBeginner interface {
	BeginTx(ctx context.Context) (Tx, error)
}

// Result stores information about the result of an Exec query
type Result interface {
	LastInsertId() (int64, error)
	RowsAffected() (int64, error)
}

// Rows represents the results from a call to Query()
type Rows interface {
	Scan(...interface{}) error
	Close() error
	Next() bool
	Err() error
	Columns() ([]string, error)
}

// Tx represents a transaction and is expected to be returned by the DBAdapter.BeginTx function
type Tx interface {
	ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
	QueryContext(ctx context.Context, query string, args ...interface{}) (Rows, error)
	Rollback(ctx context.Context) error
	Commit(ctx context.Context) error
}

// Config describes the optional arguments accepted
// by the ksql.New() function.
type Config struct {
	// MaxOpenCons defaults to 1 if not set
	MaxOpenConns int
}

// SetDefaultValues should be called by all adapters
// to set the default config values if unset.
func (c *Config) SetDefaultValues() {
	if c.MaxOpenConns == 0 {
		c.MaxOpenConns = 1
	}
}

// NewWithAdapter allows the user to insert a custom implementation
// of the DBAdapter interface
func NewWithAdapter(
	db DBAdapter,
	dialectName string,
) (DB, error) {
	dialect := supportedDialects[dialectName]
	if dialect == nil {
		return DB{}, fmt.Errorf("unsupported driver `%s`", dialectName)
	}

	return DB{
		dialect: dialect,
		driver:  dialectName,
		db:      db,
	}, nil
}

// Query queries several rows from the database,
// the input should be a slice of structs (or *struct) passed
// by reference and it will be filled with all the results.
//
// Note: it is very important to make sure the query will
// return a small known number of results, otherwise you risk
// of overloading the available memory.
func (c DB) Query(
	ctx context.Context,
	records interface{},
	query string,
	params ...interface{},
) error {
	slicePtr := reflect.ValueOf(records)
	slicePtrType := slicePtr.Type()
	if slicePtrType.Kind() != reflect.Ptr {
		return fmt.Errorf("ksql: expected to receive a pointer to slice of structs, but got: %T", records)
	}
	sliceType := slicePtrType.Elem()
	slice := slicePtr.Elem()
	structType, isSliceOfPtrs, err := kstructs.DecodeAsSliceOfStructs(sliceType)
	if err != nil {
		return err
	}

	if isSliceOfPtrs {
		// Truncate the slice so there is no risk
		// of overwritting records that were already saved
		// on the slice:
		slice = slice.Slice(0, 0)
	}

	info, err := kstructs.GetTagInfo(structType)
	if err != nil {
		return err
	}

	firstToken := strings.ToUpper(getFirstToken(query))
	if info.IsNestedStruct && firstToken == "SELECT" {
		// This error check is necessary, since if we can't build the select part of the query this feature won't work.
		return fmt.Errorf("can't generate SELECT query for nested struct: when using this feature omit the SELECT part of the query")
	}

	if firstToken == "FROM" {
		selectPrefix, err := buildSelectQuery(c.dialect, structType, info, selectQueryCache[c.dialect.DriverName()])
		if err != nil {
			return err
		}
		query = selectPrefix + query
	}

	rows, err := c.db.QueryContext(ctx, query, params...)
	if err != nil {
		return fmt.Errorf("error running query: %s", err.Error())
	}
	defer rows.Close()

	for idx := 0; rows.Next(); idx++ {
		// Allocate new slice elements
		// only if they are not already allocated:
		if slice.Len() <= idx {
			var elemValue reflect.Value
			elemValue = reflect.New(structType)
			if !isSliceOfPtrs {
				elemValue = elemValue.Elem()
			}
			slice = reflect.Append(slice, elemValue)
		}

		elemPtr := slice.Index(idx).Addr()
		if isSliceOfPtrs {
			// This is necessary since scanRows expects a *record not a **record
			elemPtr = elemPtr.Elem()
		}

		err = scanRows(c.dialect, rows, elemPtr.Interface())
		if err != nil {
			return err
		}
	}

	if err := rows.Close(); err != nil {
		return err
	}

	if rows.Err() != nil {
		return rows.Err()
	}

	// Update the original slice passed by reference:
	slicePtr.Elem().Set(slice)

	return nil
}

// QueryOne queries one instance from the database,
// the input struct must be passed by reference
// and the query should return only one result.
//
// QueryOne returns a ErrRecordNotFound if
// the query returns no results.
func (c DB) QueryOne(
	ctx context.Context,
	record interface{},
	query string,
	params ...interface{},
) error {
	v := reflect.ValueOf(record)
	t := v.Type()
	if t.Kind() != reflect.Ptr {
		return fmt.Errorf("ksql: expected to receive a pointer to struct, but got: %T", record)
	}

	if v.IsNil() {
		return fmt.Errorf("ksql: expected a valid pointer to struct as argument but received a nil pointer: %v", record)
	}

	tStruct := t.Elem()
	if tStruct.Kind() != reflect.Struct {
		return fmt.Errorf("ksql: expected to receive a pointer to struct, but got: %T", record)
	}

	info, err := kstructs.GetTagInfo(tStruct)
	if err != nil {
		return err
	}

	firstToken := strings.ToUpper(getFirstToken(query))
	if info.IsNestedStruct && firstToken == "SELECT" {
		// This error check is necessary, since if we can't build the select part of the query this feature won't work.
		return fmt.Errorf("can't generate SELECT query for nested struct: when using this feature omit the SELECT part of the query")
	}

	if firstToken == "FROM" {
		selectPrefix, err := buildSelectQuery(c.dialect, tStruct, info, selectQueryCache[c.dialect.DriverName()])
		if err != nil {
			return err
		}
		query = selectPrefix + query
	}

	rows, err := c.db.QueryContext(ctx, query, params...)
	if err != nil {
		return err
	}
	defer rows.Close()

	if !rows.Next() {
		if rows.Err() != nil {
			return rows.Err()
		}
		return ErrRecordNotFound
	}

	err = scanRowsFromType(c.dialect, rows, record, t, v)
	if err != nil {
		return err
	}

	return rows.Close()
}

// QueryChunks is meant to perform queries that returns
// more results than would normally fit on memory,
// for others cases the Query and QueryOne functions are indicated.
//
// The ChunkParser argument has 4 attributes:
// (1) The Query;
// (2) The query args;
// (3) The chunk size;
// (4) A callback function called ForEachChunk, that will be called
// to process each chunk loaded from the database.
//
// Note that the signature of the ForEachChunk callback can be
// any function that receives a slice of structs or a slice of
// pointers to struct as its only argument and that reflection
// will be used to instantiate this argument and to fill it
// with the database rows.
func (c DB) QueryChunks(
	ctx context.Context,
	parser ChunkParser,
) error {
	fnValue := reflect.ValueOf(parser.ForEachChunk)
	chunkType, err := kstructs.ParseInputFunc(parser.ForEachChunk)
	if err != nil {
		return err
	}

	chunk := reflect.MakeSlice(chunkType, 0, parser.ChunkSize)

	structType, isSliceOfPtrs, err := kstructs.DecodeAsSliceOfStructs(chunkType)
	if err != nil {
		return err
	}

	info, err := kstructs.GetTagInfo(structType)
	if err != nil {
		return err
	}

	firstToken := strings.ToUpper(getFirstToken(parser.Query))
	if info.IsNestedStruct && firstToken == "SELECT" {
		// This error check is necessary, since if we can't build the select part of the query this feature won't work.
		return fmt.Errorf("can't generate SELECT query for nested struct: when using this feature omit the SELECT part of the query")
	}

	if firstToken == "FROM" {
		selectPrefix, err := buildSelectQuery(c.dialect, structType, info, selectQueryCache[c.dialect.DriverName()])
		if err != nil {
			return err
		}
		parser.Query = selectPrefix + parser.Query
	}

	rows, err := c.db.QueryContext(ctx, parser.Query, parser.Params...)
	if err != nil {
		return err
	}
	defer rows.Close()

	var idx = 0
	for rows.Next() {
		// Allocate new slice elements
		// only if they are not already allocated:
		if chunk.Len() <= idx {
			var elemValue reflect.Value
			elemValue = reflect.New(structType)
			if !isSliceOfPtrs {
				elemValue = elemValue.Elem()
			}
			chunk = reflect.Append(chunk, elemValue)
		}

		err = scanRows(c.dialect, rows, chunk.Index(idx).Addr().Interface())
		if err != nil {
			return err
		}

		if idx < parser.ChunkSize-1 {
			idx++
			continue
		}

		idx = 0
		err, _ = fnValue.Call([]reflect.Value{chunk})[0].Interface().(error)
		if err != nil {
			if err == ErrAbortIteration {
				return nil
			}
			return err
		}
	}

	if err := rows.Close(); err != nil {
		return err
	}

	// If Next() returned false because of an error:
	if rows.Err() != nil {
		return rows.Err()
	}

	// If no rows were found or idx was reset to 0
	// on the last iteration skip this last call to ForEachChunk:
	if idx > 0 {
		chunk = chunk.Slice(0, idx)

		err, _ = fnValue.Call([]reflect.Value{chunk})[0].Interface().(error)
		if err != nil {
			if err == ErrAbortIteration {
				return nil
			}
			return err
		}
	}

	return nil
}

// Insert one or more instances on the database
//
// If the original instances have been passed by reference
// the ID is automatically updated after insertion is completed.
func (c DB) Insert(
	ctx context.Context,
	table Table,
	record interface{},
) error {
	v := reflect.ValueOf(record)
	t := v.Type()
	if err := assertStructPtr(t); err != nil {
		return fmt.Errorf(
			"ksql: expected record to be a pointer to struct, but got: %T",
			record,
		)
	}

	if v.IsNil() {
		return fmt.Errorf("ksql: expected a valid pointer to struct as argument but received a nil pointer: %v", record)
	}

	info, err := kstructs.GetTagInfo(t.Elem())
	if err != nil {
		return err
	}

	query, params, scanValues, err := buildInsertQuery(c.dialect, table.name, t, v, info, record, table.idColumns...)
	if err != nil {
		return err
	}

	switch table.insertMethodFor(c.dialect) {
	case insertWithReturning, insertWithOutput:
		err = c.insertReturningIDs(ctx, query, params, scanValues, table.idColumns)
	case insertWithLastInsertID:
		err = c.insertWithLastInsertID(ctx, t, v, info, record, query, params, table.idColumns[0])
	case insertWithNoIDRetrieval:
		err = c.insertWithNoIDRetrieval(ctx, query, params)
	default:
		// Unsupported drivers should be detected on the New() function,
		// So we don't expect the code to ever get into this default case.
		err = fmt.Errorf("code error: unsupported driver `%s`", c.driver)
	}

	return err
}

func (c DB) insertReturningIDs(
	ctx context.Context,
	query string,
	params []interface{},
	scanValues []interface{},
	idNames []string,
) error {
	rows, err := c.db.QueryContext(ctx, query, params...)
	if err != nil {
		return err
	}
	defer rows.Close()

	if !rows.Next() {
		err := fmt.Errorf("unexpected error when retrieving the id columns from the database")
		if rows.Err() != nil {
			err = rows.Err()
		}

		return err
	}

	err = rows.Scan(scanValues...)
	if err != nil {
		return err
	}

	return rows.Close()
}

func (c DB) insertWithLastInsertID(
	ctx context.Context,
	t reflect.Type,
	v reflect.Value,
	info kstructs.StructInfo,
	record interface{},
	query string,
	params []interface{},
	idName string,
) error {
	result, err := c.db.ExecContext(ctx, query, params...)
	if err != nil {
		return err
	}

	id, err := result.LastInsertId()
	if err != nil {
		return err
	}

	vID := reflect.ValueOf(id)
	tID := vID.Type()

	fieldAddr := v.Elem().Field(info.ByName(idName).Index).Addr()
	fieldType := fieldAddr.Type().Elem()

	if !tID.ConvertibleTo(fieldType) {
		return fmt.Errorf(
			"Can't convert last insert id of type int64 into field `%s` of type %s",
			idName,
			fieldType,
		)
	}

	fieldAddr.Elem().Set(vID.Convert(fieldType))
	return nil
}

func (c DB) insertWithNoIDRetrieval(
	ctx context.Context,
	query string,
	params []interface{},
) error {
	_, err := c.db.ExecContext(ctx, query, params...)
	return err
}

func assertStructPtr(t reflect.Type) error {
	if t.Kind() != reflect.Ptr {
		return fmt.Errorf("expected a Kind of Ptr but got: %s", t)
	}
	if t.Elem().Kind() != reflect.Struct {
		return fmt.Errorf("expected a Kind of Ptr to Struct but got: %s", t)
	}
	return nil
}

// Delete deletes one record from the database using the ID or IDs
// defined on the ksql.Table passed as second argument.
//
// For tables with a single ID column you can pass the record
// to be deleted as a struct, as a map or just pass the ID itself.
//
// For tables with composite keys you must pass the record
// as a struct or a map so that ksql can read all the composite keys
// from it.
//
// The examples below should work for both types of tables:
//
//     err := c.Delete(ctx, UsersTable, user)
//
//     err := c.Delete(ctx, UserPostsTable, map[string]interface{}{
//         "user_id": user.ID,
//         "post_id": post.ID,
//     })
//
// The example below is shorter but will only work for tables with a single primary key:
//
//     err := c.Delete(ctx, UsersTable, user.ID)
//
func (c DB) Delete(
	ctx context.Context,
	table Table,
	idOrRecord interface{},
) error {
	idMaps, err := normalizeIDsAsMaps(table.idColumns, []interface{}{idOrRecord})
	if err != nil {
		return err
	}

	var query string
	var params []interface{}
	if len(table.idColumns) == 1 {
		query, params = buildSingleKeyDeleteQuery(c.dialect, table.name, table.idColumns[0], idMaps)
	} else {
		query, params = buildCompositeKeyDeleteQuery(c.dialect, table.name, table.idColumns, idMaps)
	}

	result, err := c.db.ExecContext(ctx, query, params...)
	if err != nil {
		return err
	}

	n, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("unable to check if the record was succesfully deleted: %s", err)
	}

	if n == 0 {
		return ErrRecordNotFound
	}

	return err
}

func normalizeIDsAsMaps(idNames []string, ids []interface{}) ([]map[string]interface{}, error) {
	if len(idNames) == 0 {
		return nil, fmt.Errorf("internal ksql error: missing idNames")
	}

	idMaps := []map[string]interface{}{}
	for i := range ids {
		t := reflect.TypeOf(ids[i])
		switch t.Kind() {
		case reflect.Ptr:
			v := reflect.ValueOf(ids[i])
			if v.IsNil() {
				return nil, fmt.Errorf("ksql: expected a valid pointer to struct as argument but received a nil pointer: %v", ids[i])
			}

			fallthrough
		case reflect.Struct:
			m, err := kstructs.StructToMap(ids[i])
			if err != nil {
				return nil, errors.Wrapf(err, "could not get ID(s) from record on idx %d", i)
			}
			idMaps = append(idMaps, m)
		case reflect.Map:
			m, ok := ids[i].(map[string]interface{})
			if !ok {
				return nil, fmt.Errorf("expected map[string]interface{} but got %T", ids[i])
			}
			idMaps = append(idMaps, m)
		default:
			idMaps = append(idMaps, map[string]interface{}{
				idNames[0]: ids[i],
			})
		}
	}

	for i, m := range idMaps {
		for _, id := range idNames {
			if _, found := m[id]; !found {
				return nil, fmt.Errorf("missing required id field `%s` on record with idx %d", id, i)
			}
		}
	}

	return idMaps, nil
}

// Update updates the given instances on the database by id.
//
// Partial updates are supported, i.e. it will ignore nil pointer attributes
func (c DB) Update(
	ctx context.Context,
	table Table,
	record interface{},
) error {
	v := reflect.ValueOf(record)
	t := v.Type()
	tStruct := t
	if t.Kind() == reflect.Ptr {
		if v.IsNil() {
			return fmt.Errorf("ksql: expected a valid pointer to struct as argument but received a nil pointer: %v", record)
		}
		tStruct = t.Elem()
	}
	info, err := kstructs.GetTagInfo(tStruct)
	if err != nil {
		return err
	}

	query, params, err := buildUpdateQuery(c.dialect, table.name, info, record, table.idColumns...)
	if err != nil {
		return err
	}

	result, err := c.db.ExecContext(ctx, query, params...)
	if err != nil {
		return err
	}

	n, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf(
			"unexpected error: unable to fetch how many rows were affected by the update: %s",
			err,
		)
	}
	if n < 1 {
		return ErrRecordNotFound
	}

	return nil
}

func buildInsertQuery(
	dialect Dialect,
	tableName string,
	t reflect.Type,
	v reflect.Value,
	info kstructs.StructInfo,
	record interface{},
	idNames ...string,
) (query string, params []interface{}, scanValues []interface{}, err error) {
	recordMap, err := kstructs.StructToMap(record)
	if err != nil {
		return "", nil, nil, err
	}

	for _, fieldName := range idNames {
		field, found := recordMap[fieldName]
		if !found {
			continue
		}

		// Remove any ID field that was not set:
		if reflect.ValueOf(field).IsZero() {
			delete(recordMap, fieldName)
		}
	}

	columnNames := []string{}
	for col := range recordMap {
		columnNames = append(columnNames, col)
	}

	params = make([]interface{}, len(recordMap))
	valuesQuery := make([]string, len(recordMap))
	for i, col := range columnNames {
		recordValue := recordMap[col]
		params[i] = recordValue
		if info.ByName(col).SerializeAsJSON {
			params[i] = jsonSerializable{
				DriverName: dialect.DriverName(),
				Attr:       recordValue,
			}
		}

		valuesQuery[i] = dialect.Placeholder(i)
	}

	// Escape all cols to be sure they will be interpreted as column names:
	escapedColumnNames := []string{}
	for _, col := range columnNames {
		escapedColumnNames = append(escapedColumnNames, dialect.Escape(col))
	}

	var returningQuery, outputQuery string
	switch dialect.InsertMethod() {
	case insertWithReturning:
		escapedIDNames := []string{}
		for _, id := range idNames {
			escapedIDNames = append(escapedIDNames, dialect.Escape(id))
		}
		returningQuery = " RETURNING " + strings.Join(escapedIDNames, ", ")

		for _, id := range idNames {
			scanValues = append(
				scanValues,
				v.Elem().Field(info.ByName(id).Index).Addr().Interface(),
			)
		}
	case insertWithOutput:
		escapedIDNames := []string{}
		for _, id := range idNames {
			escapedIDNames = append(escapedIDNames, "INSERTED."+dialect.Escape(id))
		}
		outputQuery = " OUTPUT " + strings.Join(escapedIDNames, ", ")

		for _, id := range idNames {
			scanValues = append(
				scanValues,
				v.Elem().Field(info.ByName(id).Index).Addr().Interface(),
			)
		}
	}

	// Note that the outputQuery and the returningQuery depend
	// on the selected driver, thus, they might be empty strings.
	query = fmt.Sprintf(
		"INSERT INTO %s (%s)%s VALUES (%s)%s",
		dialect.Escape(tableName),
		strings.Join(escapedColumnNames, ", "),
		outputQuery,
		strings.Join(valuesQuery, ", "),
		returningQuery,
	)

	return query, params, scanValues, nil
}

func buildUpdateQuery(
	dialect Dialect,
	tableName string,
	info kstructs.StructInfo,
	record interface{},
	idFieldNames ...string,
) (query string, args []interface{}, err error) {
	recordMap, err := kstructs.StructToMap(record)
	if err != nil {
		return "", nil, err
	}
	numAttrs := len(recordMap)
	args = make([]interface{}, numAttrs)
	numNonIDArgs := numAttrs - len(idFieldNames)
	whereArgs := args[numNonIDArgs:]

	whereQuery := make([]string, len(idFieldNames))
	for i, fieldName := range idFieldNames {
		whereArgs[i] = recordMap[fieldName]
		whereQuery[i] = fmt.Sprintf(
			"%s = %s",
			dialect.Escape(fieldName),
			dialect.Placeholder(i+numNonIDArgs),
		)

		delete(recordMap, fieldName)
	}

	keys := []string{}
	for key := range recordMap {
		keys = append(keys, key)
	}

	var setQuery []string
	for i, k := range keys {
		recordValue := recordMap[k]
		if info.ByName(k).SerializeAsJSON {
			recordValue = jsonSerializable{
				DriverName: dialect.DriverName(),
				Attr:       recordValue,
			}
		}
		args[i] = recordValue
		setQuery = append(setQuery, fmt.Sprintf(
			"%s = %s",
			dialect.Escape(k),
			dialect.Placeholder(i),
		))
	}

	query = fmt.Sprintf(
		"UPDATE %s SET %s WHERE %s",
		dialect.Escape(tableName),
		strings.Join(setQuery, ", "),
		strings.Join(whereQuery, ", "),
	)

	return query, args, nil
}

// Exec just runs an SQL command on the database returning no rows.
func (c DB) Exec(ctx context.Context, query string, params ...interface{}) (rowsAffected int64, _ error) {
	result, err := c.db.ExecContext(ctx, query, params...)
	rowsAffected, _ = result.RowsAffected()
	return rowsAffected, err
}

// Transaction just runs an SQL command on the database returning no rows.
func (c DB) Transaction(ctx context.Context, fn func(Provider) error) error {
	switch txBeginner := c.db.(type) {
	case Tx:
		return fn(c)
	case TxBeginner:
		tx, err := txBeginner.BeginTx(ctx)
		if err != nil {
			return err
		}
		defer func() {
			if r := recover(); r != nil {
				rollbackErr := tx.Rollback(ctx)
				if rollbackErr != nil {
					r = errors.Wrap(rollbackErr,
						fmt.Sprintf("unable to rollback after panic with value: %v", r),
					)
				}
				panic(r)
			}
		}()

		ormCopy := c
		ormCopy.db = tx

		err = fn(ormCopy)
		if err != nil {
			rollbackErr := tx.Rollback(ctx)
			if rollbackErr != nil {
				err = errors.Wrap(rollbackErr,
					fmt.Sprintf("unable to rollback after error: %s", err.Error()),
				)
			}
			return err
		}

		return tx.Commit(ctx)

	default:
		return fmt.Errorf("can't start transaction: The DBAdapter doesn't implement the TxBegginner interface")
	}
}

type nopScanner struct{}

var nopScannerValue = reflect.ValueOf(&nopScanner{}).Interface()

func (nopScanner) Scan(value interface{}) error {
	return nil
}

func scanRows(dialect Dialect, rows Rows, record interface{}) error {
	v := reflect.ValueOf(record)
	t := v.Type()
	return scanRowsFromType(dialect, rows, record, t, v)
}

func scanRowsFromType(
	dialect Dialect,
	rows Rows,
	record interface{},
	t reflect.Type,
	v reflect.Value,
) error {
	if t.Kind() != reflect.Ptr {
		return fmt.Errorf("ksql: expected record to be a pointer to struct, but got: %T", record)
	}

	v = v.Elem()
	t = t.Elem()

	if t.Kind() != reflect.Struct {
		return fmt.Errorf("ksql: expected record to be a pointer to struct, but got: %T", record)
	}

	info, err := kstructs.GetTagInfo(t)
	if err != nil {
		return err
	}

	var scanArgs []interface{}
	if info.IsNestedStruct {
		// This version is positional meaning that it expect the arguments
		// to follow an specific order. It's ok because we don't allow the
		// user to type the "SELECT" part of the query for nested kstructs.
		scanArgs, err = getScanArgsForNestedStructs(dialect, rows, t, v, info)
		if err != nil {
			return err
		}
	} else {
		names, err := rows.Columns()
		if err != nil {
			return err
		}
		// Since this version uses the names of the columns it works
		// with any order of attributes/columns.
		scanArgs = getScanArgsFromNames(dialect, names, v, info)
	}

	return rows.Scan(scanArgs...)
}

func getScanArgsForNestedStructs(dialect Dialect, rows Rows, t reflect.Type, v reflect.Value, info kstructs.StructInfo) ([]interface{}, error) {
	scanArgs := []interface{}{}
	for i := 0; i < v.NumField(); i++ {
		if !info.ByIndex(i).Valid {
			continue
		}

		// TODO(vingarcia00): Handle case where type is pointer
		nestedStructInfo, err := kstructs.GetTagInfo(t.Field(i).Type)
		if err != nil {
			return nil, err
		}

		nestedStructValue := v.Field(i)
		for j := 0; j < nestedStructValue.NumField(); j++ {
			fieldInfo := nestedStructInfo.ByIndex(j)
			if !fieldInfo.Valid {
				continue
			}

			valueScanner := nopScannerValue
			if fieldInfo.Valid {
				valueScanner = nestedStructValue.Field(fieldInfo.Index).Addr().Interface()
				if fieldInfo.SerializeAsJSON {
					valueScanner = &jsonSerializable{
						DriverName: dialect.DriverName(),
						Attr:       valueScanner,
					}
				}
			}

			scanArgs = append(scanArgs, valueScanner)
		}
	}

	return scanArgs, nil
}

func getScanArgsFromNames(dialect Dialect, names []string, v reflect.Value, info kstructs.StructInfo) []interface{} {
	scanArgs := []interface{}{}
	for _, name := range names {
		fieldInfo := info.ByName(name)

		valueScanner := nopScannerValue
		if fieldInfo.Valid {
			valueScanner = v.Field(fieldInfo.Index).Addr().Interface()
			if fieldInfo.SerializeAsJSON {
				valueScanner = &jsonSerializable{
					DriverName: dialect.DriverName(),
					Attr:       valueScanner,
				}
			}
		}

		scanArgs = append(scanArgs, valueScanner)
	}

	return scanArgs
}

func buildSingleKeyDeleteQuery(
	dialect Dialect,
	table string,
	idName string,
	idMaps []map[string]interface{},
) (query string, params []interface{}) {
	values := []string{}
	for i, m := range idMaps {
		values = append(values, dialect.Placeholder(i))
		params = append(params, m[idName])
	}

	return fmt.Sprintf(
		"DELETE FROM %s WHERE %s IN (%s)",
		dialect.Escape(table),
		dialect.Escape(idName),
		strings.Join(values, ","),
	), params
}

func buildCompositeKeyDeleteQuery(
	dialect Dialect,
	table string,
	idNames []string,
	idMaps []map[string]interface{},
) (query string, params []interface{}) {
	escapedNames := []string{}
	for _, name := range idNames {
		escapedNames = append(escapedNames, dialect.Escape(name))
	}

	values := []string{}
	for _, m := range idMaps {
		tuple := []string{}
		for _, name := range idNames {
			params = append(params, m[name])
			tuple = append(tuple, dialect.Placeholder(len(values)))
		}
		values = append(values, "("+strings.Join(tuple, ",")+")")
	}

	return fmt.Sprintf(
		"DELETE FROM %s WHERE (%s) IN (VALUES %s)",
		dialect.Escape(table),
		strings.Join(escapedNames, ","),
		strings.Join(values, ","),
	), params
}

// We implemented this function instead of using
// a regex or strings.Fields because we wanted
// to preserve the performance of the package.
func getFirstToken(s string) string {
	s = strings.TrimLeftFunc(s, unicode.IsSpace)

	var token strings.Builder
	for _, c := range s {
		if unicode.IsSpace(c) {
			break
		}
		token.WriteRune(c)
	}
	return token.String()
}

func buildSelectQuery(
	dialect Dialect,
	structType reflect.Type,
	info kstructs.StructInfo,
	selectQueryCache map[reflect.Type]string,
) (query string, err error) {
	if selectQuery, found := selectQueryCache[structType]; found {
		return selectQuery, nil
	}

	if info.IsNestedStruct {
		query, err = buildSelectQueryForNestedStructs(dialect, structType, info)
		if err != nil {
			return "", err
		}
	} else {
		query = buildSelectQueryForPlainStructs(dialect, structType, info)
	}

	selectQueryCache[structType] = query
	return query, nil
}

func buildSelectQueryForPlainStructs(
	dialect Dialect,
	structType reflect.Type,
	info kstructs.StructInfo,
) string {
	var fields []string
	for i := 0; i < structType.NumField(); i++ {
		fieldInfo := info.ByIndex(i)
		if !fieldInfo.Valid {
			continue
		}

		fields = append(fields, dialect.Escape(fieldInfo.Name))
	}

	return "SELECT " + strings.Join(fields, ", ") + " "
}

func buildSelectQueryForNestedStructs(
	dialect Dialect,
	structType reflect.Type,
	info kstructs.StructInfo,
) (string, error) {
	var fields []string
	for i := 0; i < structType.NumField(); i++ {
		nestedStructInfo := info.ByIndex(i)
		if !nestedStructInfo.Valid {
			continue
		}

		nestedStructName := nestedStructInfo.Name
		nestedStructType := structType.Field(i).Type
		if nestedStructType.Kind() != reflect.Struct {
			return "", fmt.Errorf(
				"expected nested struct with `tablename:\"%s\"` to be a kind of Struct, but got %v",
				nestedStructName, nestedStructType,
			)
		}

		nestedStructTagInfo, err := kstructs.GetTagInfo(nestedStructType)
		if err != nil {
			return "", err
		}

		for j := 0; j < structType.Field(i).Type.NumField(); j++ {
			fieldInfo := nestedStructTagInfo.ByIndex(j)
			if !fieldInfo.Valid {
				continue
			}

			fields = append(
				fields,
				dialect.Escape(nestedStructName)+"."+dialect.Escape(fieldInfo.Name),
			)
		}
	}

	return "SELECT " + strings.Join(fields, ", ") + " ", nil
}


<|start_filename|>kbuilder/insert.go<|end_filename|>
package kbuilder

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/vingarcia/ksql"
	"github.com/vingarcia/ksql/kstructs"
)

// Insert is the struct template for building INSERT queries
type Insert struct {
	// Into expects a table name, e.g. "users"
	Into string

	// Data expected either a single record annotated with `ksql` tags
	// or a list of records annotated likewise.
	Data interface{}
}

// Build is a utility function for finding the dialect based on the driver and
// then calling BuildQuery(dialect)
func (i Insert) Build(driver string) (sqlQuery string, params []interface{}, _ error) {
	dialect, err := ksql.GetDriverDialect(driver)
	if err != nil {
		return "", nil, err
	}

	return i.BuildQuery(dialect)
}

// BuildQuery implements the queryBuilder interface
func (i Insert) BuildQuery(dialect ksql.Dialect) (sqlQuery string, params []interface{}, _ error) {
	var b strings.Builder
	b.WriteString("INSERT INTO " + dialect.Escape(i.Into))

	if i.Into == "" {
		return "", nil, fmt.Errorf(
			"expected the Into attr to contain the tablename, but got an empty string instead",
		)
	}

	if i.Data == nil {
		return "", nil, fmt.Errorf(
			"expected the Data attr to contain a struct or a list of structs, but got `%v`",
			i.Data,
		)
	}

	v := reflect.ValueOf(i.Data)
	t := v.Type()
	if t.Kind() != reflect.Slice {
		// Convert it to a slice of a single element:
		v = reflect.Append(reflect.MakeSlice(reflect.SliceOf(t), 0, 1), v)
	} else {
		t = t.Elem()
	}

	if v.Len() == 0 {
		return "", nil, fmt.Errorf(
			"can't create an insertion query from an empty list of values",
		)
	}

	isPtr := false
	if t.Kind() == reflect.Ptr {
		isPtr = true
		t = t.Elem()
	}

	if t.Kind() != reflect.Struct {
		return "", nil, fmt.Errorf("expected Data attr to be a struct or slice of structs but got: %v", t)
	}

	info, err := kstructs.GetTagInfo(t)
	if err != nil {
		return "", nil, err
	}

	b.WriteString(" (")
	var escapedNames []string
	for i := 0; i < info.NumFields(); i++ {
		name := info.ByIndex(i).Name
		escapedNames = append(escapedNames, dialect.Escape(name))
	}
	b.WriteString(strings.Join(escapedNames, ", "))
	b.WriteString(") VALUES ")

	params = []interface{}{}
	values := []string{}
	for i := 0; i < v.Len(); i++ {
		record := v.Index(i)
		if isPtr {
			record = record.Elem()
		}

		placeholders := []string{}
		for j := 0; j < info.NumFields(); j++ {
			placeholders = append(placeholders, dialect.Placeholder(len(params)))
			params = append(params, record.Field(j).Interface())
		}
		values = append(values, "("+strings.Join(placeholders, ", ")+")")
	}
	b.WriteString(strings.Join(values, ", "))

	return b.String(), params, nil
}


<|start_filename|>examples/example_service/example_service_test.go<|end_filename|>
package exampleservice

import (
	"context"
	"testing"

	gomock "github.com/golang/mock/gomock"
	"github.com/tj/assert"
	"github.com/vingarcia/ksql"
	"github.com/vingarcia/ksql/kstructs"
	"github.com/vingarcia/ksql/nullable"
)

func TestCreateUser(t *testing.T) {
	t.Run("should call ksql.Insert correctly", func(t *testing.T) {
		controller := gomock.NewController(t)
		defer controller.Finish()

		mockDB := NewMockProvider(controller)

		s := Service{
			db:              mockDB,
			streamChunkSize: 100,
		}

		var users []interface{}
		mockDB.EXPECT().Insert(gomock.Any(), gomock.Any(), gomock.Any()).
			DoAndReturn(func(ctx context.Context, table ksql.Table, records ...interface{}) error {
				users = append(users, records...)
				return nil
			})

		user := UserEntity{Name: nullable.String("TestUser")}

		err := s.CreateUser(context.TODO(), user)
		assert.Nil(t, err)
		assert.Equal(t, 1, len(users))
		assert.Equal(t, &user, users[0])
	})

	t.Run("another way of testing input structs", func(t *testing.T) {
		controller := gomock.NewController(t)
		defer controller.Finish()

		mockDB := NewMockProvider(controller)

		s := Service{
			db:              mockDB,
			streamChunkSize: 100,
		}

		var users []map[string]interface{}
		mockDB.EXPECT().Insert(gomock.Any(), gomock.Any(), gomock.Any()).
			DoAndReturn(func(ctx context.Context, table ksql.Table, records ...interface{}) error {
				for _, record := range records {
					// The StructToMap function will convert a struct with `ksql` tags
					// into a map using the ksql attr names as keys.
					//
					// If you are inserting an anonymous struct (not usual) this function
					// can make your tests shorter:
					uMap, err := kstructs.StructToMap(record)
					if err != nil {
						return err
					}
					users = append(users, uMap)
				}
				return nil
			})

		user := UserEntity{Name: nullable.String("TestUser")}

		err := s.CreateUser(context.TODO(), user)
		assert.Nil(t, err)
		assert.Equal(t, 1, len(users))

		assert.Equal(t, "TestUser", users[0]["name"])
	})
}

func TestUpdateUserScore(t *testing.T) {
	t.Run("should call ksql.QueryOne() & Update() correctly", func(t *testing.T) {
		controller := gomock.NewController(t)
		defer controller.Finish()

		mockDB := NewMockProvider(controller)

		s := Service{
			db:              mockDB,
			streamChunkSize: 100,
		}

		var users []interface{}
		gomock.InOrder(
			mockDB.EXPECT().QueryOne(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
				DoAndReturn(func(ctx context.Context, result interface{}, query string, params ...interface{}) error {
					// This function will use reflection to fill the
					// struct fields with the values from the map
					return kstructs.FillStructWith(result, map[string]interface{}{
						// Use int this map the keys you set on the ksql tags, e.g. `ksql:"score"`
						// Each of these fields represent the database rows returned
						// by the query.
						"score": 42,
					})
				}),
			mockDB.EXPECT().Update(gomock.Any(), gomock.Any(), gomock.Any()).
				DoAndReturn(func(ctx context.Context, table ksql.Table, records ...interface{}) error {
					users = append(users, records...)
					return nil
				}),
		)

		err := s.UpdateUserScore(context.TODO(), 1, -2)
		assert.Nil(t, err)
		assert.Equal(t, 1, len(users))

		resultUser := UserEntity{
			ID:    1,
			Score: nullable.Int(40),
		}
		assert.Equal(t, &resultUser, users[0])
	})
}

func TestListUsers(t *testing.T) {
	t.Run("should call ksql.QueryOne() & Query() correctly", func(t *testing.T) {
		controller := gomock.NewController(t)
		defer controller.Finish()

		mockDB := NewMockProvider(controller)

		s := Service{
			db:              mockDB,
			streamChunkSize: 100,
		}

		gomock.InOrder(
			mockDB.EXPECT().QueryOne(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
				DoAndReturn(func(ctx context.Context, result interface{}, query string, params ...interface{}) error {
					// This function will use reflection to fill the
					// struct fields with the values from the map
					return kstructs.FillStructWith(result, map[string]interface{}{
						// Use int this map the keys you set on the ksql tags, e.g. `ksql:"score"`
						// Each of these fields represent the database rows returned
						// by the query.
						"count": 420,
					})
				}),
			mockDB.EXPECT().Query(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
				DoAndReturn(func(ctx context.Context, results interface{}, query string, params ...interface{}) error {
					return kstructs.FillSliceWith(results, []map[string]interface{}{
						{
							"id":   1,
							"name": "<NAME>",
							"age":  42,
						},
						{
							"id":   2,
							"name": "another <NAME>",
							"age":  43,
						},
					})
				}),
		)

		total, users, err := s.ListUsers(context.TODO(), 40, 2)
		assert.Nil(t, err)
		assert.Equal(t, 420, total)
		assert.Equal(t, 2, len(users))

		expectedUsers := []UserEntity{
			{
				ID:   1,
				Name: nullable.String("<NAME>"),
				Age:  nullable.Int(42),
			},
			{
				ID:   2,
				Name: nullable.String("another fake name"),
				Age:  nullable.Int(43),
			},
		}
		assert.Equal(t, expectedUsers, users)
	})
}

func TestStreamAllUsers(t *testing.T) {
	t.Run("should call ksql.QueryChunks correctly", func(t *testing.T) {
		controller := gomock.NewController(t)
		defer controller.Finish()

		mockDB := NewMockProvider(controller)

		s := Service{
			db:              mockDB,
			streamChunkSize: 2,
		}

		mockDB.EXPECT().QueryChunks(gomock.Any(), gomock.Any()).
			DoAndReturn(func(ctx context.Context, parser ksql.ChunkParser) error {
				// Chunk 1:
				err := kstructs.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{
					{
						"id":   1,
						"name": "<NAME>",
						"age":  42,
					},
					{
						"id":   2,
						"name": "another fake name",
						"age":  43,
					},
				})
				if err != nil {
					return err
				}

				// Chunk 2:
				err = kstructs.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{
					{
						"id":   3,
						"name": "<NAME> fake name",
						"age":  44,
					},
				})
				return err
			})

		var users []UserEntity
		err := s.StreamAllUsers(context.TODO(), func(u UserEntity) error {
			users = append(users, u)
			return nil
		})

		assert.Nil(t, err)
		assert.Equal(t, 3, len(users))

		expectedUsers := []UserEntity{
			{
				ID:   1,
				Name: nullable.String("<NAME>"),
				Age:  nullable.Int(42),
			},
			{
				ID:   2,
				Name: nullable.String("another fake name"),
				Age:  nullable.Int(43),
			},
			{
				ID:   3,
				Name: nullable.String("yet another fake name"),
				Age:  nullable.Int(44),
			},
		}
		assert.Equal(t, expectedUsers, users)
	})
}

func TestDeleteUser(t *testing.T) {
	t.Run("should call ksql.Delete correctly", func(t *testing.T) {
		controller := gomock.NewController(t)
		defer controller.Finish()

		mockDB := NewMockProvider(controller)

		s := Service{
			db:              mockDB,
			streamChunkSize: 100,
		}

		var ids []interface{}
		mockDB.EXPECT().Delete(gomock.Any(), gomock.Any(), gomock.Any()).
			DoAndReturn(func(ctx context.Context, table ksql.Table, idArgs ...interface{}) error {
				ids = append(ids, idArgs...)
				return nil
			})

		err := s.DeleteUser(context.TODO(), 42)
		assert.Nil(t, err)
		assert.Equal(t, 1, len(ids))
		assert.Equal(t, 42, ids[0])
	})
}


<|start_filename|>kbuilder/insert_test.go<|end_filename|>
package kbuilder_test

import (
	"testing"

	"github.com/tj/assert"
	"github.com/vingarcia/ksql/kbuilder"
)

func TestInsertQuery(t *testing.T) {
	tests := []struct {
		desc           string
		query          kbuilder.Insert
		expectedQuery  string
		expectedParams []interface{}
		expectedErr    bool
	}{
		{
			desc: "should build queries witha single record correctly",
			query: kbuilder.Insert{
				Into: "users",
				Data: &User{
					Name: "foo",
					Age:  42,
				},
			},
			expectedQuery:  `INSERT INTO "users" ("name", "age") VALUES ($1, $2)`,
			expectedParams: []interface{}{"foo", 42},
		},
		{
			desc: "should build queries with multiple records correctly",
			query: kbuilder.Insert{
				Into: "users",
				Data: []User{
					{
						Name: "foo",
						Age:  42,
					},
					{
						Name: "bar",
						Age:  43,
					},
				},
			},
			expectedQuery:  `INSERT INTO "users" ("name", "age") VALUES ($1, $2), ($3, $4)`,
			expectedParams: []interface{}{"foo", 42, "bar", 43},
		},

		/* * * * * Testing error cases: * * * * */
		{
			desc: "should report error if the `Data` attribute is missing",
			query: kbuilder.Insert{
				Into: "users",
			},

			expectedErr: true,
		},
		{
			desc: "should report error if the `Into` attribute is missing",
			query: kbuilder.Insert{
				Data: &User{
					Name: "foo",
					Age:  42,
				},
			},

			expectedErr: true,
		},
		{
			desc: "should report error if `Data` contains an empty list",
			query: kbuilder.Insert{
				Into: "users",
				Data: []User{},
			},

			expectedErr: true,
		},
	}

	for _, test := range tests {
		t.Run(test.desc, func(t *testing.T) {
			b, err := kbuilder.New("postgres")
			assert.Equal(t, nil, err)

			query, params, err := b.Build(test.query)

			expectError(t, test.expectedErr, err)
			assert.Equal(t, test.expectedQuery, query)
			assert.Equal(t, test.expectedParams, params)
		})
	}
}


<|start_filename|>kbuilder/query_test.go<|end_filename|>
package kbuilder_test

import (
	"fmt"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/tj/assert"
	"github.com/vingarcia/ksql/kbuilder"
)

type User struct {
	Name string `ksql:"name"`
	Age  int    `ksql:"age"`
}

var nullField *int

func TestSelectQuery(t *testing.T) {
	tests := []struct {
		desc           string
		query          kbuilder.Query
		expectedQuery  string
		expectedParams []interface{}
		expectedErr    bool
	}{
		{
			desc: "should build queries correctly",
			query: kbuilder.Query{
				Select: &User{},
				From:   "users",
				Where: kbuilder.
					Where("foo < %s", 42).
					Where("bar LIKE %s", "%ending").
					WhereIf("foobar = %s", nullField),

				OrderBy: kbuilder.OrderBy("id").Desc(),
				Offset:  100,
				Limit:   10,
			},
			expectedQuery:  `SELECT "name", "age" FROM users WHERE foo < $1 AND bar LIKE $2 ORDER BY id DESC LIMIT 10 OFFSET 100`,
			expectedParams: []interface{}{42, "%ending"},
		},
		{
			desc: "should build queries omitting the OFFSET",
			query: kbuilder.Query{
				Select: &User{},
				From:   "users",
				Where: kbuilder.
					Where("foo < %s", 42).
					Where("bar LIKE %s", "%ending").
					WhereIf("foobar = %s", nullField),

				OrderBy: kbuilder.OrderBy("id").Desc(),
				Limit:   10,
			},
			expectedQuery:  `SELECT "name", "age" FROM users WHERE foo < $1 AND bar LIKE $2 ORDER BY id DESC LIMIT 10`,
			expectedParams: []interface{}{42, "%ending"},
		},
		{
			desc: "should build queries omitting the LIMIT",
			query: kbuilder.Query{
				Select: &User{},
				From:   "users",
				Where: kbuilder.
					Where("foo < %s", 42).
					Where("bar LIKE %s", "%ending").
					WhereIf("foobar = %s", nullField),

				OrderBy: kbuilder.OrderBy("id").Desc(),
				Offset:  100,
			},
			expectedQuery:  `SELECT "name", "age" FROM users WHERE foo < $1 AND bar LIKE $2 ORDER BY id DESC OFFSET 100`,
			expectedParams: []interface{}{42, "%ending"},
		},
		{
			desc: "should build queries omitting the ORDER BY clause",
			query: kbuilder.Query{
				Select: &User{},
				From:   "users",
				Where: kbuilder.
					Where("foo < %s", 42).
					Where("bar LIKE %s", "%ending").
					WhereIf("foobar = %s", nullField),

				Offset: 100,
				Limit:  10,
			},
			expectedQuery:  `SELECT "name", "age" FROM users WHERE foo < $1 AND bar LIKE $2 LIMIT 10 OFFSET 100`,
			expectedParams: []interface{}{42, "%ending"},
		},
		{
			desc: "should build queries omitting the WHERE clause",
			query: kbuilder.Query{
				Select: &User{},
				From:   "users",

				OrderBy: kbuilder.OrderBy("id").Desc(),
				Offset:  100,
				Limit:   10,
			},
			expectedQuery: `SELECT "name", "age" FROM users ORDER BY id DESC LIMIT 10 OFFSET 100`,
		},

		/* * * * * Testing error cases: * * * * */
		{
			desc: "should report error if the FROM clause is missing",
			query: kbuilder.Query{
				Select: &User{},
				Where: kbuilder.
					Where("foo < %s", 42).
					Where("bar LIKE %s", "%ending").
					WhereIf("foobar = %s", nullField),

				OrderBy: kbuilder.OrderBy("id").Desc(),
				Offset:  100,
				Limit:   10,
			},

			expectedErr: true,
		},
	}

	for _, test := range tests {
		t.Run(test.desc, func(t *testing.T) {
			b, err := kbuilder.New("postgres")
			assert.Equal(t, nil, err)

			query, params, err := b.Build(test.query)

			expectError(t, test.expectedErr, err)
			assert.Equal(t, test.expectedQuery, query)
			assert.Equal(t, test.expectedParams, params)
		})
	}
}

func expectError(t *testing.T, expect bool, err error) {
	if expect {
		require.Equal(t, true, err != nil, "expected an error, but got nothing")
	} else {
		require.Equal(t, false, err != nil, fmt.Sprintf("unexpected error %s", err))
	}
}
