Merge pull request #2 from VinGarcia/kbuilder

Adds the kbuilder package (an integrated query builder)
This commit is contained in:
Vinícius Garcia 2021-09-17 22:08:19 -03:00 committed by GitHub
commit 33dd982d7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 679 additions and 20 deletions

View File

@ -68,7 +68,7 @@ The current interface is as follows and we plan on keeping
it with as little functions as possible, so don't expect many additions:
```go
// Provider describes the public behavior of this ORM
// Provider describes the ksql public behavior
type Provider interface {
Insert(ctx context.Context, table Table, record interface{}) error
Update(ctx context.Context, table Table, record interface{}) error

View File

@ -14,7 +14,7 @@ var ErrRecordNotFound error = errors.Wrap(sql.ErrNoRows, "ksql: the query return
// ErrAbortIteration ...
var ErrAbortIteration error = fmt.Errorf("ksql: abort iteration, should only be used inside QueryChunks function")
// Provider describes the public behavior of this ORM
// Provider describes the ksql public behavior
type Provider interface {
Insert(ctx context.Context, table Table, record interface{}) error
Update(ctx context.Context, table Table, record interface{}) error
@ -69,7 +69,7 @@ func NewTable(tableName string, ids ...string) Table {
}
}
func (t Table) insertMethodFor(dialect dialect) insertMethod {
func (t Table) insertMethodFor(dialect Dialect) insertMethod {
if len(t.idColumns) == 1 {
return dialect.InsertMethod()
}

View File

@ -1,6 +1,9 @@
package ksql
import "strconv"
import (
"fmt"
"strconv"
)
type insertMethod int
@ -11,14 +14,16 @@ const (
insertWithNoIDRetrieval
)
var supportedDialects = map[string]dialect{
var supportedDialects = map[string]Dialect{
"postgres": &postgresDialect{},
"sqlite3": &sqlite3Dialect{},
"mysql": &mysqlDialect{},
"sqlserver": &sqlserverDialect{},
}
type dialect interface {
// Dialect is used to represent the different ways
// of writing SQL queries used by each SQL driver.
type Dialect interface {
InsertMethod() insertMethod
Escape(str string) string
Placeholder(idx int) string
@ -61,6 +66,21 @@ func (sqlite3Dialect) Placeholder(idx int) string {
return "?"
}
// GetDriverDialect instantiantes the dialect for the
// provided driver string, if the drive is not supported
// it returns an error
func GetDriverDialect(driver string) (Dialect, error) {
dialect, found := map[string]Dialect{
"postgres": &postgresDialect{},
"sqlite3": &sqlite3Dialect{},
}[driver]
if !found {
return nil, fmt.Errorf("unsupported driver `%s`", driver)
}
return dialect, nil
}
type mysqlDialect struct{}
func (mysqlDialect) DriverName() string {

1
go.mod
View File

@ -13,6 +13,7 @@ require (
github.com/lib/pq v1.10.2
github.com/mattn/go-sqlite3 v1.14.6
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.7.0 // indirect
github.com/tj/assert v0.0.3
google.golang.org/appengine v1.6.7 // indirect
)

14
kbuilder/README.md Normal file
View File

@ -0,0 +1,14 @@
# Welcome to the KISS Query Builder
This is the Keep It Stupid Simple query builder created to work
either in conjunction or separated from the ksql package.
This package was started after ksql and while the ksql is already
in a usable state I still don't recommend using this one since this
being actively implemented and might change without further warning.
## TODO List
- Add support to Update and Delete operations
- Improve support to JOINs by adding the `tablename` tag to the structs
- Add error check for when the Select, Insert and Update attrs are all empty

105
kbuilder/insert.go Normal file
View File

@ -0,0 +1,105 @@
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 := kstructs.GetTagInfo(t)
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
}

92
kbuilder/insert_test.go Normal file
View File

@ -0,0 +1,92 @@
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)
})
}
}

33
kbuilder/kbuilder.go Normal file
View File

@ -0,0 +1,33 @@
package kbuilder
import (
"github.com/vingarcia/ksql"
)
// Builder is the basic container for injecting
// query builder configurations.
//
// All the Query structs can also be called
// directly without this builder, but we kept it
// here for convenience.
type Builder struct {
dialect ksql.Dialect
}
type queryBuilder interface {
BuildQuery(dialect ksql.Dialect) (sqlQuery string, params []interface{}, _ error)
}
// New creates a new Builder container.
func New(driver string) (Builder, error) {
dialect, err := ksql.GetDriverDialect(driver)
return Builder{
dialect: dialect,
}, err
}
// Build receives a query builder struct, injects it with the configurations
// build the query according to its arguments.
func (builder *Builder) Build(query queryBuilder) (sqlQuery string, params []interface{}, _ error) {
return query.BuildQuery(builder.dialect)
}

218
kbuilder/query.go Normal file
View File

@ -0,0 +1,218 @@
package kbuilder
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/vingarcia/ksql"
"github.com/vingarcia/ksql/kstructs"
)
// Query is is the struct template for building SELECT queries.
type Query struct {
// Select expects either a struct using the `ksql` tags
// or a string listing the column names using SQL syntax,
// e.g.: `id, username, address`
Select interface{}
// From expects the FROM clause from an SQL query, e.g. `users JOIN posts USING(post_id)`
From string
// Where expects a list of WhereQuery instances built
// by the public Where() function.
Where WhereQueries
Limit int
Offset int
OrderBy OrderByQuery
}
// Build is a utility function for finding the dialect based on the driver and
// then calling BuildQuery(dialect)
func (q Query) Build(driver string) (sqlQuery string, params []interface{}, _ error) {
dialect, err := ksql.GetDriverDialect(driver)
if err != nil {
return "", nil, err
}
return q.BuildQuery(dialect)
}
// BuildQuery implements the queryBuilder interface
func (q Query) BuildQuery(dialect ksql.Dialect) (sqlQuery string, params []interface{}, _ error) {
var b strings.Builder
switch v := q.Select.(type) {
case string:
b.WriteString("SELECT " + v)
default:
selectQuery, err := buildSelectQuery(v, dialect)
if err != nil {
return "", nil, errors.Wrap(err, "error reading the Select field")
}
b.WriteString("SELECT " + selectQuery)
}
b.WriteString(" FROM " + q.From)
if len(q.Where) > 0 {
var whereQuery string
whereQuery, params = q.Where.build(dialect)
b.WriteString(" WHERE " + whereQuery)
}
if strings.TrimSpace(q.From) == "" {
return "", nil, fmt.Errorf("the From field is mandatory for every query")
}
if q.OrderBy.fields != "" {
b.WriteString(" ORDER BY " + q.OrderBy.fields)
if q.OrderBy.desc {
b.WriteString(" DESC")
}
}
if q.Limit > 0 {
b.WriteString(" LIMIT " + strconv.Itoa(q.Limit))
}
if q.Offset > 0 {
b.WriteString(" OFFSET " + strconv.Itoa(q.Offset))
}
return b.String(), params, nil
}
// WhereQuery represents a single condition in a WHERE expression.
type WhereQuery struct {
// Accepts any SQL boolean expression
// This expression may optionally contain
// string formatting directives %s and only %s.
//
// For each of these directives we expect a new param
// on the params list below.
//
// In the resulting query each %s will be properly replaced
// by placeholders according to the database driver, e.g. `$1`
// for postgres or `?` for sqlite3.
cond string
params []interface{}
}
// WhereQueries is the helper for creating complex WHERE queries
// in a dynamic way.
type WhereQueries []WhereQuery
func (w WhereQueries) build(dialect ksql.Dialect) (query string, params []interface{}) {
var conds []string
for _, whereQuery := range w {
var placeholders []interface{}
for i := range whereQuery.params {
placeholders = append(placeholders, dialect.Placeholder(len(params)+i))
}
conds = append(conds, fmt.Sprintf(whereQuery.cond, placeholders...))
params = append(params, whereQuery.params...)
}
return strings.Join(conds, " AND "), params
}
// Where adds a new bollean condition to an existing
// WhereQueries helper.
func (w WhereQueries) Where(cond string, params ...interface{}) WhereQueries {
return append(w, WhereQuery{
cond: cond,
params: params,
})
}
// WhereIf condionally adds a new boolean expression to the WhereQueries helper.
func (w WhereQueries) WhereIf(cond string, param interface{}) WhereQueries {
if param == nil || reflect.ValueOf(param).IsNil() {
return w
}
return append(w, WhereQuery{
cond: cond,
params: []interface{}{param},
})
}
// Where adds a new bollean condition to an existing
// WhereQueries helper.
func Where(cond string, params ...interface{}) WhereQueries {
return WhereQueries{{
cond: cond,
params: params,
}}
}
// WhereIf condionally adds a new boolean expression to the WhereQueries helper
func WhereIf(cond string, param interface{}) WhereQueries {
if param == nil || reflect.ValueOf(param).IsNil() {
return WhereQueries{}
}
return WhereQueries{{
cond: cond,
params: []interface{}{param},
}}
}
// OrderByQuery represents the ORDER BY part of the query
type OrderByQuery struct {
fields string
desc bool
}
// Desc is a setter function for configuring the
// ORDER BY part of the query as DESC
func (o OrderByQuery) Desc() OrderByQuery {
return OrderByQuery{
fields: o.fields,
desc: true,
}
}
// OrderBy is a helper for building the ORDER BY
// part of the query.
func OrderBy(fields string) OrderByQuery {
return OrderByQuery{
fields: fields,
desc: false,
}
}
var cachedSelectQueries = map[reflect.Type]string{}
// Builds the select query using cached info so that its efficient
func buildSelectQuery(obj interface{}, dialect ksql.Dialect) (string, error) {
t := reflect.TypeOf(obj)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return "", fmt.Errorf("expected to receive a pointer to struct, but got: %T", obj)
}
if query, found := cachedSelectQueries[t]; found {
return query, nil
}
info := kstructs.GetTagInfo(t)
var escapedNames []string
for i := 0; i < info.NumFields(); i++ {
name := info.ByIndex(i).Name
escapedNames = append(escapedNames, dialect.Escape(name))
}
query := strings.Join(escapedNames, ", ")
cachedSelectQueries[t] = query
return query, nil
}

144
kbuilder/query_test.go Normal file
View File

@ -0,0 +1,144 @@
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))
}
}

22
ksql.go
View File

@ -26,7 +26,7 @@ func init() {
// the KissSQL interface `ksql.Provider`.
type DB struct {
driver string
dialect dialect
dialect Dialect
db DBAdapter
}
@ -632,7 +632,7 @@ func (c DB) Update(
}
func buildInsertQuery(
dialect dialect,
dialect Dialect,
tableName string,
record interface{},
idNames ...string,
@ -736,7 +736,7 @@ func buildInsertQuery(
}
func buildUpdateQuery(
dialect dialect,
dialect Dialect,
tableName string,
record interface{},
idFieldNames ...string,
@ -857,7 +857,7 @@ func (nopScanner) Scan(value interface{}) error {
return nil
}
func scanRows(dialect dialect, rows Rows, record interface{}) error {
func scanRows(dialect Dialect, rows Rows, record interface{}) error {
v := reflect.ValueOf(record)
t := v.Type()
if t.Kind() != reflect.Ptr {
@ -892,7 +892,7 @@ func scanRows(dialect dialect, rows Rows, record interface{}) error {
return rows.Scan(scanArgs...)
}
func getScanArgsForNestedStructs(dialect dialect, rows Rows, t reflect.Type, v reflect.Value, info kstructs.StructInfo) []interface{} {
func getScanArgsForNestedStructs(dialect Dialect, rows Rows, t reflect.Type, v reflect.Value, info kstructs.StructInfo) []interface{} {
scanArgs := []interface{}{}
for i := 0; i < v.NumField(); i++ {
// TODO(vingarcia00): Handle case where type is pointer
@ -919,7 +919,7 @@ func getScanArgsForNestedStructs(dialect dialect, rows Rows, t reflect.Type, v r
return scanArgs
}
func getScanArgsFromNames(dialect dialect, names []string, v reflect.Value, info kstructs.StructInfo) []interface{} {
func getScanArgsFromNames(dialect Dialect, names []string, v reflect.Value, info kstructs.StructInfo) []interface{} {
scanArgs := []interface{}{}
for _, name := range names {
fieldInfo := info.ByName(name)
@ -942,7 +942,7 @@ func getScanArgsFromNames(dialect dialect, names []string, v reflect.Value, info
}
func buildSingleKeyDeleteQuery(
dialect dialect,
dialect Dialect,
table string,
idName string,
idMaps []map[string]interface{},
@ -962,7 +962,7 @@ func buildSingleKeyDeleteQuery(
}
func buildCompositeKeyDeleteQuery(
dialect dialect,
dialect Dialect,
table string,
idNames []string,
idMaps []map[string]interface{},
@ -1007,7 +1007,7 @@ func getFirstToken(s string) string {
}
func buildSelectQuery(
dialect dialect,
dialect Dialect,
structType reflect.Type,
info kstructs.StructInfo,
selectQueryCache map[reflect.Type]string,
@ -1030,7 +1030,7 @@ func buildSelectQuery(
}
func buildSelectQueryForPlainStructs(
dialect dialect,
dialect Dialect,
structType reflect.Type,
info kstructs.StructInfo,
) string {
@ -1043,7 +1043,7 @@ func buildSelectQueryForPlainStructs(
}
func buildSelectQueryForNestedStructs(
dialect dialect,
dialect Dialect,
structType reflect.Type,
info kstructs.StructInfo,
) (string, error) {

View File

@ -2022,7 +2022,7 @@ func shiftErrSlice(errs *[]error) error {
return err
}
func getUsersByID(db DBAdapter, dialect dialect, resultsPtr *[]User, ids ...uint) error {
func getUsersByID(db DBAdapter, dialect Dialect, resultsPtr *[]User, ids ...uint) error {
placeholders := make([]string, len(ids))
params := make([]interface{}, len(ids))
for i := range ids {
@ -2063,7 +2063,7 @@ func getUsersByID(db DBAdapter, dialect dialect, resultsPtr *[]User, ids ...uint
return nil
}
func getUserByID(db DBAdapter, dialect dialect, result *User, id uint) error {
func getUserByID(db DBAdapter, dialect Dialect, result *User, id uint) error {
rows, err := db.QueryContext(context.TODO(), `SELECT id, name, age, address FROM users WHERE id=`+dialect.Placeholder(0), id)
if err != nil {
return err

View File

@ -51,6 +51,11 @@ func (s StructInfo) add(field FieldInfo) {
s.byName[field.Name] = &field
}
// NumFields ...
func (s StructInfo) NumFields() int {
return len(s.byIndex)
}
// This cache is kept as a pkg variable
// because the total number of types on a program
// should be finite. So keeping a single cache here

View File

@ -1,6 +1,9 @@
package ksql
import "context"
import (
"context"
"fmt"
)
var _ Provider = Mock{}
@ -20,40 +23,64 @@ type Mock struct {
// Insert ...
func (m Mock) Insert(ctx context.Context, table Table, record interface{}) error {
if m.InsertFn == nil {
panic(fmt.Errorf("Mock.Insert(ctx, %v, %v) called but the ksql.Mock.InsertFn() is not set", table, record))
}
return m.InsertFn(ctx, table, record)
}
// Update ...
func (m Mock) Update(ctx context.Context, table Table, record interface{}) error {
if m.UpdateFn == nil {
panic(fmt.Errorf("Mock.Update(ctx, %v, %v) called but the ksql.Mock.UpdateFn() is not set", table, record))
}
return m.UpdateFn(ctx, table, record)
}
// Delete ...
func (m Mock) Delete(ctx context.Context, table Table, ids ...interface{}) error {
if m.DeleteFn == nil {
panic(fmt.Errorf("Mock.Delete(ctx, %v, %v) called but the ksql.Mock.DeleteFn() is not set", table, ids))
}
return m.DeleteFn(ctx, table, ids...)
}
// Query ...
func (m Mock) Query(ctx context.Context, records interface{}, query string, params ...interface{}) error {
if m.QueryFn == nil {
panic(fmt.Errorf("Mock.Query(ctx, %v, %s, %v) called but the ksql.Mock.QueryFn() is not set", records, query, params))
}
return m.QueryFn(ctx, records, query, params...)
}
// QueryOne ...
func (m Mock) QueryOne(ctx context.Context, record interface{}, query string, params ...interface{}) error {
if m.QueryOneFn == nil {
panic(fmt.Errorf("Mock.QueryOne(ctx, %v, %s, %v) called but the ksql.Mock.QueryOneFn() is not set", record, query, params))
}
return m.QueryOneFn(ctx, record, query, params...)
}
// QueryChunks ...
func (m Mock) QueryChunks(ctx context.Context, parser ChunkParser) error {
if m.QueryChunksFn == nil {
panic(fmt.Errorf("Mock.QueryChunks(ctx, %v) called but the ksql.Mock.QueryChunksFn() is not set", parser))
}
return m.QueryChunksFn(ctx, parser)
}
// Exec ...
func (m Mock) Exec(ctx context.Context, query string, params ...interface{}) error {
if m.ExecFn == nil {
panic(fmt.Errorf("Mock.Exec(ctx, %s, %v) called but the ksql.Mock.ExecFn() is not set", query, params))
}
return m.ExecFn(ctx, query, params...)
}
// Transaction ...
func (m Mock) Transaction(ctx context.Context, fn func(db Provider) error) error {
if m.TransactionFn == nil {
return fn(m)
}
return m.TransactionFn(ctx, fn)
}