From abe9159b003bbf0907c0e3654c6ddb333289cd36 Mon Sep 17 00:00:00 2001 From: doug-martin Date: Wed, 12 Jun 2019 16:02:16 -0500 Subject: [PATCH] v7.0.0 * Add linting checks and fixed errors * Renamed all snake_case variables to be camelCase. * Updated all sql generations methods to from `Sql` to `SQL` * `ToSql` -> `ToSQL` * `ToInsertSql` -> `ToInsertSQL` * `ToUpdateSql` -> `ToUpdateSQL` * `ToDeleteSql` -> `ToDeleteSQL` * `ToTruncateSql` -> `ToTruncateSQL` * Fixed examples to always map to a defined method --- .golangci.yml | 64 + HISTORY.md | 31 + Makefile | 6 + README.md | 1070 ++++--- adapters.go | 272 -- adapters/mysql/dataset_adapter_test.go | 227 -- adapters/mysql/mysql.go | 96 - adapters/postgres/dataset_adapter_test.go | 31 - adapters/postgres/postgres.go | 18 - adapters/sqlite3/dataset_adapter_test.go | 220 -- adapters/sqlite3/sqlite3.go | 93 - adapters_test.go | 49 - crud_exec.go | 410 --- database.go | 524 ++- database_example_test.go | 90 + database_test.go | 240 +- dataset.go | 940 ++++-- dataset_actions.go | 166 - dataset_actions_test.go | 389 --- dataset_delete.go | 99 - dataset_delete_test.go | 134 - dataset_insert.go | 231 -- dataset_insert_test.go | 692 ---- dataset_query_example_test.go | 374 +++ dataset_query_test.go | 458 +++ dataset_select.go | 447 --- dataset_select_test.go | 1177 ------- dataset_sql_example_test.go | 1280 ++++++++ dataset_sql_test.go | 2844 +++++++++++++++++ dataset_test.go | 1633 +++++----- dataset_update.go | 104 - dataset_update_test.go | 404 --- default_adapter.go | 1123 ------- dialect/mysql/mysql.go | 67 + dialect/mysql/mysql_dialect_test.go | 180 ++ {adapters => dialect}/mysql/mysql_test.go | 204 +- dialect/postgres/postgres.go | 16 + .../postgres/postgres_test.go | 173 +- dialect/sqlite3/sqlite3.go | 66 + dialect/sqlite3/sqlite3_dialect_test.go | 179 ++ {adapters => dialect}/sqlite3/sqlite3_test.go | 165 +- errors.go | 28 - example_test.go | 1888 ----------- exec/query_executor.go | 221 ++ exec/query_factory.go | 35 + exec/scanner.go | 122 + crud_exec_test.go => exec/scanner_test.go | 286 +- exp/alias.go | 38 + exp/bool.go | 156 + exp/cast.go | 52 + exp/clauses.go | 353 ++ exp/clauses_test.go | 563 ++++ exp/col.go | 84 + exp/compound.go | 19 + exp/conflict.go | 86 + exp/cte.go | 23 + exp/exp.go | 508 +++ exp/exp_list.go | 143 + exp/exp_map.go | 48 + exp/exp_test.go | 7 + exp/func.go | 48 + exp/ident.go | 133 + exp/insert.go | 182 ++ exp/insert_test.go | 229 ++ exp/join.go | 140 + exp/literal.go | 62 + exp/lock.go | 41 + exp/order.go | 47 + exp/range.go | 57 + exp/truncate.go | 11 + exp/update.go | 80 + expressions.go | 1605 +--------- expressions_example_test.go | 1650 ++++++++++ go.mod | 3 +- go.sum | 4 + go.test.sh | 8 +- goqu.go | 33 + goqu_example_test.go | 232 ++ goqu_test.go | 52 - internal/errors/error.go | 28 + internal/sb/sql_builder.go | 107 + tags.go => internal/tag/tags.go | 29 +- internal/util/reflect.go | 200 ++ internal/util/reflect_test.go | 89 + internal/util/value_slice.go | 33 + mocks/SQLDialect.go | 38 + sql_builder.go | 28 - sql_dialect.go | 1164 +++++++ sql_dialect_example_test.go | 23 + sql_dialect_options.go | 507 +++ sql_dialect_test.go | 1995 ++++++++++++ vendor/github.com/davecgh/go-spew/LICENSE | 2 +- .../github.com/davecgh/go-spew/spew/bypass.go | 187 +- .../davecgh/go-spew/spew/bypasssafe.go | 2 +- .../github.com/davecgh/go-spew/spew/common.go | 2 +- .../github.com/davecgh/go-spew/spew/dump.go | 10 +- .../github.com/davecgh/go-spew/spew/format.go | 4 +- vendor/github.com/stretchr/objx/.gitignore | 4 + vendor/github.com/stretchr/objx/.travis.yml | 13 + vendor/github.com/stretchr/objx/Gopkg.lock | 27 + vendor/github.com/stretchr/objx/Gopkg.toml | 3 + vendor/github.com/stretchr/objx/LICENSE | 22 + vendor/github.com/stretchr/objx/README.md | 78 + vendor/github.com/stretchr/objx/Taskfile.yml | 26 + vendor/github.com/stretchr/objx/accessors.go | 171 + vendor/github.com/stretchr/objx/constants.go | 13 + .../github.com/stretchr/objx/conversions.go | 108 + vendor/github.com/stretchr/objx/doc.go | 66 + vendor/github.com/stretchr/objx/map.go | 193 ++ vendor/github.com/stretchr/objx/mutations.go | 74 + vendor/github.com/stretchr/objx/security.go | 17 + vendor/github.com/stretchr/objx/tests.go | 17 + .../stretchr/objx/type_specific_codegen.go | 2501 +++++++++++++++ vendor/github.com/stretchr/objx/value.go | 56 + .../github.com/stretchr/testify/mock/doc.go | 44 + .../github.com/stretchr/testify/mock/mock.go | 886 +++++ vendor/modules.txt | 5 +- 117 files changed, 22717 insertions(+), 12318 deletions(-) create mode 100644 .golangci.yml create mode 100644 Makefile delete mode 100644 adapters.go delete mode 100644 adapters/mysql/dataset_adapter_test.go delete mode 100644 adapters/mysql/mysql.go delete mode 100644 adapters/postgres/dataset_adapter_test.go delete mode 100644 adapters/postgres/postgres.go delete mode 100644 adapters/sqlite3/dataset_adapter_test.go delete mode 100644 adapters/sqlite3/sqlite3.go delete mode 100644 adapters_test.go delete mode 100644 crud_exec.go create mode 100644 database_example_test.go delete mode 100644 dataset_actions.go delete mode 100644 dataset_actions_test.go delete mode 100644 dataset_delete.go delete mode 100644 dataset_delete_test.go delete mode 100644 dataset_insert.go delete mode 100644 dataset_insert_test.go create mode 100644 dataset_query_example_test.go create mode 100644 dataset_query_test.go delete mode 100644 dataset_select.go delete mode 100644 dataset_select_test.go create mode 100644 dataset_sql_example_test.go create mode 100644 dataset_sql_test.go delete mode 100644 dataset_update.go delete mode 100644 dataset_update_test.go delete mode 100644 default_adapter.go create mode 100644 dialect/mysql/mysql.go create mode 100644 dialect/mysql/mysql_dialect_test.go rename {adapters => dialect}/mysql/mysql_test.go (62%) create mode 100644 dialect/postgres/postgres.go rename {adapters => dialect}/postgres/postgres_test.go (71%) create mode 100644 dialect/sqlite3/sqlite3.go create mode 100644 dialect/sqlite3/sqlite3_dialect_test.go rename {adapters => dialect}/sqlite3/sqlite3_test.go (60%) delete mode 100644 errors.go delete mode 100644 example_test.go create mode 100644 exec/query_executor.go create mode 100644 exec/query_factory.go create mode 100644 exec/scanner.go rename crud_exec_test.go => exec/scanner_test.go (57%) create mode 100644 exp/alias.go create mode 100644 exp/bool.go create mode 100644 exp/cast.go create mode 100644 exp/clauses.go create mode 100644 exp/clauses_test.go create mode 100644 exp/col.go create mode 100644 exp/compound.go create mode 100644 exp/conflict.go create mode 100644 exp/cte.go create mode 100644 exp/exp.go create mode 100644 exp/exp_list.go create mode 100644 exp/exp_map.go create mode 100644 exp/exp_test.go create mode 100644 exp/func.go create mode 100644 exp/ident.go create mode 100644 exp/insert.go create mode 100644 exp/insert_test.go create mode 100644 exp/join.go create mode 100644 exp/literal.go create mode 100644 exp/lock.go create mode 100644 exp/order.go create mode 100644 exp/range.go create mode 100644 exp/truncate.go create mode 100644 exp/update.go create mode 100644 expressions_example_test.go create mode 100644 goqu_example_test.go delete mode 100644 goqu_test.go create mode 100644 internal/errors/error.go create mode 100644 internal/sb/sql_builder.go rename tags.go => internal/tag/tags.go (51%) create mode 100644 internal/util/reflect.go create mode 100644 internal/util/reflect_test.go create mode 100644 internal/util/value_slice.go create mode 100644 mocks/SQLDialect.go delete mode 100644 sql_builder.go create mode 100644 sql_dialect.go create mode 100644 sql_dialect_example_test.go create mode 100644 sql_dialect_options.go create mode 100644 sql_dialect_test.go create mode 100644 vendor/github.com/stretchr/objx/.gitignore create mode 100644 vendor/github.com/stretchr/objx/.travis.yml create mode 100644 vendor/github.com/stretchr/objx/Gopkg.lock create mode 100644 vendor/github.com/stretchr/objx/Gopkg.toml create mode 100644 vendor/github.com/stretchr/objx/LICENSE create mode 100644 vendor/github.com/stretchr/objx/README.md create mode 100644 vendor/github.com/stretchr/objx/Taskfile.yml create mode 100644 vendor/github.com/stretchr/objx/accessors.go create mode 100644 vendor/github.com/stretchr/objx/constants.go create mode 100644 vendor/github.com/stretchr/objx/conversions.go create mode 100644 vendor/github.com/stretchr/objx/doc.go create mode 100644 vendor/github.com/stretchr/objx/map.go create mode 100644 vendor/github.com/stretchr/objx/mutations.go create mode 100644 vendor/github.com/stretchr/objx/security.go create mode 100644 vendor/github.com/stretchr/objx/tests.go create mode 100644 vendor/github.com/stretchr/objx/type_specific_codegen.go create mode 100644 vendor/github.com/stretchr/objx/value.go create mode 100644 vendor/github.com/stretchr/testify/mock/doc.go create mode 100644 vendor/github.com/stretchr/testify/mock/mock.go diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..4dbb8367 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,64 @@ +linters-settings: + govet: + check-shadowing: true + settings: + printf: + funcs: + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf + golint: + min-confidence: 0 + gocyclo: + min-complexity: 20 + maligned: + suggest-new: true + goconst: + min-len: 2 + min-occurrences: 2 + depguard: + list-type: blacklist + packages: + # logging is allowed only by logutils.Log, logrus + # is allowed to use only in logutils package + - github.com/sirupsen/logrus + misspell: + locale: US + lll: + line-length: 140 + exclude: '\/\/ ' + goimports: + local-prefixes: github.com/golangci/golangci-lint + gocritic: + enabled-tags: + - performance + - style + disabled-checks: + - wrapperFunc + +linters: + enable-all: true + disable: + - maligned + - prealloc + - gochecknoglobals + - gochecknoinits + - dupl + +run: + skip-dirs: + - pkg/golinters/goanalysis/(checker|passes) + +issues: + exclude-rules: + - text: "weak cryptographic primitive" + linters: + - gosec + +# golangci.com configuration +# https://github.com/golangci/golangci/wiki/Configuration +service: + golangci-lint-version: 1.15.x # use the fixed version to not introduce new linters unexpectedly + prepare: + - echo "here I can run custom commands, but no preparation needed for this repo" \ No newline at end of file diff --git a/HISTORY.md b/HISTORY.md index 53bcc194..0ee5088f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,34 @@ +## v7.0.0 + +**Linting** +* Add linting checks and fixed errors + * Renamed all snake_case variables to be camelCase. + * Fixed examples to always map to a defined method +* Renamed `adapters` to `dialect` to more closely match their intended purpose. + +**API Changes** +* Updated all sql generations methods to from `Sql` to `SQL` + * `ToSql` -> `ToSQL` + * `ToInsertSql` -> `ToInsertSQL` + * `ToUpdateSql` -> `ToUpdateSQL` + * `ToDeleteSql` -> `ToDeleteSQL` + * `ToTruncateSql` -> `ToTruncateSQL` +* Abstracted out `dialect_options` from the adapter to make the dialect self contained. + * This also removed the dataset<->adapter co dependency making the dialect self contained. +* Refactored the `goqu.I` method. + * Added new `goqu.S`, `goqu.T` and `goqu.C` methods to clarify why type of identifier you are using. + * `goqu.I` should only be used when you have a qualified identifier (e.g. `goqu.I("my_schema.my_table.my_col") +* Added new `goqu.Dialect` method to make using `goqu` as an SQL builder easier. + +**Internal Changes** +* Pulled expressions into their own package + * Broke up expressions.go into multiple files to make working with and defining them easier. + * Moved the user facing methods into the main `goqu` to keep the same API as before. +* Added more examples +* Moved non-user facing structs and interfaces to internal modules to clean up API. +* Increased test coverage. + + ## v6.1.0 * Handle nil *time.Time Literal [#73](https://github.com/doug-martin/goqu/pull/73) and [#52](https://github.com/doug-martin/goqu/pull/52) - [@RoarkeRandall](https://github.com/RoarkeRandall) and [@quetz](https://github.com/quetz) diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..bbc4f602 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +#phony dependency task that does nothing +#"make executable" does not run if there is a ./executable directory, unless the task has a dependency +phony: + +lint: + golangci-lint run diff --git a/README.md b/README.md index d701afcb..23ecf62a 100644 --- a/README.md +++ b/README.md @@ -9,30 +9,17 @@ [![Build Status](https://travis-ci.org/doug-martin/goqu.svg?branch=master)](https://travis-ci.org/doug-martin/goqu) [![GoDoc](https://godoc.org/github.com/doug-martin/goqu?status.png)](http://godoc.org/github.com/doug-martin/goqu) [![codecov](https://codecov.io/gh/doug-martin/goqu/branch/master/graph/badge.svg)](https://codecov.io/gh/doug-martin/goqu) -[![Go Report Card](https://goreportcard.com/badge/github.com/doug-martin/goqu/v6)](https://goreportcard.com/report/github.com/doug-martin/goqu/v6) +[![Go Report Card](https://goreportcard.com/badge/github.com/doug-martin/goqu/v7)](https://goreportcard.com/report/github.com/doug-martin/goqu/v7) -`goqu` is an expressive SQL builder +`goqu` is an expressive SQL builder and executor -* [Basics](#basics) -* [Expressions](#expressions) - * [Complex Example](#complex-example) -* [Querying](#querying) - * [Dataset](#dataset) - * [Prepared Statements](#dataset_prepared) - * [Database](#database) - * [Transactions](#transactions) -* [Logging](#logging) -* [Adapters](#adapters) -* [Contributions](#contributions) -* [Changelog](https://github.com/doug-martin/goqu/tree/master/HISTORY.md) +## Installation -This library was built with the following goals: +```sh +go get -u github.com/doug-martin/goqu/v7 +``` -* Make the generation of SQL easy and enjoyable -* Provide a DSL that accounts for the common SQL expressions, NOT every nuance for each database. -* Allow users to use SQL when desired -* Provide a simple query API for scanning rows -* Allow the user to use the native sql.Db methods when desired +[Migrating Between Versions](#migrating) ## Features @@ -40,7 +27,7 @@ This library was built with the following goals: * Query Builder * Parameter interpolation (e.g `SELECT * FROM "items" WHERE "id" = ?` -> `SELECT * FROM "items" WHERE "id" = 1`) -* Built from the ground up with adapters in mind +* Built from the ground up with multiple dialects in mind * Insert, Multi Insert, Update, and Delete support * Scanning of rows to struct[s] or primitive value[s] @@ -50,515 +37,708 @@ or hooks I would recommend looking at some of the great ORM libraries such as: * [gorm](https://github.com/jinzhu/gorm) * [hood](https://github.com/eaigner/hood) +## Why? -## Installation +We tried a few other sql builders but each was a thin wrapper around sql fragments that we found error prone. `goqu` was built with the following goals in mind: -```sh -go get -u github.com/doug-martin/goqu/v6 -``` +* Make the generation of SQL easy and enjoyable +* Create an expressive DSL that would find common errors with SQL at compile time. +* Provide a DSL that accounts for the common SQL expressions, NOT every nuance for each database. +* Provide developers the ability to: + * Use SQL when desired + * Easily scan results into primitive values and structs + * Use the native sql.Db methods when desired + +## Usage + +* [Building SQL](#building-sql) + * [Expressions](#expressions) + * [`Ex{}`](#ex) - Expression map filtering + * [`ExOr{}`](#ex-or) - ORed expression map filtering + * [`S()`](#S) - Schema identifiers + * [`T()`](#T) - Table identifiers + * [`C()`](#C) - Column identifiers + * [`I()`](#I) - Parsing identifiers wit + * [`L()`](#L) - Literal SQL expressions + * [`And()`](#and) - ANDed sql expressions + * [`OR()`](#or) - ORed sql expressions + * [Complex Example](#complex-example) + * [Querying](#querying) + * [Executing Queries](#executing-queries) + * [Dataset](#dataset) + * [Prepared Statements](#dataset_prepared) +* [Database](#database) + * [Transactions](#transactions) +* [Logging](#logging) +* [Dialects](#dialects) + +## Dataset - -## Basics +A [`goqu.Dataset`](https://godoc.org/github.com/doug-martin/goqu#Dataset) is the most commonly used data structure used in `goqu`. A `Dataset` can be used to: +* [build SQL](#building-sql) - When used with a `dialect` and `expressions` a dataset is an expressive SQL builder +* [execute queries](#querying) - When used with a `goqu.Database` a `goqu.Dataset` can be used to: + * [`ScanStruct`](#ds-scan-struct) - scan into a struct + * [`ScanStructs`](#ds-scan-structs) - scan into a slice of structs + * [`ScanVal`](#ds-scan-val) - scan into a primitive value or a `driver.Valuer` + * [`ScanVals`](#ds-scan-vals) - scan into a slice of primitive values or `driver.Valuer`s + * [`Count`](#ds-count) - count the number of records in a table + * [`Pluck`](#ds-pluck) - pluck a column from a table + * [`Insert`](#ds-insert) - insert records into a table + * [`Update`](#ds-update) - update records in a table + * [`Delete`](#ds-delete) - delete records in a table -In order to start using goqu with your database you need to load an adapter. We have included some adapters by default. + +### Building SQL -1. Postgres - `import "github.com/doug-martin/goqu/v6/adapters/postgres"` -2. MySQL - `import "github.com/doug-martin/goqu/v6/adapters/mysql"` -3. SQLite3 - `import "github.com/doug-martin/goqu/v6/adapters/sqlite3"` +To build SQL with a dialect you can use `goqu.Dialect` -Adapters in goqu work the same way as a driver with the database in that they register themselves with goqu once loaded. +**NOTE** if you use do not create a `goqu.Database` you can only create SQL ```go import ( - "database/sql" - "github.com/doug-martin/goqu/v6" - _ "github.com/doug-martin/goqu/v6/adapters/postgres" - _ "github.com/lib/pq" + "fmt" + "github.com/doug-martin/goqu/v7" + _ "github.com/doug-martin/goqu/v7/dialect/postgres" ) -``` -Notice that we imported the adapter and driver for side effect only. -Once you have your adapter and driver loaded you can create a goqu.Database instance - -```go -pgDb, err := sql.Open("postgres", "user=postgres dbname=goqupostgres sslmode=disable ") -if err != nil { - panic(err.Error()) -} -db := goqu.New("postgres", pgDb) -``` -Now that you have your goqu.Database you can build your SQL and it will be formatted appropriately for the provided dialect. +dialect := goqu.Dialect("postgres") -```go //interpolated sql -sql, _, _ := db.From("user").Where(goqu.Ex{ - "id": 10, -}).ToSql() -fmt.Println(sql) +ds := dialect.From("test").Where(goqu.Ex{"id": 10}) +sql, args, err := ds.ToSQL() +if err != nil{ + fmt.Println("An error occurred while generating the SQL", err.Error()) +}else{ + fmt.Println(sql, args) +} //prepared sql -sql, args, _ := db.From("user"). - Prepared(true). - Where(goqu.Ex{ - "id": 10, - }). - ToSql() -fmt.Println(sql) +sql, args, err := ds.Prepared(true).ToSQL() +if err != nil{ + fmt.Println("An error occurred while generating the SQL", err.Error()) +}else{ + fmt.Println(sql, args) +} + ``` -Output -```sql -SELECT * FROM "user" WHERE "id" = 10 -SELECT * FROM "user" WHERE "id" = $1 + +Output: +``` +SELECT * FROM "test" WHERE "id" = 10 [] +SELECT * FROM "test" WHERE "id" = $1 [10] ``` ### Expressions -`goqu` provides an idiomatic DSL for generating SQL however the Dataset only provides the different clause methods (e.g. Where, From, Select), most of these clause methods accept Expressions(with a few exceptions) which are the building blocks for your SQL statement, you can think of them as fragments of SQL. +`goqu` provides an idiomatic DSL for generating SQL. Datasets only act as a clause builder (i.e. Where, From, Select), most of these clause methods accept Expressions which are the building blocks for your SQL statement, you can think of them as fragments of SQL. The entry points for expressions are: + * [`Ex{}`](https://godoc.org/github.com/doug-martin/goqu#Ex) - A map where the key will become an Identifier and the Key is the value, this is most commonly used in the Where clause. By default `Ex` will use the equality operator except in cases where the equality operator will not work, see the example below. -```go -sql, _, _ := db.From("items").Where(goqu.Ex{ - "col1": "a", - "col2": 1, - "col3": true, - "col4": false, - "col5": nil, - "col6": []string{"a", "b", "c"}, -}).ToSql() -fmt.Println(sql) -``` -Output: -```sql -SELECT * FROM "items" WHERE (("col1" = 'a') AND ("col2" = 1) AND ("col3" IS TRUE) AND ("col4" IS FALSE) AND ("col5" IS NULL) AND ("col6" IN ('a', 'b', 'c'))) -``` -You can also use the [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op) map which allows you to create more complex expressions using the map syntax. When using the `Op` map the key is the name of the comparison you want to make (e.g. `"neq"`, `"like"`, `"is"`, `"in"`), the key is case insensitive. -```go -sql, _, _ := db.From("items").Where(goqu.Ex{ + ```go + sql, _, _ := db.From("items").Where(goqu.Ex{ + "col1": "a", + "col2": 1, + "col3": true, + "col4": false, + "col5": nil, + "col6": []string{"a", "b", "c"}, + }).ToSQL() + fmt.Println(sql) + ``` + + Output: + ```sql + SELECT * FROM "items" WHERE (("col1" = 'a') AND ("col2" = 1) AND ("col3" IS TRUE) AND ("col4" IS FALSE) AND ("col5" IS NULL) AND ("col6" IN ('a', 'b', 'c'))) + ``` + + You can also use the [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op) map which allows you to create more complex expressions using the map syntax. When using the `Op` map the key is the name of the comparison you want to make (e.g. `"neq"`, `"like"`, `"is"`, `"in"`), the key is case insensitive. + ```go + sql, _, _ := db.From("items").Where(goqu.Ex{ "col1": goqu.Op{"neq": "a"}, "col3": goqu.Op{"isNot": true}, "col6": goqu.Op{"notIn": []string{"a", "b", "c"}}, -}).ToSql() -fmt.Println(sql) -``` -Output: -```sql -SELECT * FROM "items" WHERE (("col1" != 'a') AND ("col3" IS NOT TRUE) AND ("col6" NOT IN ('a', 'b', 'c'))) -``` -For a more complete examples see the [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op) and [`Ex`](https://godoc.org/github.com/doug-martin/goqu#Ex) docs + }).ToSQL() + fmt.Println(sql) + ``` + + Output: + ```sql + SELECT * FROM "items" WHERE (("col1" != 'a') AND ("col3" IS NOT TRUE) AND ("col6" NOT IN ('a', 'b', 'c'))) + ``` + For a more complete examples see the [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op) and [`Ex`](https://godoc.org/github.com/doug-martin/goqu#Ex) docs + * [`ExOr{}`](https://godoc.org/github.com/doug-martin/goqu#ExOr) - A map where the key will become an Identifier and the Key is the value, this is most commonly used in the Where clause. By default `ExOr` will use the equality operator except in cases where the equality operator will not work, see the example below. -```go -sql, _, _ := db.From("items").Where(goqu.ExOr{ - "col1": "a", - "col2": 1, - "col3": true, - "col4": false, - "col5": nil, - "col6": []string{"a", "b", "c"}, -}).ToSql() -fmt.Println(sql) -``` -Output: -```sql -SELECT * FROM "items" WHERE (("col1" = 'a') OR ("col2" = 1) OR ("col3" IS TRUE) OR ("col4" IS FALSE) OR ("col5" IS NULL) OR ("col6" IN ('a', 'b', 'c'))) -``` -You can also use the [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op) map which allows you to create more complex expressions using the map syntax. When using the `Op` map the key is the name of the comparison you want to make (e.g. `"neq"`, `"like"`, `"is"`, `"in"`), the key is case insensitive. -```go -sql, _, _ := db.From("items").Where(goqu.ExOr{ + ```go + sql, _, _ := db.From("items").Where(goqu.ExOr{ + "col1": "a", + "col2": 1, + "col3": true, + "col4": false, + "col5": nil, + "col6": []string{"a", "b", "c"}, + }).ToSQL() + fmt.Println(sql) + ``` + + Output: + ```sql + SELECT * FROM "items" WHERE (("col1" = 'a') OR ("col2" = 1) OR ("col3" IS TRUE) OR ("col4" IS FALSE) OR ("col5" IS NULL) OR ("col6" IN ('a', 'b', 'c'))) + ``` + + You can also use the [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op) map which allows you to create more complex expressions using the map syntax. When using the `Op` map the key is the name of the comparison you want to make (e.g. `"neq"`, `"like"`, `"is"`, `"in"`), the key is case insensitive. + + ```go + sql, _, _ := db.From("items").Where(goqu.ExOr{ "col1": goqu.Op{"neq": "a"}, "col3": goqu.Op{"isNot": true}, "col6": goqu.Op{"notIn": []string{"a", "b", "c"}}, -}).ToSql() -fmt.Println(sql) -``` -Output: -```sql -SELECT * FROM "items" WHERE (("col1" != 'a') OR ("col3" IS NOT TRUE) OR ("col6" NOT IN ('a', 'b', 'c'))) -``` -For a more complete examples see the [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op) and [`ExOr`](https://godoc.org/github.com/doug-martin/goqu#Ex) docs + }).ToSQL() + fmt.Println(sql) + ``` + + Output: + ```sql + SELECT * FROM "items" WHERE (("col1" != 'a') OR ("col3" IS NOT TRUE) OR ("col6" NOT IN ('a', 'b', 'c'))) + ``` + For a more complete examples see the [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op) and [`ExOr`](https://godoc.org/github.com/doug-martin/goqu#Ex) docs + + +* [`S()`](https://godoc.org/github.com/doug-martin/goqu#S) - An Identifier that represents a schema. With a schema identifier you can fully qualify tables and columns. + ```go + s := goqu.S("my_schema") + + // "my_schema"."my_table" + t := s.Table("my_table") + + // "my_schema"."my_table"."my_column" + + sql, _, _ := goqu.From(t).Select(t.Col("my_column").ToSQL() + // SELECT "my_schema"."my_table"."my_column" FROM "my_schema"."my_table" + fmt.Println(sql) + ``` + + +* [`T()`](https://godoc.org/github.com/doug-martin/goqu#T) - An Identifier that represents a Table. With a Table identifier you can fully qualify columns. + ```go + t := s.Table("my_table") + + sql, _, _ := goqu.From(t).Select(t.Col("my_column").ToSQL() + // SELECT "my_table"."my_column" FROM "my_table" + fmt.Println(sql) + + // qualify the table with a schema + sql, _, _ := goqu.From(t.Schema("my_schema")).Select(t.Col("my_column").ToSQL() + // SELECT "my_table"."my_column" FROM "my_schema"."my_table" + fmt.Println(sql) + ``` + + +* [`C()`](https://godoc.org/github.com/doug-martin/goqu#C) - An Identifier that represents a Column. See the [docs]((https://godoc.org/github.com/doug-martin/goqu#C)) for more examples + ```go + sql, _, _ := goqu.From("table").Where(goqu.C("col").Eq(10)).ToSQL() + // SELECT * FROM "table" WHERE "col" = 10 + fmt.Println(sql) + ``` + + +* [`I()`](https://godoc.org/github.com/doug-martin/goqu#I) - An Identifier represents a schema, table, or column or any combination. `I` parses identifiers seperated by a `.` character. + ```go + // with three parts it is assumed you have provided a schema, table and column + goqu.I("my_schema.table.col") == goqu.S("my_schema").Table("table").Col("col") + + // with two parts it is assumed you have provided a table and column + goqu.I("table.col") == goqu.T("table").Col("col") + + // with a single value it is the same as calling goqu.C + goqu.I("col") == goqu.C("col") + + ``` + + +* [`L()`](https://godoc.org/github.com/doug-martin/goqu#L) - An SQL literal. You may find yourself in a situation where an IdentifierExpression cannot expression an SQL fragment that your database supports. In that case you can use a LiteralExpression + ```go + // manual casting + goqu.L(`"json"::TEXT = "other_json"::text`) + + // custom function invocation + goqu.L(`custom_func("a")`) + + // postgres JSON access + goqu.L(`"json_col"->>'someField'`).As("some_field") + ``` + + You can also use placeholders in your literal with a `?` character. `goqu` will handle changing it to what the dialect needs (e.g. `?` mysql, `$1` postgres, `?` sqlite3). + + **NOTE** If your query is not prepared the placeholders will be properly interpolated. + + ```go + goqu.L("col IN (?, ?, ?)", "a", "b", "c") + ``` + + Putting it together + + ```go + ds := db.From("test").Where( + goqu.L(`("json"::TEXT = "other_json"::TEXT)`), + goqu.L("col IN (?, ?, ?)", "a", "b", "c"), + ) -* [`I()`](https://godoc.org/github.com/doug-martin/goqu#I) - An Identifier represents a schema, table, or column or any combination. You can use this when your expression cannot be expressed via the [`Ex`](https://godoc.org/github.com/doug-martin/goqu#Ex) map (e.g. Cast). -```go -goqu.I("my_schema.table.col") -goqu.I("table.col") -goqu.I("col") -``` -If you look at the [`IdentiferExpression`](https://godoc.org/github.com/doug-martin/goqu#IdentifierExpression) docs it implements many of your common sql operations that you would perform. -```go -goqu.I("col").Eq(10) -goqu.I("col").In([]int64{1,2,3,4}) -goqu.I("col").Like(regexp.MustCompile("^(a|b)") -goqu.I("col").IsNull() -``` -Please see the exmaples for [`I()`](https://godoc.org/github.com/doug-martin/goqu#example-I) to see more in depth examples + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) -* [`L()`](https://godoc.org/github.com/doug-martin/goqu#example-L) - An SQL literal. You may find yourself in a situation where an IdentifierExpression cannot expression an SQL fragment that your database supports. In that case you can use a LiteralExpression -```go -goqu.L(`"col"::TEXT = ""other_col"::text`) -``` -You can also use placeholders in your literal. When using the LiteralExpressions placeholders are normalized to the ? character and will be transformed to the correct placeholder for your adapter (e.g. `?` mysql, `$1` postgres, `?` sqlite3) -```go -goqu.L("col IN (?, ?, ?)", "a", "b", "c") -``` -Putting it together -```go -sql, _, _ := db.From("test").Where( - goqu.I("col").Eq(10), - goqu.L(`"json"::TEXT = "other_json"::TEXT`), -).ToSql() -fmt.Println(sql) -``` -```sql -SELECT * FROM "test" WHERE (("col" = 10) AND "json"::TEXT = "other_json"::TEXT) -``` -Both the Identifier and Literal expressions will be ANDed together by default. -You may however want to have your expressions ORed together you can use the [`Or()`](https://godoc.org/github.com/doug-martin/goqu#example-Or) function to create an ExpressionList -```go -sql, _, _ := db.From("test").Where( - goqu.Or( - goqu.I("col").Eq(10), - goqu.L(`"col"::TEXT = "other_col"::TEXT`), - ), -).ToSql() -fmt.Println(sql) -``` -```sql -SELECT * FROM "test" WHERE (("col" = 10) OR "col"::TEXT = "other_col"::TEXT) -``` + sql, args, _ := ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + ``` -```go -sql, _, _ := db.From("test").Where( - Or( - goqu.I("col").Eq(10), - goqu.L(`"col"::TEXT = "other_col"::TEXT`), - ), -).ToSql() -fmt.Println(sql) -``` -```sql -SELECT * FROM "test" WHERE (("col" = 10) OR "col"::TEXT = "other_col"::TEXT) -``` + Output: + ```sql + SELECT * FROM "test" WHERE ("json"::TEXT = "other_json"::TEXT) AND col IN ('a', 'b', 'c') [] + -- assuming postgres dialect + SELECT * FROM "test" WHERE ("json"::TEXT = "other_json"::TEXT) AND col IN ($1, $2, $3) [a, b, c] + ``` -You can also use Or and the And function in tandem which will give you control not only over how the Expressions are joined together, but also how they are grouped -```go -sql, _, _ := db.From("test").Where( - goqu.Or( - goqu.I("a").Gt(10), - goqu.And( - goqu.I("b").Eq(100), - goqu.I("c").Neq("test"), - ), - ), -).ToSql() -fmt.Println(sql) -``` -Output: -```sql -SELECT * FROM "test" WHERE (("a" > 10) OR (("b" = 100) AND ("c" != 'test'))) -``` + +* [`And()`](https://godoc.org/github.com/doug-martin/goqu#And) - You can use the `And` function to AND multiple expressions together. -You can also use Or with the map syntax -```go -sql, _, _ := db.From("test").Where( - goqu.Or( - //Ex will be anded together - goqu.Ex{ - "col1": nil, - "col2": true, - }, - goqu.Ex{ - "col3": nil, - "col4": false, - }, - goqu.L(`"col"::TEXT = "other_col"::TEXT`), - ), -).ToSql() -fmt.Println(sql) -``` -Output: -```sql -SELECT * FROM "test" WHERE ((("col1" IS NULL) AND ("col2" IS TRUE)) OR (("col3" IS NULL) AND ("col4" IS FALSE)) OR "col"::TEXT = "other_col"::TEXT) -``` + **NOTE** By default goqu will AND expressions together + + ```go + ds := goqu.From("test").Where( + goqu.And( + goqu.C("col").Gt(10), + goqu.C("col").Lt(20), + ), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + ``` + + Output: + ```sql + SELECT * FROM "test" WHERE (("col" > 10) AND ("col" < 20)) [] + SELECT * FROM "test" WHERE (("col" > ?) AND ("col" < ?)) [10 20] + ``` + + +* [`Or()`](https://godoc.org/github.com/doug-martin/goqu#Or) - You can use the `Or` function to OR multiple expressions together. + + ```go + ds := goqu.From("test").Where( + goqu.Or( + goqu.C("col").Eq(10), + goqu.C("col").Eq(20), + ), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + ``` + + Output: + ```sql + SELECT * FROM "test" WHERE (("col" = 10) OR ("col" = 20)) [] + SELECT * FROM "test" WHERE (("col" = ?) OR ("col" = ?)) [10 20] + ``` + + You can also use `Or` and `And` functions in tandem which will give you control not only over how the Expressions are joined together, but also how they are grouped + + ```go + ds := goqu.From("items").Where( + goqu.Or( + goqu.C("a").Gt(10), + goqu.And( + goqu.C("b").Eq(100), + goqu.C("c").Neq("test"), + ), + ), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + ``` + + Output: + ```sql + SELECT * FROM "items" WHERE (("a" > 10) OR (("b" = 100) AND ("c" != 'test'))) [] + SELECT * FROM "items" WHERE (("a" > ?) OR (("b" = ?) AND ("c" != ?))) [10 100 test] + ``` + + You can also use Or with the map syntax + ```go + ds := goqu.From("test").Where( + goqu.Or( + // Ex will be anded together + goqu.Ex{ + "col1": 1, + "col2": true, + }, + goqu.Ex{ + "col3": nil, + "col4": "foo", + }, + ), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + ``` + + Output: + ```sql + SELECT * FROM "test" WHERE ((("col1" = 1) AND ("col2" IS TRUE)) OR (("col3" IS NULL) AND ("col4" = 'foo'))) [] + SELECT * FROM "test" WHERE ((("col1" = ?) AND ("col2" IS TRUE)) OR (("col3" IS NULL) AND ("col4" = ?))) [1 foo] + ``` ### Complex Example Using the Ex map syntax ```go -sql, _, _ := db.From("test"). - Select(goqu.COUNT("*")). - InnerJoin(goqu.I("test2"), goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.id")))). - LeftJoin(goqu.I("test3"), goqu.On(goqu.I("test2.fkey").Eq(goqu.I("test3.id")))). - Where( - goqu.Ex{ - "test.name": goqu.Op{"like": regexp.MustCompile("^(a|b)")}, - "test2.amount": goqu.Op{"isNot": nil}, - }, - goqu.ExOr{ - "test3.id": nil, - "test3.status": []string{"passed", "active", "registered"}, - }). - Order(goqu.I("test.created").Desc().NullsLast()). - GroupBy(goqu.I("test.user_id")). - Having(goqu.AVG("test3.age").Gt(10)). - ToSql() +ds := db.From("test"). + Select(goqu.COUNT("*")). + InnerJoin(goqu.I("test2"), goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.id")})). + LeftJoin(goqu.I("test3"), goqu.On(goqu.Ex{"test2.fkey": goqu.I("test3.id")})). + Where( + goqu.Ex{ + "test.name": goqu.Op{"like": regexp.MustCompile("^(a|b)")}, + "test2.amount": goqu.Op{"isNot": nil}, + }, + goqu.ExOr{ + "test3.id": nil, + "test3.status": []string{"passed", "active", "registered"}, + }, + ). + Order(goqu.I("test.created").Desc().NullsLast()). + GroupBy(goqu.I("test.user_id")). + Having(goqu.AVG("test3.age").Gt(10)) + +sql, args, _ := ds.ToSQL() +fmt.Println(sql) + +sql, args, _ := ds.Prepared(true).ToSQL() fmt.Println(sql) ``` Using the Expression syntax ```go -sql, _, _ := db.From("test"). - Select(goqu.COUNT("*")). - InnerJoin(goqu.I("test2"), goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.id")))). - LeftJoin(goqu.I("test3"), goqu.On(goqu.I("test2.fkey").Eq(goqu.I("test3.id")))). - Where( - goqu.I("test.name").Like(regexp.MustCompile("^(a|b)")), - goqu.I("test2.amount").IsNotNull(), - goqu.Or( - goqu.I("test3.id").IsNull(), - goqu.I("test3.status").In("passed", "active", "registered"), - )). - Order(goqu.I("test.created").Desc().NullsLast()). - GroupBy(goqu.I("test.user_id")). - Having(goqu.AVG("test3.age").Gt(10)). - ToSql() +ds := db.From("test"). + Select(goqu.COUNT("*")). + InnerJoin(goqu.I("test2"), goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.id")))). + LeftJoin(goqu.I("test3"), goqu.On(goqu.I("test2.fkey").Eq(goqu.I("test3.id")))). + Where( + goqu.I("test.name").Like(regexp.MustCompile("^(a|b)")), + goqu.I("test2.amount").IsNotNull(), + goqu.Or( + goqu.I("test3.id").IsNull(), + goqu.I("test3.status").In("passed", "active", "registered"), + ), + ). + Order(goqu.I("test.created").Desc().NullsLast()). + GroupBy(goqu.I("test.user_id")). + Having(goqu.AVG("test3.age").Gt(10)) + +sql, args, _ := ds.ToSQL() +fmt.Println(sql) + +sql, args, _ := ds.Prepared(true).ToSQL() fmt.Println(sql) ``` Both examples generate the following SQL ```sql +-- interpolated SELECT COUNT(*) FROM "test" - INNER JOIN "test2" ON ("test"."fkey" = "test2"."id") - LEFT JOIN "test3" ON ("test2"."fkey" = "test3"."id") -WHERE ( - ("test"."name" ~ '^(a|b)') AND - ("test2"."amount" IS NOT NULL) AND - ( - ("test3"."id" IS NULL) OR - ("test3"."status" IN ('passed', 'active', 'registered')) - ) -) + INNER JOIN "test2" ON ("test"."fkey" = "test2"."id") + LEFT JOIN "test3" ON ("test2"."fkey" = "test3"."id") +WHERE ((("test"."name" ~ '^(a|b)') AND ("test2"."amount" IS NOT NULL)) AND + (("test3"."id" IS NULL) OR ("test3"."status" IN ('passed', 'active', 'registered')))) GROUP BY "test"."user_id" HAVING (AVG("test3"."age") > 10) -ORDER BY "test"."created" DESC NULLS LAST +ORDER BY "test"."created" DESC NULLS LAST [] + +-- prepared +SELECT COUNT(*) +FROM "test" + INNER JOIN "test2" ON ("test"."fkey" = "test2"."id") + LEFT JOIN "test3" ON ("test2"."fkey" = "test3"."id") +WHERE ((("test"."name" ~ ?) AND ("test2"."amount" IS NOT NULL)) AND + (("test3"."id" IS NULL) OR ("test3"."status" IN (?, ?, ?)))) +GROUP BY "test"."user_id" +HAVING (AVG("test3"."age") > ?) +ORDER BY "test"."created" DESC NULLS LAST [^(a|b) passed active registered 10] ``` ## Querying -goqu also has basic query support through the use of either the Database or the Dataset. +`goqu` also has basic query support through the use of either the Database or the Dataset. - -### Dataset + +### Executing Queries -* [`ScanStructs`](http://godoc.org/github.com/doug-martin/goqu#Dataset.ScanStructs) - scans rows into a slice of structs +You can also create a `goqu.Database` instance to query records. -**NOTE** [`ScanStructs`](http://godoc.org/github.com/doug-martin/goqu#Dataset.ScanStructs) will only select the columns that can be scanned in to the structs unless you have explicitly selected certain columns. +In the example below notice that we imported the dialect and driver for side effect only. ```go -type User struct{ - FirstName string `db:"first_name"` - LastName string `db:"last_name"` -} +import ( + "database/sql" + "github.com/doug-martin/goqu/v7" + _ "github.com/doug-martin/goqu/v7/dialect/postgres" + _ "github.com/lib/pq" +) -var users []User -//SELECT "first_name", "last_name" FROM "user"; -if err := db.From("user").ScanStructs(&users); err != nil{ - fmt.Println(err.Error()) - return +dialect := goqu.Dialect("postgres") + +pgDb, err := sql.Open("postgres", "user=postgres dbname=goqupostgres sslmode=disable ") +if err != nil { + panic(err.Error()) } -fmt.Printf("\n%+v", users) +db := dialect.DB(pgDb) -var users []User -//SELECT "first_name" FROM "user"; -if err := db.From("user").Select("first_name").ScanStructs(&users); err != nil{ - fmt.Println(err.Error()) - return +// "SELECT COUNT(*) FROM "user"; +if count, err := db.From("user").Count(); err != nil { + fmt.Println(err.Error()) +}else{ + fmt.Printf("User count = %d", count) } -fmt.Printf("\n%+v", users) ``` -* [`ScanStruct`](http://godoc.org/github.com/doug-martin/goqu#Dataset.ScanStruct) - scans a row into a slice a struct, returns false if a row wasnt found + +* [`ScanStructs`](http://godoc.org/github.com/doug-martin/goqu#Dataset.ScanStructs) - scans rows into a slice of structs -**NOTE** [`ScanStruct`](http://godoc.org/github.com/doug-martin/goqu#Dataset.ScanStruct) will only select the columns that can be scanned in to the struct unless you have explicitly selected certain columns. + **NOTE** [`ScanStructs`](http://godoc.org/github.com/doug-martin/goqu#Dataset.ScanStructs) will only select the columns that can be scanned in to the structs unless you have explicitly selected certain columns. -```go + ```go + type User struct{ + FirstName string `db:"first_name"` + LastName string `db:"last_name"` + } + + var users []User + //SELECT "first_name", "last_name" FROM "user"; + if err := db.From("user").ScanStructs(&users); err != nil{ + panic(err.Error()) + } + fmt.Printf("\n%+v", users) + + var users []User + //SELECT "first_name" FROM "user"; + if err := db.From("user").Select("first_name").ScanStructs(&users); err != nil{ + panic(err.Error()) + } + fmt.Printf("\n%+v", users) + ``` + + +* [`ScanStruct`](http://godoc.org/github.com/doug-martin/goqu#Dataset.ScanStruct) - scans a row into a slice a struct, returns false if a row wasnt found + + **NOTE** [`ScanStruct`](http://godoc.org/github.com/doug-martin/goqu#Dataset.ScanStruct) will only select the columns that can be scanned in to the struct unless you have explicitly selected certain columns. -type User struct{ + ```go + type User struct{ FirstName string `db:"first_name"` LastName string `db:"last_name"` -} + } -var user User -//SELECT "first_name", "last_name" FROM "user" LIMIT 1; -found, err := db.From("user").ScanStruct(&user) -if err != nil{ + var user User + // SELECT "first_name", "last_name" FROM "user" LIMIT 1; + found, err := db.From("user").ScanStruct(&user) + if err != nil{ fmt.Println(err.Error()) return -} -if !found { + } + if !found { fmt.Println("No user found") -} else { + } else { fmt.Printf("\nFound user: %+v", user) -} -``` - - -**NOTE** Using the `goqu.SetColumnRenameFunction` function, you can change the function that's used to rename struct fields when struct tags aren't defined - -```go -import "strings" + } + ``` -goqu.SetColumnRenameFunction(strings.ToUpper) -type User struct{ - FirstName string - LastName string -} + **NOTE** Using the `goqu.SetColumnRenameFunction` function, you can change the function that's used to rename struct fields when struct tags aren't defined -var user User -//SELECT "FIRSTNAME", "LASTNAME" FROM "user" LIMIT 1; -found, err := db.From("user").ScanStruct(&user) -// ... -``` + ```go + import "strings" + goqu.SetColumnRenameFunction(strings.ToUpper) + type User struct{ + FirstName string + LastName string + } + var user User + //SELECT "FIRSTNAME", "LASTNAME" FROM "user" LIMIT 1; + found, err := db.From("user").ScanStruct(&user) + // ... + ``` + * [`ScanVals`](http://godoc.org/github.com/doug-martin/goqu#Dataset.ScanVals) - scans a rows of 1 column into a slice of primitive values -```go -var ids []int64 -if err := db.From("user").Select("id").ScanVals(&ids); err != nil{ + ```go + var ids []int64 + if err := db.From("user").Select("id").ScanVals(&ids); err != nil{ fmt.Println(err.Error()) return -} -fmt.Printf("\n%+v", ids) -``` + } + fmt.Printf("\n%+v", ids) + ``` -* [`ScanVal`](http://godoc.org/github.com/doug-martin/goqu#Dataset.ScanVal) - scans a row of 1 column into a primitive value, returns false if a row wasnt found. **Note** when using the dataset a `LIMIT` of 1 is automatically applied. -```go -var id int64 -found, err := db.From("user").Select("id").ScanVal(&id) -if err != nil{ + +* [`ScanVal`](http://godoc.org/github.com/doug-martin/goqu#Dataset.ScanVal) - scans a row of 1 column into a primitive value, returns false if a row wasnt found. + + **Note** when using the dataset a `LIMIT` of 1 is automatically applied. + ```go + var id int64 + found, err := db.From("user").Select("id").ScanVal(&id) + if err != nil{ fmt.Println(err.Error()) return -} -if !found{ + } + if !found{ fmt.Println("No id found") -}else{ + }else{ fmt.Printf("\nFound id: %d", id) -} -``` + } + ``` + * [`Count`](http://godoc.org/github.com/doug-martin/goqu#Dataset.Count) - Returns the count for the current query -```go -count, err := db.From("user").Count() -if err != nil{ + ```go + count, err := db.From("user").Count() + if err != nil{ fmt.Println(err.Error()) return -} -fmt.Printf("\nCount:= %d", count) -``` + } + fmt.Printf("\nCount:= %d", count) + ``` + * [`Pluck`](http://godoc.org/github.com/doug-martin/goqu#Dataset.Pluck) - Selects a single column and stores the results into a slice of primitive values -```go -var ids []int64 -if err := db.From("user").Pluck(&ids, "id"); err != nil{ + ```go + var ids []int64 + if err := db.From("user").Pluck(&ids, "id"); err != nil{ fmt.Println(err.Error()) return -} -fmt.Printf("\nIds := %+v", ids) -``` - -* [`Insert`](http://godoc.org/github.com/doug-martin/goqu#Dataset.Insert) - Creates an `INSERT` statement and returns a [`CrudExec`](http://godoc.org/github.com/doug-martin/goqu#CrudExec) to execute the statement -```go -insert := db.From("user").Insert(goqu.Record{"first_name": "Bob", "last_name":"Yukon", "created": time.Now()}) -if _, err := insert.Exec(); err != nil{ + } + fmt.Printf("\nIds := %+v", ids) + ``` + + +* [`Insert`](http://godoc.org/github.com/doug-martin/goqu#Dataset.Insert) - Creates an `INSERT` statement and returns a [`QueryExecutor`](http://godoc.org/github.com/doug-martin/goqu/exec/#QueryExecutor) to execute the statement + ```go + insert := db.From("user").Insert(goqu.Record{ + "first_name": "Bob", + "last_name": "Yukon", + "created": time.Now(), + }) + if _, err := insert.Exec(); err != nil{ fmt.Println(err.Error()) return -} -``` -Insert will also handle multi inserts if supported by the database -```go -users := []goqu.Record{ - {"first_name": "Bob", "last_name":"Yukon", "created": time.Now()}, - {"first_name": "Sally", "last_name":"Yukon", "created": time.Now()}, - {"first_name": "Jimmy", "last_name":"Yukon", "created": time.Now()}, -} -if _, err := db.From("user").Insert(users).Exec(); err != nil{ + } + ``` + + Insert will also handle multi inserts if supported by the database + + ```go + users := []goqu.Record{ + {"first_name": "Bob", "last_name": "Yukon", "created": time.Now()}, + {"first_name": "Sally", "last_name": "Yukon", "created": time.Now()}, + {"first_name": "Jimmy", "last_name": "Yukon", "created": time.Now()}, + } + if _, err := db.From("user").Insert(users).Exec(); err != nil{ fmt.Println(err.Error()) return -} -``` -If your database supports the `RETURN` clause you can also use the different Scan methods to get results -```go -var ids []int64 -users := []goqu.Record{ - {"first_name": "Bob", "last_name":"Yukon", "created": time.Now()}, - {"first_name": "Sally", "last_name":"Yukon", "created": time.Now()}, - {"first_name": "Jimmy", "last_name":"Yukon", "created": time.Now()}, -} -if err := db.From("user").Returning(goqu.I("id")).Insert(users).ScanVals(&ids); err != nil{ + } + ``` + + If your database supports the `RETURN` clause you can also use the different Scan methods to get results + ```go + var ids []int64 + users := []goqu.Record{ + {"first_name": "Bob", "last_name": "Yukon", "created": time.Now()}, + {"first_name": "Sally", "last_name": "Yukon", "created": time.Now()}, + {"first_name": "Jimmy", "last_name": "Yukon", "created": time.Now()}, + } + if err := db.From("user").Returning(goqu.C("id")).Insert(users).ScanVals(&ids); err != nil{ fmt.Println(err.Error()) return -} -``` + } + ``` -* [`Update`](http://godoc.org/github.com/doug-martin/goqu#Dataset.Update) - Creates an `UPDATE` statement and returns an[`CrudExec`](http://godoc.org/github.com/doug-martin/goqu#CrudExec) to execute the statement -```go -update := db.From("user"). - Where(goqu.I("status").Eq("inactive")). + +* [`Update`](http://godoc.org/github.com/doug-martin/goqu#Dataset.Update) - Creates an `UPDATE` statement and returns [`QueryExecutor`](http://godoc.org/github.com/doug-martin/goqu/exec/#QueryExecutor) to execute the statement + + ```go + update := db.From("user"). + Where(goqu.C("status").Eq("inactive")). Update(goqu.Record{"password": nil, "updated": time.Now()}) -if _, err := update.Exec(); err != nil{ + if _, err := update.Exec(); err != nil{ fmt.Println(err.Error()) return -} -`````` -If your database supports the `RETURN` clause you can also use the different Scan methods to get results -```go -var ids []int64 -update := db.From("user"). + } + ``` + + If your database supports the `RETURN` clause you can also use the different Scan methods to get results + ```go + var ids []int64 + update := db.From("user"). Where(goqu.Ex{"status":"inactive"}). Returning("id"). Update(goqu.Record{"password": nil, "updated": time.Now()}) -if err := update.ScanVals(&ids); err != nil{ + if err := update.ScanVals(&ids); err != nil{ fmt.Println(err.Error()) return -} -``` -* [`Delete`](http://godoc.org/github.com/doug-martin/goqu#Dataset.Delete) - Creates an `DELETE` statement and returns a [`CrudExec`](http://godoc.org/github.com/doug-martin/goqu#CrudExec) to execute the statement -```go -delete := db.From("invoice"). + } + ``` + + +* [`Delete`](http://godoc.org/github.com/doug-martin/goqu#Dataset.Delete) - Creates an `DELETE` statement and returns a [`QueryExecutor`](http://godoc.org/github.com/doug-martin/goqu/exec/#QueryExecutor) to execute the statement + ```go + delete := db.From("invoice"). Where(goqu.Ex{"status":"paid"}). Delete() -if _, err := delete.Exec(); err != nil{ + if _, err := delete.Exec(); err != nil{ fmt.Println(err.Error()) return -} -``` -If your database supports the `RETURN` clause you can also use the different Scan methods to get results -```go -var ids []int64 -delete := db.From("invoice"). - Where(goqu.I("status").Eq("paid")). - Returning(goqu.I("id")). + } + ``` + + If your database supports the `RETURN` clause you can also use the different Scan methods to get results + + ```go + var ids []int64 + delete := db.From("invoice"). + Where(goqu.C("status").Eq("paid")). + Returning(goqu.C("id")). Delete() -if err := delete.ScanVals(&ids); err != nil{ + if err := delete.ScanVals(&ids); err != nil{ fmt.Println(err.Error()) return -} -``` + } + ``` #### Prepared Statements By default the `Dataset` will interpolate all parameters, if you do not want to have values interpolated you can use the [`Prepared`](http://godoc.org/github.com/doug-martin/goqu#Dataset.Prepared) method to prevent this. -**Note** For the examples all placeholders are `?` this will be adapter specific when using other examples (e.g. Postgres `$1, $2...`) +**Note** For the examples all placeholders are `?` this will be dialect specific when using other examples (e.g. Postgres `$1, $2...`) ```go @@ -570,23 +750,23 @@ sql, args, _ := preparedDs.Where(goqu.Ex{ "col3": true, "col4": false, "col5": []string{"a", "b", "c"}, -}).ToSql() +}).ToSQL() fmt.Println(sql, args) -sql, args, _ = preparedDs.ToInsertSql( +sql, args, _ = preparedDs.ToInsertSQL( goqu.Record{"name": "Test1", "address": "111 Test Addr"}, goqu.Record{"name": "Test2", "address": "112 Test Addr"}, ) fmt.Println(sql, args) -sql, args, _ = preparedDs.ToUpdateSql( +sql, args, _ = preparedDs.ToUpdateSQL( goqu.Record{"name": "Test", "address": "111 Test Addr"}, ) fmt.Println(sql, args) sql, args, _ = preparedDs. Where(goqu.Ex{"id": goqu.Op{"gt": 10}}). - ToDeleteSql() + ToDeleteSQL() fmt.Println(sql, args) // Output: @@ -670,10 +850,7 @@ err = tx.Wrap(func() error{ update := tx.From("user"). Where(goqu.Ex("password": nil}). Update(goqu.Record{"status": "inactive"}) - if _, err = update.Exec(); err != nil{ - return err - } - return nil + return update.Exec() }) //err will be the original error from the update statement, unless there was an error executing ROLLBACK if err != nil{ @@ -691,68 +868,66 @@ To enable trace logging of SQL statements use the [`Database.Logger`](http://god **NOTE** If you start a transaction using a database your set a logger on the transaction will inherit that logger automatically - -## Adapters - -Adapters in goqu are the foundation of building the correct SQL for each DB dialect. + +## Dialects -Between most dialects there is a large portion of shared syntax, for this reason we have a [`DefaultAdapter`](http://godoc.org/github.com/doug-martin/goqu/#DefaultAdapter) that can be used as a base for any new Dialect specific adapter. -In fact for most use cases you will not have to override any methods but instead just override the default values as documented for [`DefaultAdapter`](http://godoc.org/github.com/doug-martin/goqu/#DefaultAdapter). +Dialects in goqu are the foundation of building the correct SQL for each DB dialect. -### Literal +### Dialect Options -The [`DefaultAdapter`](http://godoc.org/github.com/doug-martin/goqu/#DefaultAdapter) has a [`Literal`](http://godoc.org/github.com/doug-martin/goqu/#DefaultAdapter.Literal) function which should be used to serialize all sub expressions or values. This method prevents you from having to re-implement each adapter method while having your adapter methods called correctly. +Most SQL dialects share a majority of their syntax, for this reason `goqu` has a [default set of dialect options]((http://godoc.org/github.com/doug-martin/goqu/#DefaultDialectOptions)) that can be used as a base for any new Dialect. -**How does it work?** +When creating a new `SQLDialect` you just need to override the default values that are documented in [`SQLDialectOptions`](http://godoc.org/github.com/doug-martin/goqu/#SQLDialectOptions). -The Literal method delegates back to the [`Dataset.Literal`](http://godoc.org/github.com/doug-martin/goqu/#Dataset.Literal) method which then calls the appropriate method on the adapter acting as a trampoline, between the DefaultAdapter and your Adapter. +Take a look at [`postgres`](./dialect/postgres/postgres.go), [`mysql`](./dialect/mysql/mysql.go) and [`sqlite3`](./dialect/sqlite3/sqlite3.go) for examples. -For example if your adapter overrode the [`DefaultAdapter.QuoteIdentifier`](http://godoc.org/github.com/doug-martin/goqu/#DefaultAdapter.QuoteIdentifier), method which is used by most methods in the [`DefaultAdapter`](http://godoc.org/github.com/doug-martin/goqu/#DefaultAdapter), we need to ensure that your Adapters QuoteIdentifier method is called instead of the default implementation. +### Creating a custom dialect -Because the Dataset has a pointer to your Adapter it will call the correct method, so instead of calling `DefaultAdapter.QuoteIdentifier` internally we delegate back to the Dataset by calling the [`Dataset.Literal`](http://godoc.org/github.com/doug-martin/goqu/#Dataset.Literal) which will the call your Adapters method. +When creating a new dialect you must register it using [`RegisterDialect`](http://godoc.org/github.com/doug-martin/goqu/#RegisterDialect). This method requires 2 arguments. -``` -Dataset.Literal -> Adapter.ExpressionListSql -> Adapter.Literal -> Dataset.Literal -> YourAdapter.QuoteIdentifier -``` +1. `dialect string` - The name of your dialect +2. `opts SQLDialectOptions` - The custom options for your dialect -It is important to maintain this pattern when writing your own Adapter. - -### Registering - -When creating your adapters you must register your adapter with [`RegisterAdapter`](http://godoc.org/github.com/doug-martin/goqu/#RegisterAdapter). This method requires 2 arguments. +For example you could create a custom dialect that replaced the default quote `'"'` with a backtick ` +```go +opts := goqu.DefaultDialectOptions() +opts.QuoteRune = '`' +goqu.RegisterDialect("custom-dialect", opts) -1. dialect - The dialect for your adapter. -2. datasetAdapterFactory - This is a factory function that will return a new goqu.Adapter used to create the dialect specific SQL. +dialect := goqu.Dialect("custom-dialect") +ds := dialect.From("test") -For example the code for the postgres adapter is fairly short. -```go -package postgres +sql, args, _ := ds.ToSQL() +fmt.Println(sql, args) +``` -import ( - "github.com/doug-martin/goqu/v6" -) +Output: +``` +SELECT * FROM `test` [] +``` -//postgres requires a $ placeholder for prepared statements -const placeholder_rune = '$' +For more examples look at [`postgres`](./dialect/postgres/postgres.go), [`mysql`](./dialect/mysql/mysql.go) and [`sqlite3`](./dialect/sqlite3/sqlite3.go) for examples. -func newDatasetAdapter(ds *goqu.Dataset) goqu.Adapter { - ret := goqu.NewDefaultAdapter(ds).(*goqu.DefaultAdapter) + +## Migrating Between Versions - //override the settings required - ret.PlaceHolderRune = placeholder_rune - //postgres requires a paceholder number (e.g. $1) - ret.IncludePlaceholderNum = true - return ret -} +### ` `ToSQL` + * `ToInsertSql` -> `ToInsertSQL` + * `ToUpdateSql` -> `ToUpdateSQL` + * `ToDeleteSql` -> `ToDeleteSQL` + * `ToTruncateSql` -> `ToTruncateSQL` +* Abstracted out `dialect_options` from the adapter to make the dialect self contained. + * This also removed the `dataset<->adapter` co dependency making the dialect self contained. + * Added new dialect options to specify the order than SQL statements are built. +* Refactored the `goqu.I` method. + * Added new `goqu.S`, `goqu.T` and `goqu.C` methods to clarify why type of identifier you are using. + * `goqu.I` should only be used when you have a qualified identifier (e.g. `goqu.I("my_schema.my_table.my_col") +* Added new `goqu.Dialect` method to make using `goqu` as an SQL builder easier. -If you are looking to write your own adapter take a look at the postgresm, mysql or sqlite3 adapter located at . ## Contributions @@ -804,3 +979,4 @@ GO_VERSION=latest docker-compose run goqu `goqu` is released under the [MIT License](http://www.opensource.org/licenses/MIT). + diff --git a/adapters.go b/adapters.go deleted file mode 100644 index e5d17525..00000000 --- a/adapters.go +++ /dev/null @@ -1,272 +0,0 @@ -package goqu - -import ( - "reflect" - "strings" - "time" -) - -type ( - //An adapter interface to be used by a Dataset to generate SQL for a specific dialect. - //See DefaultAdapter for a concrete implementation and examples. - Adapter interface { - //Returns true if the dialect supports ORDER BY expressions in DELETE statements - SupportsOrderByOnDelete() bool - //Returns true if the dialect supports ORDER BY expressions in UPDATE statements - SupportsOrderByOnUpdate() bool - //Returns true if the dialect supports LIMIT expressions in DELETE statements - SupportsLimitOnDelete() bool - //Returns true if the dialect supports LIMIT expressions in UPDATE statements - SupportsLimitOnUpdate() bool - //Returns true if the dialect supports RETURN expressions - SupportsReturn() bool - //Generates the sql for placeholders. Only invoked when not interpolating values. - // - //buf: The current SqlBuilder to write the sql to - //i: the value that should be added the the sqlbuilders args. - PlaceHolderSql(buf *SqlBuilder, i interface{}) error - //Generates the correct beginning sql for an UPDATE statement - // - //buf: The current SqlBuilder to write the sql to - UpdateBeginSql(buf *SqlBuilder) error - //Generates the correct beginning sql for an INSERT statement - // - //buf: The current SqlBuilder to write the sql to - InsertBeginSql(buf *SqlBuilder, o ConflictExpression) error - //Generates the correct beginning sql for a DELETE statement - // - //buf: The current SqlBuilder to write the sql to - DeleteBeginSql(buf *SqlBuilder) error - //Generates the correct beginning sql for a TRUNCATE statement - // - //buf: The current SqlBuilder to write the sql to - TruncateSql(buf *SqlBuilder, cols ColumnList, opts TruncateOptions) error - //Generates the correct sql for inserting default values in SQL - // - //buf: The current SqlBuilder to write the sql to - DefaultValuesSql(buf *SqlBuilder) error - //Generates the sql for update expressions - // - //buf: The current SqlBuilder to write the sql to - UpdateExpressionsSql(buf *SqlBuilder, updates ...UpdateExpression) error - //Generates the sql for the SELECT and ColumnList for a select statement - // - //buf: The current SqlBuilder to write the sql to - SelectSql(buf *SqlBuilder, cols ColumnList) error - //Generates the sql for the SELECT DISTINCT and ColumnList for a select statement - // - //buf: The current SqlBuilder to write the sql to - SelectDistinctSql(buf *SqlBuilder, cols ColumnList) error - //Generates the sql for a RETURNING clause - // - //buf: The current SqlBuilder to write the sql to - ReturningSql(buf *SqlBuilder, cols ColumnList) error - //Generates the sql for a FROM clause - // - //buf: The current SqlBuilder to write the sql to - FromSql(buf *SqlBuilder, from ColumnList) error - //Generates the sql for a list of columns. - // - //buf: The current SqlBuilder to write the sql to - SourcesSql(buf *SqlBuilder, from ColumnList) error - //Generates the sql for JoiningClauses clauses - // - //buf: The current SqlBuilder to write the sql to - JoinSql(buf *SqlBuilder, joins JoiningClauses) error - //Generates the sql for WHERE clause - // - //buf: The current SqlBuilder to write the sql to - WhereSql(buf *SqlBuilder, where ExpressionList) error - //Generates the sql for GROUP BY clause - // - //buf: The current SqlBuilder to write the sql to - GroupBySql(buf *SqlBuilder, groupBy ColumnList) error - //Generates the sql for HAVING clause - // - //buf: The current SqlBuilder to write the sql to - HavingSql(buf *SqlBuilder, having ExpressionList) error - //Generates the sql for COMPOUND expressions, such as UNION, and INTERSECT - // - //buf: The current SqlBuilder to write the sql to - CompoundsSql(buf *SqlBuilder, compounds []CompoundExpression) error - //Generates the sql for the WITH clauses for common table expressions (CTE) - // - //buf: The current SqlBuilder to write the sql to - CommonTablesSql(buf *SqlBuilder, ctes []CommonTableExpression) error - //Generates the sql for ORDER BY clause - // - //buf: The current SqlBuilder to write the sql to - OrderSql(buf *SqlBuilder, order ColumnList) error - //Generates the sql for LIMIT clause - // - //buf: The current SqlBuilder to write the sql to - LimitSql(buf *SqlBuilder, limit interface{}) error - //Generates the sql for OFFSET clause - // - //buf: The current SqlBuilder to write the sql to - OffsetSql(buf *SqlBuilder, offset uint) error - //Generates the sql for FOR clause - // - //buf: The current SqlBuilder to write the sql to - ForSql(buf *SqlBuilder, lockingClause Lock) error - //Generates the sql for another Dataset being used as a sub select. - // - //buf: The current SqlBuilder to write the sql to - DatasetSql(buf *SqlBuilder, builder Dataset) error - //Correctly quotes an Identifier for use in SQL. - // - //buf: The current SqlBuilder to write the sql to - QuoteIdentifier(buf *SqlBuilder, ident IdentifierExpression) error - //Generates SQL value for nil - // - //buf: The current SqlBuilder to write the sql to - LiteralNil(buf *SqlBuilder) error - //Generates SQL value for a bool (e.g. TRUE, FALSE, 1, 0) - // - //buf: The current SqlBuilder to write the sql to - LiteralBool(buf *SqlBuilder, b bool) error - //Generates SQL value for a time.Time - // - //buf: The current SqlBuilder to write the sql to - LiteralTime(buf *SqlBuilder, t time.Time) error - //Generates SQL value for float64 - // - //buf: The current SqlBuilder to write the sql to - LiteralFloat(buf *SqlBuilder, f float64) error - //Generates SQL value for an int64 - // - //buf: The current SqlBuilder to write the sql to - LiteralInt(buf *SqlBuilder, i int64) error - //Generates SQL value for a string - // - //buf: The current SqlBuilder to write the sql to - LiteralString(buf *SqlBuilder, s string) error - //Generates SQL value for a Slice of Bytes - // - //buf: The current SqlBuilder to write the sql to - LiteralBytes(buf *SqlBuilder, bs []byte) error - //Generates SQL value for a Slice - // - //buf: The current SqlBuilder to write the sql to - SliceValueSql(buf *SqlBuilder, slice reflect.Value) error - //Generates SQL value for an AliasedExpression - // - //buf: The current SqlBuilder to write the sql to - AliasedExpressionSql(buf *SqlBuilder, aliased AliasedExpression) error - //Generates SQL value for a BooleanExpression - // - //buf: The current SqlBuilder to write the sql to - BooleanExpressionSql(buf *SqlBuilder, operator BooleanExpression) error - //Generates SQL value for a RangeExpression - // - //buf: The current SqlBuilder to write the sql to - RangeExpressionSql(buf *SqlBuilder, operator RangeExpression) error - //Generates SQL value for an OrderedExpression - // - //buf: The current SqlBuilder to write the sql to - OrderedExpressionSql(buf *SqlBuilder, order OrderedExpression) error - //Generates SQL value for an ExpressionList - // - //buf: The current SqlBuilder to write the sql to - ExpressionListSql(buf *SqlBuilder, expressionList ExpressionList) error - //Generates SQL value for a SqlFunction - // - //buf: The current SqlBuilder to write the sql to - SqlFunctionExpressionSql(buf *SqlBuilder, sqlFunc SqlFunctionExpression) error - //Generates SQL value for a CastExpression - // - //buf: The current SqlBuilder to write the sql to - CastExpressionSql(buf *SqlBuilder, casted CastExpression) error - //Generates SQL value for a CompoundExpression - // - //buf: The current SqlBuilder to write the sql to - CompoundExpressionSql(buf *SqlBuilder, compound CompoundExpression) error - //Generates SQL value for a CommonTableExpression - // - //buf: The current SqlBuilder to write the sql to - CommonTableExpressionSql(buf *SqlBuilder, commonTable CommonTableExpression) error - //Generates SQL value for a ColumnList - // - //buf: The current SqlBuilder to write the sql to - ColumnListSql(buf *SqlBuilder, columnList ColumnList) error - //Generates SQL value for an UpdateExpression - // - //buf: The current SqlBuilder to write the sql to - UpdateExpressionSql(buf *SqlBuilder, update UpdateExpression) error - Literal(buf *SqlBuilder, i interface{}) error - //Generates SQL value for a LiteralExpression - // - //buf: The current SqlBuilder to write the sql to - LiteralExpressionSql(buf *SqlBuilder, literal LiteralExpression) error - //Generates SQL value for an Ex Expression map - // - //buf: The current SqlBuilder to write the sql to - ExpressionMapSql(buf *SqlBuilder, ex Ex) error - //Generates SQL value for an ExOr Expression map - // - //buf: The current SqlBuilder to write the sql to - ExpressionOrMapSql(buf *SqlBuilder, ex ExOr) error - //Generates SQL value for the columns in an INSERT statement - // - //buf: The current SqlBuilder to write the sql to - InsertColumnsSql(buf *SqlBuilder, cols ColumnList) error - //Generates SQL value for the values in an INSERT statement - // - //buf: The current SqlBuilder to write the sql to - InsertValuesSql(buf *SqlBuilder, values [][]interface{}) error - //Returns true if the dialect supports INSERT IGNORE INTO syntax - SupportsInsertIgnoreSyntax() bool - //Returns true if the dialect supports ON CONFLICT (key) expressions - SupportsConflictTarget() bool - //Generates SQL value for the ON CONFLICT clause of an INSERT statement - // - //buf: The current SqlBuilder to write the sql to - OnConflictSql(buf *SqlBuilder, o ConflictExpression) error - //Returns true if the dialect supports a WHERE clause on upsert - SupportConflictUpdateWhere() bool - //Returns true if the dialect supports WITH common table expressions - SupportsWithCTE() bool - //Returns true if the dialect supports WITH RECURSIVE common table expressions - SupportsWithRecursiveCTE() bool - } -) - -var ds_adapters = make(map[string]func(dataset *Dataset) Adapter) - -//Registers an adapter. -// -// dialect: The dialect this adapter is for -// factory: a function that can be called to create a new Adapter for the dialect. -func RegisterAdapter(dialect string, factory func(ds *Dataset) Adapter) { - dialect = strings.ToLower(dialect) - ds_adapters[dialect] = factory -} - -//Returns true if the dialect has an adapter registered -// -// dialect: The dialect to test -func HasAdapter(dialect string) bool { - dialect = strings.ToLower(dialect) - _, ok := ds_adapters[dialect] - return ok -} - -//Clears the current adapter map, used internally for tests -func removeAdapter(dialect string) { - if HasAdapter(dialect) { - dialect = strings.ToLower(dialect) - delete(ds_adapters, dialect) - } -} - -//Creates the appropriate adapter for the given dialect. -// -// dialect: the dialect to create an adapter for -// dataset: The dataset to be used by the adapter -func NewAdapter(dialect string, dataset *Dataset) Adapter { - dialect = strings.ToLower(dialect) - if adapterGen, ok := ds_adapters[dialect]; ok { - return adapterGen(dataset) - } - return NewDefaultAdapter(dataset) -} diff --git a/adapters/mysql/dataset_adapter_test.go b/adapters/mysql/dataset_adapter_test.go deleted file mode 100644 index 29b289a5..00000000 --- a/adapters/mysql/dataset_adapter_test.go +++ /dev/null @@ -1,227 +0,0 @@ -package mysql - -import ( - "regexp" - "testing" - - "github.com/doug-martin/goqu/v6" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" -) - -type datasetAdapterTest struct { - suite.Suite -} - -func (me *datasetAdapterTest) TestPlaceholderSql() { - t := me.T() - buf := goqu.NewSqlBuilder(true) - dsAdapter := newDatasetAdapter(goqu.From("test")) - dsAdapter.PlaceHolderSql(buf, 1) - dsAdapter.PlaceHolderSql(buf, 2) - dsAdapter.PlaceHolderSql(buf, 3) - dsAdapter.PlaceHolderSql(buf, 4) - sql, args := buf.ToSql() - assert.Equal(t, args, []interface{}{1, 2, 3, 4}) - assert.Equal(t, sql, "????") -} - -func (me *datasetAdapterTest) GetDs(table string) *goqu.Dataset { - ret := goqu.From(table) - adapter := newDatasetAdapter(ret) - ret.SetAdapter(adapter) - return ret -} - -func (me *datasetAdapterTest) TestSupportsReturn() { - t := me.T() - dsAdapter := me.GetDs("test").Adapter() - assert.False(t, dsAdapter.SupportsReturn()) -} - -func (me *datasetAdapterTest) TestSupportsLimitOnDelete() { - t := me.T() - dsAdapter := me.GetDs("test").Adapter() - assert.True(t, dsAdapter.SupportsLimitOnDelete()) -} - -func (me *datasetAdapterTest) TestSupportsLimitOnUpdate() { - t := me.T() - dsAdapter := me.GetDs("test").Adapter() - assert.True(t, dsAdapter.SupportsLimitOnDelete()) -} - -func (me *datasetAdapterTest) TestSupportsOrderByOnDelete() { - t := me.T() - dsAdapter := me.GetDs("test").Adapter() - assert.True(t, dsAdapter.SupportsLimitOnDelete()) -} - -func (me *datasetAdapterTest) TestSupportsOrderByOnUpdate() { - t := me.T() - dsAdapter := me.GetDs("test").Adapter() - assert.True(t, dsAdapter.SupportsLimitOnDelete()) -} - -func (me *datasetAdapterTest) TestSupportsWithCTE() { - t := me.T() - dsAdapter := me.GetDs("test").Adapter() - assert.False(t, dsAdapter.SupportsWithCTE()) - assert.False(t, dsAdapter.SupportsWithRecursiveCTE()) -} - -func (me *datasetAdapterTest) TestIdentifiers() { - t := me.T() - ds := me.GetDs("test") - sql, _, err := ds.Select("a", goqu.I("a.b.c"), goqu.I("c.d"), goqu.I("test").As("test")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT `a`, `a`.`b`.`c`, `c`.`d`, `test` AS `test` FROM `test`") -} - -func (me *datasetAdapterTest) TestLiteralString() { - t := me.T() - ds := me.GetDs("test") - sql, _, err := ds.Where(goqu.I("a").Eq("test")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test')") - - sql, _, err = ds.Where(goqu.I("a").Eq("test'test")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\'test')") - - sql, _, err = ds.Where(goqu.I("a").Eq(`test"test`)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\"test')") - - sql, _, err = ds.Where(goqu.I("a").Eq(`test\test`)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\\test')") - - sql, _, err = ds.Where(goqu.I("a").Eq("test\ntest")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\ntest')") - - sql, _, err = ds.Where(goqu.I("a").Eq("test\rtest")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\rtest')") - - sql, _, err = ds.Where(goqu.I("a").Eq("test\x00test")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x00test')") - - sql, _, err = ds.Where(goqu.I("a").Eq("test\x1atest")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')") -} - -func (me *datasetAdapterTest) TestLiteralBytes() { - t := me.T() - ds := me.GetDs("test") - sql, _, err := ds.Where(goqu.I("a").Eq([]byte("test"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte("test'test"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\'test')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte(`test"test`))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\"test')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte(`test\test`))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\\test')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte("test\ntest"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\ntest')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte("test\rtest"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\rtest')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte("test\x00test"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x00test')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte("test\x1atest"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')") -} - -func (me *datasetAdapterTest) TestBooleanOperations() { - t := me.T() - ds := me.GetDs("test") - sql, _, err := ds.Where(goqu.I("a").Eq(true)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS TRUE)") - sql, _, err = ds.Where(goqu.I("a").Eq(false)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS FALSE)") - sql, _, err = ds.Where(goqu.I("a").Is(true)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS TRUE)") - sql, _, err = ds.Where(goqu.I("a").Is(false)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS FALSE)") - sql, _, err = ds.Where(goqu.I("a").IsTrue()).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS TRUE)") - sql, _, err = ds.Where(goqu.I("a").IsFalse()).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS FALSE)") - - sql, _, err = ds.Where(goqu.I("a").Neq(true)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT TRUE)") - sql, _, err = ds.Where(goqu.I("a").Neq(false)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT FALSE)") - sql, _, err = ds.Where(goqu.I("a").IsNot(true)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT TRUE)") - sql, _, err = ds.Where(goqu.I("a").IsNot(false)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT FALSE)") - sql, _, err = ds.Where(goqu.I("a").IsNotTrue()).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT TRUE)") - sql, _, err = ds.Where(goqu.I("a").IsNotFalse()).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT FALSE)") - - sql, _, err = ds.Where(goqu.I("a").Like("a%")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` LIKE BINARY 'a%')") - - sql, _, err = ds.Where(goqu.I("a").NotLike("a%")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT LIKE BINARY 'a%')") - - sql, _, err = ds.Where(goqu.I("a").ILike("a%")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` LIKE 'a%')") - sql, _, err = ds.Where(goqu.I("a").NotILike("a%")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')") - - sql, _, err = ds.Where(goqu.I("a").Like(regexp.MustCompile("(a|b)"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` REGEXP BINARY '(a|b)')") - sql, _, err = ds.Where(goqu.I("a").NotLike(regexp.MustCompile("(a|b)"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT REGEXP BINARY '(a|b)')") - sql, _, err = ds.Where(goqu.I("a").ILike(regexp.MustCompile("(a|b)"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` REGEXP '(a|b)')") - sql, _, err = ds.Where(goqu.I("a").NotILike(regexp.MustCompile("(a|b)"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT REGEXP '(a|b)')") - -} - -func TestDatasetAdapterSuite(t *testing.T) { - suite.Run(t, new(datasetAdapterTest)) -} diff --git a/adapters/mysql/mysql.go b/adapters/mysql/mysql.go deleted file mode 100644 index 424c7249..00000000 --- a/adapters/mysql/mysql.go +++ /dev/null @@ -1,96 +0,0 @@ -package mysql - -import "github.com/doug-martin/goqu/v6" - -var ( - placeholder_rune = '?' - quote_rune = '`' - default_values_frag = []byte("") - mysql_true = []byte("1") - mysql_false = []byte("0") - time_format = "2006-01-02 15:04:05" - operator_lookup = map[goqu.BooleanOperation][]byte{ - goqu.EQ_OP: []byte("="), - goqu.NEQ_OP: []byte("!="), - goqu.GT_OP: []byte(">"), - goqu.GTE_OP: []byte(">="), - goqu.LT_OP: []byte("<"), - goqu.LTE_OP: []byte("<="), - goqu.IN_OP: []byte("IN"), - goqu.NOT_IN_OP: []byte("NOT IN"), - goqu.IS_OP: []byte("IS"), - goqu.IS_NOT_OP: []byte("IS NOT"), - goqu.LIKE_OP: []byte("LIKE BINARY"), - goqu.NOT_LIKE_OP: []byte("NOT LIKE BINARY"), - goqu.I_LIKE_OP: []byte("LIKE"), - goqu.NOT_I_LIKE_OP: []byte("NOT LIKE"), - goqu.REGEXP_LIKE_OP: []byte("REGEXP BINARY"), - goqu.REGEXP_NOT_LIKE_OP: []byte("NOT REGEXP BINARY"), - goqu.REGEXP_I_LIKE_OP: []byte("REGEXP"), - goqu.REGEXP_NOT_I_LIKE_OP: []byte("NOT REGEXP"), - } - escape_runes = map[rune][]byte{ - '\'': []byte("\\'"), - '"': []byte("\\\""), - '\\': []byte("\\\\"), - '\n': []byte("\\n"), - '\r': []byte("\\r"), - 0: []byte("\\x00"), - 0x1a: []byte("\\x1a"), - } - insert_ignore_clause = []byte("INSERT IGNORE INTO") - conflict_fragment = []byte("") - conflict_update_fragment = []byte(" ON DUPLICATE KEY UPDATE ") - conflict_nothing_fragment = []byte("") -) - -type DatasetAdapter struct { - *goqu.DefaultAdapter -} - -func (me *DatasetAdapter) SupportsReturn() bool { - return false -} - -func (me *DatasetAdapter) SupportsLimitOnDelete() bool { - return true -} - -func (me *DatasetAdapter) SupportsLimitOnUpdate() bool { - return true -} - -func (me *DatasetAdapter) SupportsOrderByOnDelete() bool { - return true -} - -func (me *DatasetAdapter) SupportsOrderByOnUpdate() bool { - return true -} - -func newDatasetAdapter(ds *goqu.Dataset) goqu.Adapter { - def := goqu.NewDefaultAdapter(ds).(*goqu.DefaultAdapter) - def.PlaceHolderRune = placeholder_rune - def.IncludePlaceholderNum = false - def.QuoteRune = quote_rune - def.DefaultValuesFragment = default_values_frag - def.True = mysql_true - def.False = mysql_false - def.TimeFormat = time_format - def.BooleanOperatorLookup = operator_lookup - def.EscapedRunes = escape_runes - def.InsertIgnoreClause = insert_ignore_clause - def.ConflictFragment = conflict_fragment - def.ConflictDoUpdateFragment = conflict_update_fragment - def.ConflictDoNothingFragment = conflict_nothing_fragment - def.ConflictUpdateWhereSupported = false - def.InsertIgnoreSyntaxSupported = true - def.ConflictTargetSupported = false - def.WithCTESupported = false - def.WithCTERecursiveSupported = false - return &DatasetAdapter{def} -} - -func init() { - goqu.RegisterAdapter("mysql", newDatasetAdapter) -} diff --git a/adapters/postgres/dataset_adapter_test.go b/adapters/postgres/dataset_adapter_test.go deleted file mode 100644 index 3283e030..00000000 --- a/adapters/postgres/dataset_adapter_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package postgres - -import ( - "testing" - - "github.com/doug-martin/goqu/v6" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" -) - -type datasetAdapterTest struct { - suite.Suite -} - -func (me *datasetAdapterTest) TestPlaceholderSql() { - t := me.T() - buf := goqu.NewSqlBuilder(true) - dsAdapter := newDatasetAdapter(goqu.From("test")) - dsAdapter.PlaceHolderSql(buf, 1) - dsAdapter.PlaceHolderSql(buf, 2) - dsAdapter.PlaceHolderSql(buf, 3) - dsAdapter.PlaceHolderSql(buf, 4) - sql, args := buf.ToSql() - assert.Equal(t, args, []interface{}{1, 2, 3, 4}) - assert.Equal(t, sql, "$1$2$3$4") -} - -func TestDatasetAdapterSuite(t *testing.T) { - suite.Run(t, new(datasetAdapterTest)) -} diff --git a/adapters/postgres/postgres.go b/adapters/postgres/postgres.go deleted file mode 100644 index bca3d655..00000000 --- a/adapters/postgres/postgres.go +++ /dev/null @@ -1,18 +0,0 @@ -package postgres - -import ( - "github.com/doug-martin/goqu/v6" -) - -const placeholder_rune = '$' - -func newDatasetAdapter(ds *goqu.Dataset) goqu.Adapter { - ret := goqu.NewDefaultAdapter(ds).(*goqu.DefaultAdapter) - ret.PlaceHolderRune = placeholder_rune - ret.IncludePlaceholderNum = true - return ret -} - -func init() { - goqu.RegisterAdapter("postgres", newDatasetAdapter) -} diff --git a/adapters/sqlite3/dataset_adapter_test.go b/adapters/sqlite3/dataset_adapter_test.go deleted file mode 100644 index 25d571cb..00000000 --- a/adapters/sqlite3/dataset_adapter_test.go +++ /dev/null @@ -1,220 +0,0 @@ -package sqlite3 - -import ( - "regexp" - "testing" - - "github.com/doug-martin/goqu/v6" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" -) - -type datasetAdapterTest struct { - suite.Suite -} - -func (me *datasetAdapterTest) TestPlaceholderSql() { - t := me.T() - buf := goqu.NewSqlBuilder(true) - dsAdapter := newDatasetAdapter(goqu.From("test")) - dsAdapter.PlaceHolderSql(buf, 1) - dsAdapter.PlaceHolderSql(buf, 2) - dsAdapter.PlaceHolderSql(buf, 3) - dsAdapter.PlaceHolderSql(buf, 4) - sql, args := buf.ToSql() - assert.Equal(t, args, []interface{}{1, 2, 3, 4}) - assert.Equal(t, sql, "????") -} - -func (me *datasetAdapterTest) GetDs(table string) *goqu.Dataset { - ret := goqu.From(table) - adapter := newDatasetAdapter(ret) - ret.SetAdapter(adapter) - return ret -} - -func (me *datasetAdapterTest) TestSupportsReturn() { - t := me.T() - dsAdapter := me.GetDs("test").Adapter() - assert.False(t, dsAdapter.SupportsReturn()) -} - -func (me *datasetAdapterTest) TestSupportsLimitOnDelete() { - t := me.T() - dsAdapter := me.GetDs("test").Adapter() - assert.True(t, dsAdapter.SupportsLimitOnDelete()) -} - -func (me *datasetAdapterTest) TestSupportsLimitOnUpdate() { - t := me.T() - dsAdapter := me.GetDs("test").Adapter() - assert.True(t, dsAdapter.SupportsLimitOnDelete()) -} - -func (me *datasetAdapterTest) TestSupportsOrderByOnDelete() { - t := me.T() - dsAdapter := me.GetDs("test").Adapter() - assert.True(t, dsAdapter.SupportsLimitOnDelete()) -} - -func (me *datasetAdapterTest) TestSupportsOrderByOnUpdate() { - t := me.T() - dsAdapter := me.GetDs("test").Adapter() - assert.True(t, dsAdapter.SupportsLimitOnDelete()) -} - -func (me *datasetAdapterTest) TestIdentifiers() { - t := me.T() - ds := me.GetDs("test") - sql, _, err := ds.Select("a", goqu.I("a.b.c"), goqu.I("c.d"), goqu.I("test").As("test")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT `a`, `a`.`b`.`c`, `c`.`d`, `test` AS `test` FROM `test`") -} - -func (me *datasetAdapterTest) TestLiteralString() { - t := me.T() - ds := me.GetDs("test") - sql, _, err := ds.Where(goqu.I("a").Eq("test")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test')") - - sql, _, err = ds.Where(goqu.I("a").Eq("test'test")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\'test')") - - sql, _, err = ds.Where(goqu.I("a").Eq(`test"test`)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\"test')") - - sql, _, err = ds.Where(goqu.I("a").Eq(`test\test`)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\\test')") - - sql, _, err = ds.Where(goqu.I("a").Eq("test\ntest")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\ntest')") - - sql, _, err = ds.Where(goqu.I("a").Eq("test\rtest")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\rtest')") - - sql, _, err = ds.Where(goqu.I("a").Eq("test\x00test")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x00test')") - - sql, _, err = ds.Where(goqu.I("a").Eq("test\x1atest")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')") -} - -func (me *datasetAdapterTest) TestLiteralBytes() { - t := me.T() - ds := me.GetDs("test") - sql, _, err := ds.Where(goqu.I("a").Eq([]byte("test"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte("test'test"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\'test')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte(`test"test`))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\"test')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte(`test\test`))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\\test')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte("test\ntest"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\ntest')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte("test\rtest"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\rtest')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte("test\x00test"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x00test')") - - sql, _, err = ds.Where(goqu.I("a").Eq([]byte("test\x1atest"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')") -} - -func (me *datasetAdapterTest) TestBooleanOperations() { - t := me.T() - ds := me.GetDs("test") - sql, _, err := ds.Where(goqu.I("a").Eq(true)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS 1)") - sql, _, err = ds.Where(goqu.I("a").Eq(false)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS 0)") - sql, _, err = ds.Where(goqu.I("a").Is(true)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS 1)") - sql, _, err = ds.Where(goqu.I("a").Is(false)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS 0)") - sql, _, err = ds.Where(goqu.I("a").IsTrue()).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS 1)") - sql, _, err = ds.Where(goqu.I("a").IsFalse()).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS 0)") - - sql, _, err = ds.Where(goqu.I("a").Neq(true)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT 1)") - sql, _, err = ds.Where(goqu.I("a").Neq(false)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT 0)") - sql, _, err = ds.Where(goqu.I("a").IsNot(true)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT 1)") - sql, _, err = ds.Where(goqu.I("a").IsNot(false)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT 0)") - sql, _, err = ds.Where(goqu.I("a").IsNotTrue()).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT 1)") - sql, _, err = ds.Where(goqu.I("a").IsNotFalse()).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT 0)") - - sql, _, err = ds.Where(goqu.I("a").Like("a%")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` LIKE 'a%')") - - sql, _, err = ds.Where(goqu.I("a").NotLike("a%")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')") - - sql, _, err = ds.Where(goqu.I("a").ILike("a%")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` LIKE 'a%')") - sql, _, err = ds.Where(goqu.I("a").NotILike("a%")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')") - - sql, _, err = ds.Where(goqu.I("a").Like(regexp.MustCompile("(a|b)"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` REGEXP '(a|b)')") - sql, _, err = ds.Where(goqu.I("a").NotLike(regexp.MustCompile("(a|b)"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT REGEXP '(a|b)')") - sql, _, err = ds.Where(goqu.I("a").ILike(regexp.MustCompile("(a|b)"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` REGEXP '(a|b)')") - sql, _, err = ds.Where(goqu.I("a").NotILike(regexp.MustCompile("(a|b)"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT REGEXP '(a|b)')") - -} - -func TestDatasetAdapterSuite(t *testing.T) { - suite.Run(t, new(datasetAdapterTest)) -} diff --git a/adapters/sqlite3/sqlite3.go b/adapters/sqlite3/sqlite3.go deleted file mode 100644 index 89adff31..00000000 --- a/adapters/sqlite3/sqlite3.go +++ /dev/null @@ -1,93 +0,0 @@ -package sqlite3 - -import "github.com/doug-martin/goqu/v6" - -var ( - placeholder_rune = '?' - quote_rune = '`' - singlq_quote = '\'' - default_values_frag = []byte("") - sqlite3_true = []byte("1") - sqlite3_false = []byte("0") - time_format = "2006-01-02 15:04:05" - operator_lookup = map[goqu.BooleanOperation][]byte{ - goqu.EQ_OP: []byte("="), - goqu.NEQ_OP: []byte("!="), - goqu.GT_OP: []byte(">"), - goqu.GTE_OP: []byte(">="), - goqu.LT_OP: []byte("<"), - goqu.LTE_OP: []byte("<="), - goqu.IN_OP: []byte("IN"), - goqu.NOT_IN_OP: []byte("NOT IN"), - goqu.IS_OP: []byte("IS"), - goqu.IS_NOT_OP: []byte("IS NOT"), - goqu.LIKE_OP: []byte("LIKE"), - goqu.NOT_LIKE_OP: []byte("NOT LIKE"), - goqu.I_LIKE_OP: []byte("LIKE"), - goqu.NOT_I_LIKE_OP: []byte("NOT LIKE"), - goqu.REGEXP_LIKE_OP: []byte("REGEXP"), - goqu.REGEXP_NOT_LIKE_OP: []byte("NOT REGEXP"), - goqu.REGEXP_I_LIKE_OP: []byte("REGEXP"), - goqu.REGEXP_NOT_I_LIKE_OP: []byte("NOT REGEXP"), - } - escape_runes = map[rune][]byte{ - '\'': []byte("\\'"), - '"': []byte("\\\""), - '\\': []byte("\\\\"), - '\n': []byte("\\n"), - '\r': []byte("\\r"), - 0: []byte("\\x00"), - 0x1a: []byte("\\x1a"), - } - insert_ignore_clause = []byte("INSERT OR IGNORE") -) - -type DatasetAdapter struct { - *goqu.DefaultAdapter -} - -func (me *DatasetAdapter) SupportsReturn() bool { - return false -} - -func (me *DatasetAdapter) SupportsLimitOnDelete() bool { - return true -} - -func (me *DatasetAdapter) SupportsLimitOnUpdate() bool { - return true -} - -func (me *DatasetAdapter) SupportsOrderByOnDelete() bool { - return true -} - -func (me *DatasetAdapter) SupportsOrderByOnUpdate() bool { - return true -} - -func newDatasetAdapter(ds *goqu.Dataset) goqu.Adapter { - def := goqu.NewDefaultAdapter(ds).(*goqu.DefaultAdapter) - def.PlaceHolderRune = placeholder_rune - def.IncludePlaceholderNum = false - def.QuoteRune = quote_rune - def.DefaultValuesFragment = default_values_frag - def.True = sqlite3_true - def.False = sqlite3_false - def.TimeFormat = time_format - def.BooleanOperatorLookup = operator_lookup - def.UseLiteralIsBools = false - def.EscapedRunes = escape_runes - def.InsertIgnoreClause = insert_ignore_clause - def.ConflictFragment = []byte("") - def.ConflictDoUpdateFragment = []byte("") - def.ConflictDoNothingFragment = []byte("") - def.ConflictUpdateWhereSupported = false - def.InsertIgnoreSyntaxSupported = true - def.ConflictTargetSupported = false - return &DatasetAdapter{def} -} - -func init() { - goqu.RegisterAdapter("sqlite3", newDatasetAdapter) -} diff --git a/adapters_test.go b/adapters_test.go deleted file mode 100644 index 03f9fd0e..00000000 --- a/adapters_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package goqu - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" -) - -type adapterTest struct { - suite.Suite -} - -func (me *adapterTest) TestHasAdapter() { - t := me.T() - assert.False(t, HasAdapter("test")) - RegisterAdapter("test", func(ds *Dataset) Adapter { - return NewDefaultAdapter(ds) - }) - assert.True(t, HasAdapter("test")) - removeAdapter("test") -} - -func (me *adapterTest) TestRegisterAdapter() { - t := me.T() - RegisterAdapter("test", func(ds *Dataset) Adapter { - return NewDefaultAdapter(ds) - }) - assert.True(t, HasAdapter("test")) - removeAdapter("test") -} - -func (me *adapterTest) TestNewAdapter() { - t := me.T() - RegisterAdapter("test", func(ds *Dataset) Adapter { - return NewDefaultAdapter(ds) - }) - assert.True(t, HasAdapter("test")) - adapter := NewAdapter("test", From("test")) - assert.NotNil(t, adapter) - removeAdapter("test") - - adapter = NewAdapter("test", From("test")) - assert.NotNil(t, adapter) -} - -func TestAdapterSuite(t *testing.T) { - suite.Run(t, new(adapterTest)) -} diff --git a/crud_exec.go b/crud_exec.go deleted file mode 100644 index c24f8892..00000000 --- a/crud_exec.go +++ /dev/null @@ -1,410 +0,0 @@ -package goqu - -import ( - "context" - "database/sql" - "fmt" - "reflect" - "strings" - "sync" -) - -type ( - columnData struct { - ColumnName string - Transient bool - FieldName string - GoType reflect.Type - } - columnMap map[string]columnData - CrudExec struct { - database database - Sql string - Args []interface{} - err error - } - selectResults []Record -) - -var defaultColumnRenameFunction = strings.ToLower -var columnRenameFunction = defaultColumnRenameFunction -func SetColumnRenameFunction(new_function func(string) string) { - columnRenameFunction = new_function -} - -var struct_map_cache = make(map[interface{}]columnMap) -var struct_map_cache_lock = sync.Mutex{} - -func newCrudExec(database database, err error, sql string, args ...interface{}) *CrudExec { - return &CrudExec{database: database, err: err, Sql: sql, Args: args} -} - -func (me CrudExec) Exec() (sql.Result, error) { - return me.ExecContext(context.Background()) -} - -func (me CrudExec) ExecContext(ctx context.Context) (sql.Result, error) { - if me.err != nil { - return nil, me.err - } - return me.database.ExecContext(ctx, me.Sql, me.Args...) -} - -//This will execute the SQL and append results to the slice -// var myStructs []MyStruct -// if err := From("test").ScanStructs(&myStructs); err != nil{ -// panic(err.Error() -// } -// //use your structs -// -// -//i: A pointer to a slice of structs. -func (me CrudExec) ScanStructs(i interface{}) error { - return me.ScanStructsContext(context.Background(), i) -} - -//This will execute the SQL and append results to the slice -// var myStructs []MyStruct -// if err := From("test").ScanStructsContext(ctx, &myStructs); err != nil{ -// panic(err.Error() -// } -// //use your structs -// -// -//i: A pointer to a slice of structs. -func (me CrudExec) ScanStructsContext(ctx context.Context, i interface{}) error { - if me.err != nil { - return me.err - } - val := reflect.ValueOf(i) - if val.Kind() != reflect.Ptr { - return NewGoquError("Type must be a pointer to a slice when calling ScanStructs") - } - if reflect.Indirect(val).Kind() != reflect.Slice { - return NewGoquError("Type must be a pointer to a slice when calling ScanStructs") - } - _, err := me.scanContext(ctx, i, me.Sql, me.Args...) - return err -} - -//This will execute the SQL and fill out the struct with the fields returned. This method returns a boolean value that is false if no record was found -// var myStruct MyStruct -// found, err := From("test").Limit(1).ScanStruct(&myStruct) -// if err != nil{ -// panic(err.Error() -// } -// if !found{ -// fmt.Println("NOT FOUND") -// } -// -//i: A pointer to a struct -func (me CrudExec) ScanStruct(i interface{}) (bool, error) { - return me.ScanStructContext(context.Background(), i) -} - -//This will execute the SQL and fill out the struct with the fields returned. This method returns a boolean value that is false if no record was found -// var myStruct MyStruct -// found, err := From("test").Limit(1).ScanStructContext(ctx, &myStruct) -// if err != nil{ -// panic(err.Error() -// } -// if !found{ -// fmt.Println("NOT FOUND") -// } -// -//i: A pointer to a struct -func (me CrudExec) ScanStructContext(ctx context.Context, i interface{}) (bool, error) { - if me.err != nil { - return false, me.err - } - val := reflect.ValueOf(i) - if val.Kind() != reflect.Ptr { - return false, NewGoquError("Type must be a pointer to a struct when calling ScanStruct") - } - if reflect.Indirect(val).Kind() != reflect.Struct { - return false, NewGoquError("Type must be a pointer to a struct when calling ScanStruct") - } - return me.scanContext(ctx, i, me.Sql, me.Args...) -} - -//This will execute the SQL and append results to the slice. -// var ids []uint32 -// if err := From("test").Select("id").ScanVals(&ids); err != nil{ -// panic(err.Error() -// } -// -//i: Takes a pointer to a slice of primitive values. -func (me CrudExec) ScanVals(i interface{}) error { - return me.ScanValsContext(context.Background(), i) -} - -//This will execute the SQL and append results to the slice. -// var ids []uint32 -// if err := From("test").Select("id").ScanValsContext(ctx, &ids); err != nil{ -// panic(err.Error() -// } -// -//i: Takes a pointer to a slice of primitive values. -func (me CrudExec) ScanValsContext(ctx context.Context, i interface{}) error { - if me.err != nil { - return me.err - } - val := reflect.ValueOf(i) - if val.Kind() != reflect.Ptr { - return NewGoquError("Type must be a pointer to a slice when calling ScanVals") - } - val = reflect.Indirect(val) - if val.Kind() != reflect.Slice { - return NewGoquError("Type must be a pointer to a slice when calling ScanVals") - } - t, _, isSliceOfPointers := getTypeInfo(i, val) - rows, err := me.database.QueryContext(ctx, me.Sql, me.Args...) - if err != nil { - return err - } - defer rows.Close() - for rows.Next() { - row := reflect.New(t) - if err := rows.Scan(row.Interface()); err != nil { - return err - } - if isSliceOfPointers { - val.Set(reflect.Append(val, row)) - } else { - val.Set(reflect.Append(val, reflect.Indirect(row))) - } - } - if err := rows.Err(); err != nil { - return err - } - return nil -} - -//This will execute the SQL and set the value of the primitive. This method will return false if no record is found. -// var id uint32 -// found, err := From("test").Select("id").Limit(1).ScanVal(&id) -// if err != nil{ -// panic(err.Error() -// } -// if !found{ -// fmt.Println("NOT FOUND") -// } -// -// i: Takes a pointer to a primitive value. -func (me CrudExec) ScanVal(i interface{}) (bool, error) { - return me.ScanValContext(context.Background(), i) -} - -//This will execute the SQL and set the value of the primitive. This method will return false if no record is found. -// var id uint32 -// found, err := From("test").Select("id").Limit(1).ScanValContext(ctx, &id) -// if err != nil{ -// panic(err.Error() -// } -// if !found{ -// fmt.Println("NOT FOUND") -// } -// -// i: Takes a pointer to a primitive value. -func (me CrudExec) ScanValContext(ctx context.Context, i interface{}) (bool, error) { - if me.err != nil { - return false, me.err - } - val := reflect.ValueOf(i) - if val.Kind() != reflect.Ptr { - return false, NewGoquError("Type must be a pointer when calling ScanVal") - } - val = reflect.Indirect(val) - if val.Kind() == reflect.Slice { - return false, NewGoquError("Cannot scan into a slice when calling ScanVal") - } - rows, err := me.database.QueryContext(ctx, me.Sql, me.Args...) - if err != nil { - return false, err - } - count := 0 - defer rows.Close() - for rows.Next() { - count++ - if err := rows.Scan(i); err != nil { - return false, err - } - } - if err := rows.Err(); err != nil { - return false, err - } - return count != 0, nil -} - -func (me CrudExec) scanContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) { - var ( - found bool - results []Record - ) - cm, err := getColumnMap(i) - if err != nil { - return found, err - } - rows, err := me.database.QueryContext(ctx, query, args...) - if err != nil { - return false, err - } - defer rows.Close() - columns, err := rows.Columns() - if err != nil { - return false, err - } - for rows.Next() { - scans := make([]interface{}, len(columns)) - for i, col := range columns { - if data, ok := cm[col]; ok { - scans[i] = reflect.New(data.GoType).Interface() - } else { - return false, NewGoquError(`Unable to find corresponding field to column "%s" returned by query`, col) - } - } - if err := rows.Scan(scans...); err != nil { - return false, err - } - result := Record{} - for index, col := range columns { - result[col] = scans[index] - } - results = append(results, result) - } - if rows.Err() != nil { - return false, rows.Err() - } - if len(results) > 0 { - found = true - return found, assignVals(i, results, cm) - } - return found, nil -} - -func assignVals(i interface{}, results []Record, cm columnMap) error { - val := reflect.Indirect(reflect.ValueOf(i)) - t, _, isSliceOfPointers := getTypeInfo(i, val) - switch val.Kind() { - case reflect.Struct: - result := results[0] - initEmbeddedPtr(val) - for name, data := range cm { - src, ok := result[name] - if ok { - srcVal := reflect.ValueOf(src) - f := val.FieldByName(data.FieldName) - if f.Kind() == reflect.Ptr { - f.Set(reflect.ValueOf(srcVal)) - } else { - f.Set(reflect.Indirect(srcVal)) - } - } - } - case reflect.Slice: - for _, result := range results { - row := reflect.Indirect(reflect.New(t)) - initEmbeddedPtr(row) - for name, data := range cm { - src, ok := result[name] - if ok { - srcVal := reflect.ValueOf(src) - f := row.FieldByName(data.FieldName) - if f.Kind() == reflect.Ptr { - f.Set(reflect.ValueOf(srcVal)) - } else { - f.Set(reflect.Indirect(srcVal)) - } - } - } - if isSliceOfPointers { - val.Set(reflect.Append(val, row.Addr())) - } else { - val.Set(reflect.Append(val, row)) - } - } - } - return nil -} - -func initEmbeddedPtr(value reflect.Value) { - for i := 0; i < value.NumField(); i++ { - v := value.Field(i) - kind := v.Kind() - t := value.Type().Field(i) - if t.Anonymous && kind == reflect.Ptr { - z := reflect.New(t.Type.Elem()) - v.Set(z) - } - } -} - -func getColumnMap(i interface{}) (columnMap, error) { - val := reflect.Indirect(reflect.ValueOf(i)) - t, valKind, _ := getTypeInfo(i, val) - if valKind != reflect.Struct { - return nil, NewGoquError(fmt.Sprintf("Cannot SELECT into this type: %v", t)) - } - - struct_map_cache_lock.Lock() - defer struct_map_cache_lock.Unlock() - if _, ok := struct_map_cache[t]; !ok { - struct_map_cache[t] = createColumnMap(t) - } - return struct_map_cache[t], nil -} - -func createColumnMap(t reflect.Type) columnMap { - cm, n := columnMap{}, t.NumField() - var subColMaps []columnMap - for i := 0; i < n; i++ { - f := t.Field(i) - if f.Anonymous && (f.Type.Kind() == reflect.Struct || f.Type.Kind() == reflect.Ptr) { - if f.Type.Kind() == reflect.Ptr { - subColMaps = append(subColMaps, createColumnMap(f.Type.Elem())) - } else { - subColMaps = append(subColMaps, createColumnMap(f.Type)) - } - } else { - columnName := f.Tag.Get("db") - if columnName == "" { - columnName = columnRenameFunction(f.Name) - } - cm[columnName] = columnData{ - ColumnName: columnName, - Transient: columnName == "-", - FieldName: f.Name, - GoType: f.Type, - } - } - } - for _, subCm := range subColMaps { - for key, val := range subCm { - if _, ok := cm[key]; !ok { - cm[key] = val - } - } - } - return cm -} - -func getTypeInfo(i interface{}, val reflect.Value) (reflect.Type, reflect.Kind, bool) { - var t reflect.Type - isSliceOfPointers := false - valKind := val.Kind() - if valKind == reflect.Slice { - if reflect.ValueOf(i).Kind() == reflect.Ptr { - t = reflect.TypeOf(i).Elem().Elem() - } else { - t = reflect.TypeOf(i).Elem() - } - if t.Kind() == reflect.Ptr { - isSliceOfPointers = true - t = t.Elem() - } - valKind = t.Kind() - } else { - t = val.Type() - } - return t, valKind, isSliceOfPointers -} diff --git a/database.go b/database.go index 38af88a7..8b02caef 100644 --- a/database.go +++ b/database.go @@ -3,48 +3,35 @@ package goqu import ( "context" "database/sql" + + "github.com/doug-martin/goqu/v7/exec" ) type ( - database interface { - queryAdapter(builder *Dataset) Adapter - From(cols ...interface{}) *Dataset - Logger(logger Logger) - Exec(query string, args ...interface{}) (sql.Result, error) - ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) - Prepare(query string) (*sql.Stmt, error) - PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) - Query(query string, args ...interface{}) (*sql.Rows, error) - QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) - QueryRow(query string, args ...interface{}) *sql.Row - QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row - ScanStructs(i interface{}, query string, args ...interface{}) error - ScanStructsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error - ScanStruct(i interface{}, query string, args ...interface{}) (bool, error) - ScanStructContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) - ScanVals(i interface{}, query string, args ...interface{}) error - ScanValsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error - ScanVal(i interface{}, query string, args ...interface{}) (bool, error) - ScanValContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) + Logger interface { + Printf(format string, v ...interface{}) } - //This struct is the wrapper for a Db. The struct delegates most calls to either an Exec instance or to the Db passed into the constructor. + // This struct is the wrapper for a Db. The struct delegates most calls to either an Exec instance or to the Db + // passed into the constructor. Database struct { logger Logger - Dialect string + dialect string Db *sql.DB + qf exec.QueryFactory } ) -//This is the common entry point into goqu. +// This is the common entry point into goqu. // -//dialect: This is the adapter dialect, you should see your database adapter for the string to use. Built in adpaters can be found at https://github.com/doug-martin/goqu/tree/master/adapters +// dialect: This is the adapter dialect, you should see your database adapter for the string to use. Built in adapters +// can be found at https://github.com/doug-martin/goqu/tree/master/adapters // -//db: A sql.Db to use for querying the database +// db: A sql.Db to use for querying the database // import ( // "database/sql" // "fmt" -// "github.com/doug-martin/goqu/v6" -// _ "github.com/doug-martin/goqu/v6/adapters/postgres" +// "github.com/doug-martin/goqu/v7" +// _ "github.com/doug-martin/goqu/v7/adapters/postgres" // _ "github.com/lib/pq" // ) // @@ -55,86 +42,86 @@ type ( // } // db := goqu.New("postgres", sqlDb) // } -//The most commonly used Database method is From, which creates a new Dataset that uses the correct adapter and supports queries. +// The most commonly used Database method is From, which creates a new Dataset that uses the correct adapter and +// supports queries. // var ids []uint32 // if err := db.From("items").Where(goqu.I("id").Gt(10)).Pluck("id", &ids); err != nil { // panic(err.Error()) // } // fmt.Printf("%+v", ids) -func New(dialect string, db *sql.DB) *Database { - return &Database{Dialect: dialect, Db: db} +func newDatabase(dialect string, db *sql.DB) *Database { + return &Database{dialect: dialect, Db: db} +} + +// returns this databases dialect +func (d *Database) Dialect() string { + return d.dialect } -//Starts a new Transaction. -func (me *Database) Begin() (*TxDatabase, error) { - tx, err := me.Db.Begin() +// Starts a new Transaction. +func (d *Database) Begin() (*TxDatabase, error) { + tx, err := d.Db.Begin() if err != nil { return nil, err } - return &TxDatabase{Dialect: me.Dialect, Tx: tx, logger: me.logger}, nil -} - -//used internally to create a new Adapter for a dataset -func (me *Database) queryAdapter(dataset *Dataset) Adapter { - return NewAdapter(me.Dialect, dataset) + return &TxDatabase{dialect: d.dialect, Tx: tx, logger: d.logger}, nil } -//Creates a new Dataset that uses the correct adapter and supports queries. +// Creates a new Dataset that uses the correct adapter and supports queries. // var ids []uint32 // if err := db.From("items").Where(goqu.I("id").Gt(10)).Pluck("id", &ids); err != nil { // panic(err.Error()) // } // fmt.Printf("%+v", ids) // -//from...: Sources for you dataset, could be table names (strings), a goqu.Literal or another goqu.Dataset -func (me *Database) From(from ...interface{}) *Dataset { - return withDatabase(me).From(from...) +// from...: Sources for you dataset, could be table names (strings), a goqu.Literal or another goqu.Dataset +func (d *Database) From(from ...interface{}) *Dataset { + return newDataset(d.dialect, d.queryFactory()).From(from...) } -//Sets the logger for to use when logging queries -func (me *Database) Logger(logger Logger) { - me.logger = logger +// Sets the logger for to use when logging queries +func (d *Database) Logger(logger Logger) { + d.logger = logger } -//Logs a given operation with the specified sql and arguments -func (me *Database) Trace(op, sql string, args ...interface{}) { - if me.logger != nil { - if sql != "" { +// Logs a given operation with the specified sql and arguments +func (d *Database) Trace(op, sqlString string, args ...interface{}) { + if d.logger != nil { + if sqlString != "" { if len(args) != 0 { - me.logger.Printf("[goqu] %s [query:=`%s` args:=%+v]", op, sql, args) + d.logger.Printf("[goqu] %s [query:=`%s` args:=%+v]", op, sqlString, args) } else { - me.logger.Printf("[goqu] %s [query:=`%s`]", op, sql) + d.logger.Printf("[goqu] %s [query:=`%s`]", op, sqlString) } } else { - me.logger.Printf("[goqu] %s", op) + d.logger.Printf("[goqu] %s", op) } } } -//Uses the db to Execute the query with arguments and return the sql.Result +// Uses the db to Execute the query with arguments and return the sql.Result // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) Exec(query string, args ...interface{}) (sql.Result, error) { - me.Trace("EXEC", query, args...) - return me.Db.Exec(query, args...) +// args...: for any placeholder parameters in the query +func (d *Database) Exec(query string, args ...interface{}) (sql.Result, error) { + return d.ExecContext(context.Background(), query, args...) } -//Uses the db to Execute the query with arguments and return the sql.Result +// Uses the db to Execute the query with arguments and return the sql.Result // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { - me.Trace("EXEC", query, args...) - return me.Db.ExecContext(ctx, query, args...) +// args...: for any placeholder parameters in the query +func (d *Database) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + d.Trace("EXEC", query, args...) + return d.Db.ExecContext(ctx, query, args...) } -//Can be used to prepare a query. +// Can be used to prepare a query. // -//You can use this in tandem with a dataset by doing the following. -// sql, args, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSql(true) +// You can use this in tandem with a dataset by doing the following. +// sql, args, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSQL(true) // if err != nil{ // panic(err.Error()) //you could gracefully handle the error also // } @@ -155,16 +142,15 @@ func (me *Database) ExecContext(ctx context.Context, query string, args ...inter // panic(err.Error()) //you could gracefully handle the error also // } // -//query: The SQL statement to prepare. -func (me *Database) Prepare(query string) (*sql.Stmt, error) { - me.Trace("PREPARE", query) - return me.Db.Prepare(query) +// query: The SQL statement to prepare. +func (d *Database) Prepare(query string) (*sql.Stmt, error) { + return d.PrepareContext(context.Background(), query) } -//Can be used to prepare a query. +// Can be used to prepare a query. // -//You can use this in tandem with a dataset by doing the following. -// sql, args, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSql(true) +// You can use this in tandem with a dataset by doing the following. +// sql, args, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSQL(true) // if err != nil{ // panic(err.Error()) //you could gracefully handle the error also // } @@ -185,16 +171,16 @@ func (me *Database) Prepare(query string) (*sql.Stmt, error) { // panic(err.Error()) //you could gracefully handle the error also // } // -//query: The SQL statement to prepare. -func (me *Database) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { - me.Trace("PREPARE", query) - return me.Db.PrepareContext(ctx, query) +// query: The SQL statement to prepare. +func (d *Database) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { + d.Trace("PREPARE", query) + return d.Db.PrepareContext(ctx, query) } -//Used to query for multiple rows. +// Used to query for multiple rows. // -//You can use this in tandem with a dataset by doing the following. -// sql, err := db.From("items").Where(goqu.I("id").Gt(10)).Sql() +// You can use this in tandem with a dataset by doing the following. +// sql, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSQL() // if err != nil{ // panic(err.Error()) //you could gracefully handle the error also // } @@ -210,18 +196,17 @@ func (me *Database) PrepareContext(ctx context.Context, query string) (*sql.Stmt // panic(err.Error()) //you could gracefully handle the error also // } // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) Query(query string, args ...interface{}) (*sql.Rows, error) { - me.Trace("QUERY", query, args...) - return me.Db.Query(query, args...) +// args...: for any placeholder parameters in the query +func (d *Database) Query(query string, args ...interface{}) (*sql.Rows, error) { + return d.QueryContext(context.Background(), query, args...) } -//Used to query for multiple rows. +// Used to query for multiple rows. // -//You can use this in tandem with a dataset by doing the following. -// sql, err := db.From("items").Where(goqu.I("id").Gt(10)).Sql() +// You can use this in tandem with a dataset by doing the following. +// sql, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSQL() // if err != nil{ // panic(err.Error()) //you could gracefully handle the error also // } @@ -237,18 +222,18 @@ func (me *Database) Query(query string, args ...interface{}) (*sql.Rows, error) // panic(err.Error()) //you could gracefully handle the error also // } // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { - me.Trace("QUERY", query, args...) - return me.Db.QueryContext(ctx, query, args...) +// args...: for any placeholder parameters in the query +func (d *Database) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { + d.Trace("QUERY", query, args...) + return d.Db.QueryContext(ctx, query, args...) } -//Used to query for a single row. +// Used to query for a single row. // -//You can use this in tandem with a dataset by doing the following. -// sql, err := db.From("items").Where(goqu.I("id").Gt(10)).Limit(1).Sql() +// You can use this in tandem with a dataset by doing the following. +// sql, err := db.From("items").Where(goqu.I("id").Gt(10)).Limit(1).ToSQL() // if err != nil{ // panic(err.Error()) //you could gracefully handle the error also // } @@ -258,18 +243,17 @@ func (me *Database) QueryContext(ctx context.Context, query string, args ...inte // } // //scan your row // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) QueryRow(query string, args ...interface{}) *sql.Row { - me.Trace("QUERY ROW", query, args...) - return me.Db.QueryRow(query, args...) +// args...: for any placeholder parameters in the query +func (d *Database) QueryRow(query string, args ...interface{}) *sql.Row { + return d.QueryRowContext(context.Background(), query, args...) } -//Used to query for a single row. +// Used to query for a single row. // -//You can use this in tandem with a dataset by doing the following. -// sql, err := db.From("items").Where(goqu.I("id").Gt(10)).Limit(1).Sql() +// You can use this in tandem with a dataset by doing the following. +// sql, err := db.From("items").Where(goqu.I("id").Gt(10)).Limit(1).ToSQL() // if err != nil{ // panic(err.Error()) //you could gracefully handle the error also // } @@ -279,276 +263,280 @@ func (me *Database) QueryRow(query string, args ...interface{}) *sql.Row { // } // //scan your row // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { - me.Trace("QUERY ROW", query, args...) - return me.Db.QueryRowContext(ctx, query, args...) +// args...: for any placeholder parameters in the query +func (d *Database) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + d.Trace("QUERY ROW", query, args...) + return d.Db.QueryRowContext(ctx, query, args...) } -//Queries the database using the supplied query, and args and uses CrudExec.ScanStructs to scan the results into a slice of structs +func (d *Database) queryFactory() exec.QueryFactory { + if d.qf == nil { + d.qf = exec.NewQueryFactory(d) + } + return d.qf +} + +// Queries the database using the supplied query, and args and uses CrudExec.ScanStructs to scan the results into a +// slice of structs // -//i: A pointer to a slice of structs +// i: A pointer to a slice of structs // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) ScanStructs(i interface{}, query string, args ...interface{}) error { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanStructs(i) +// args...: for any placeholder parameters in the query +func (d *Database) ScanStructs(i interface{}, query string, args ...interface{}) error { + return d.ScanStructsContext(context.Background(), i, query, args...) } -//Queries the database using the supplied context, query, and args and uses CrudExec.ScanStructsContext to scan the results into a slice of structs +// Queries the database using the supplied context, query, and args and uses CrudExec.ScanStructsContext to scan the +// results into a slice of structs // -//i: A pointer to a slice of structs +// i: A pointer to a slice of structs // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) ScanStructsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanStructsContext(ctx, i) +// args...: for any placeholder parameters in the query +func (d *Database) ScanStructsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error { + return d.queryFactory().FromSQL(query, args...).ScanStructsContext(ctx, i) } -//Queries the database using the supplied query, and args and uses CrudExec.ScanStruct to scan the results into a struct +// Queries the database using the supplied query, and args and uses CrudExec.ScanStruct to scan the results into a +// struct // -//i: A pointer to a struct +// i: A pointer to a struct // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) ScanStruct(i interface{}, query string, args ...interface{}) (bool, error) { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanStruct(i) +// args...: for any placeholder parameters in the query +func (d *Database) ScanStruct(i interface{}, query string, args ...interface{}) (bool, error) { + return d.ScanStructContext(context.Background(), i, query, args...) } -//Queries the database using the supplied context, query, and args and uses CrudExec.ScanStructContext to scan the results into a struct +// Queries the database using the supplied context, query, and args and uses CrudExec.ScanStructContext to scan the +// results into a struct // -//i: A pointer to a struct +// i: A pointer to a struct // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) ScanStructContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanStructContext(ctx, i) +// args...: for any placeholder parameters in the query +func (d *Database) ScanStructContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) { + return d.queryFactory().FromSQL(query, args...).ScanStructContext(ctx, i) } -//Queries the database using the supplied query, and args and uses CrudExec.ScanVals to scan the results into a slice of primitive values +// Queries the database using the supplied query, and args and uses CrudExec.ScanVals to scan the results into a slice +// of primitive values // -//i: A pointer to a slice of primitive values +// i: A pointer to a slice of primitive values // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) ScanVals(i interface{}, query string, args ...interface{}) error { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanVals(i) +// args...: for any placeholder parameters in the query +func (d *Database) ScanVals(i interface{}, query string, args ...interface{}) error { + return d.ScanValsContext(context.Background(), i, query, args...) } -//Queries the database using the supplied context, query, and args and uses CrudExec.ScanValsContext to scan the results into a slice of primitive values +// Queries the database using the supplied context, query, and args and uses CrudExec.ScanValsContext to scan the +// results into a slice of primitive values // -//i: A pointer to a slice of primitive values +// i: A pointer to a slice of primitive values // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) ScanValsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanValsContext(ctx, i) +// args...: for any placeholder parameters in the query +func (d *Database) ScanValsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error { + return d.queryFactory().FromSQL(query, args...).ScanValsContext(ctx, i) } -//Queries the database using the supplied query, and args and uses CrudExec.ScanVal to scan the results into a primitive value +// Queries the database using the supplied query, and args and uses CrudExec.ScanVal to scan the results into a +// primitive value // -//i: A pointer to a primitive value +// i: A pointer to a primitive value // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) ScanVal(i interface{}, query string, args ...interface{}) (bool, error) { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanVal(i) +// args...: for any placeholder parameters in the query +func (d *Database) ScanVal(i interface{}, query string, args ...interface{}) (bool, error) { + return d.ScanValContext(context.Background(), i, query, args...) } -//Queries the database using the supplied context, query, and args and uses CrudExec.ScanValContext to scan the results into a primitive value +// Queries the database using the supplied context, query, and args and uses CrudExec.ScanValContext to scan the +// results into a primitive value // -//i: A pointer to a primitive value +// i: A pointer to a primitive value // -//query: The SQL to execute +// query: The SQL to execute // -//args...: for any placeholder parameters in the query -func (me *Database) ScanValContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanValContext(ctx, i) +// args...: for any placeholder parameters in the query +func (d *Database) ScanValContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) { + return d.queryFactory().FromSQL(query, args...).ScanValContext(ctx, i) } -//A wrapper around a sql.Tx and works the same way as Database -type TxDatabase struct { - logger Logger - Dialect string - Tx *sql.Tx -} +// A wrapper around a sql.Tx and works the same way as Database +type ( + TxDatabase struct { + logger Logger + dialect string + Tx *sql.Tx + qf exec.QueryFactory + } +) -//used internally to create a new query adapter for a Dataset -func (me *TxDatabase) queryAdapter(dataset *Dataset) Adapter { - return NewAdapter(me.Dialect, dataset) +// returns this databases dialect +func (td *TxDatabase) Dialect() string { + return td.dialect } -//Creates a new Dataset for querying a Database. -func (me *TxDatabase) From(cols ...interface{}) *Dataset { - return withDatabase(me).From(cols...) - +// Creates a new Dataset for querying a Database. +func (td *TxDatabase) From(cols ...interface{}) *Dataset { + return newDataset(td.dialect, td.queryFactory()).From(cols...) } -//Sets the logger -func (me *TxDatabase) Logger(logger Logger) { - me.logger = logger +// Sets the logger +func (td *TxDatabase) Logger(logger Logger) { + td.logger = logger } -func (me *TxDatabase) Trace(op, sql string, args ...interface{}) { - if me.logger != nil { - if sql != "" { +func (td *TxDatabase) Trace(op, sqlString string, args ...interface{}) { + if td.logger != nil { + if sqlString != "" { if len(args) != 0 { - me.logger.Printf("[goqu - transaction] %s [query:=`%s` args:=%+v] ", op, sql, args) + td.logger.Printf("[goqu - transaction] %s [query:=`%s` args:=%+v] ", op, sqlString, args) } else { - me.logger.Printf("[goqu - transaction] %s [query:=`%s`] ", op, sql) + td.logger.Printf("[goqu - transaction] %s [query:=`%s`] ", op, sqlString) } } else { - me.logger.Printf("[goqu - transaction] %s", op) + td.logger.Printf("[goqu - transaction] %s", op) } } } -//See Database#Exec -func (me *TxDatabase) Exec(query string, args ...interface{}) (sql.Result, error) { - me.Trace("EXEC", query, args...) - return me.Tx.Exec(query, args...) +// See Database#Exec +func (td *TxDatabase) Exec(query string, args ...interface{}) (sql.Result, error) { + return td.ExecContext(context.Background(), query, args...) +} + +// See Database#ExecContext +func (td *TxDatabase) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + td.Trace("EXEC", query, args...) + return td.Tx.ExecContext(ctx, query, args...) } -//See Database#ExecContext -func (me *TxDatabase) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { - me.Trace("EXEC", query, args...) - return me.Tx.ExecContext(ctx, query, args...) +// See Database#Prepare +func (td *TxDatabase) Prepare(query string) (*sql.Stmt, error) { + return td.PrepareContext(context.Background(), query) } -//See Database#Prepare -func (me *TxDatabase) Prepare(query string) (*sql.Stmt, error) { - me.Trace("PREPARE", query) - return me.Tx.Prepare(query) +// See Database#PrepareContext +func (td *TxDatabase) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { + td.Trace("PREPARE", query) + return td.Tx.PrepareContext(ctx, query) } -//See Database#PrepareContext -func (me *TxDatabase) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { - me.Trace("PREPARE", query) - return me.Tx.PrepareContext(ctx, query) +// See Database#Query +func (td *TxDatabase) Query(query string, args ...interface{}) (*sql.Rows, error) { + return td.QueryContext(context.Background(), query, args...) } -//See Database#Query -func (me *TxDatabase) Query(query string, args ...interface{}) (*sql.Rows, error) { - me.Trace("QUERY", query, args...) - return me.Tx.Query(query, args...) +// See Database#QueryContext +func (td *TxDatabase) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { + td.Trace("QUERY", query, args...) + return td.Tx.QueryContext(ctx, query, args...) } -//See Database#QueryContext -func (me *TxDatabase) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { - me.Trace("QUERY", query, args...) - return me.Tx.QueryContext(ctx, query, args...) +// See Database#QueryRow +func (td *TxDatabase) QueryRow(query string, args ...interface{}) *sql.Row { + return td.QueryRowContext(context.Background(), query, args...) } -//See Database#QueryRow -func (me *TxDatabase) QueryRow(query string, args ...interface{}) *sql.Row { - me.Trace("QUERY ROW", query, args...) - return me.Tx.QueryRow(query, args...) +// See Database#QueryRowContext +func (td *TxDatabase) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + td.Trace("QUERY ROW", query, args...) + return td.Tx.QueryRowContext(ctx, query, args...) } -//See Database#QueryRowContext -func (me *TxDatabase) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { - me.Trace("QUERY ROW", query, args...) - return me.Tx.QueryRowContext(ctx, query, args...) +func (td *TxDatabase) queryFactory() exec.QueryFactory { + if td.qf == nil { + td.qf = exec.NewQueryFactory(td) + } + return td.qf } -//See Database#ScanStructs -func (me *TxDatabase) ScanStructs(i interface{}, query string, args ...interface{}) error { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanStructs(i) +// See Database#ScanStructs +func (td *TxDatabase) ScanStructs(i interface{}, query string, args ...interface{}) error { + return td.ScanStructsContext(context.Background(), i, query, args...) } -//See Database#ScanStructsContext -func (me *TxDatabase) ScanStructsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanStructsContext(ctx, i) +// See Database#ScanStructsContext +func (td *TxDatabase) ScanStructsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error { + return td.queryFactory().FromSQL(query, args...).ScanStructsContext(ctx, i) } -//See Database#ScanStruct -func (me *TxDatabase) ScanStruct(i interface{}, query string, args ...interface{}) (bool, error) { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanStruct(i) +// See Database#ScanStruct +func (td *TxDatabase) ScanStruct(i interface{}, query string, args ...interface{}) (bool, error) { + return td.ScanStructContext(context.Background(), i, query, args...) } -//See Database#ScanStructContext -func (me *TxDatabase) ScanStructContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanStructContext(ctx, i) +// See Database#ScanStructContext +func (td *TxDatabase) ScanStructContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) { + return td.queryFactory().FromSQL(query, args...).ScanStructContext(ctx, i) } -//See Database#ScanVals -func (me *TxDatabase) ScanVals(i interface{}, query string, args ...interface{}) error { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanVals(i) +// See Database#ScanVals +func (td *TxDatabase) ScanVals(i interface{}, query string, args ...interface{}) error { + return td.ScanValsContext(context.Background(), i, query, args...) } -//See Database#ScanValsContext -func (me *TxDatabase) ScanValsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanValsContext(ctx, i) +// See Database#ScanValsContext +func (td *TxDatabase) ScanValsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error { + return td.queryFactory().FromSQL(query, args...).ScanValsContext(ctx, i) } -//See Database#ScanVal -func (me *TxDatabase) ScanVal(i interface{}, query string, args ...interface{}) (bool, error) { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanVal(i) +// See Database#ScanVal +func (td *TxDatabase) ScanVal(i interface{}, query string, args ...interface{}) (bool, error) { + return td.ScanValContext(context.Background(), i, query, args...) } -//See Database#ScanValContext -func (me *TxDatabase) ScanValContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) { - exec := newCrudExec(me, nil, query, args...) - return exec.ScanValContext(ctx, i) +// See Database#ScanValContext +func (td *TxDatabase) ScanValContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) { + return td.queryFactory().FromSQL(query, args...).ScanValContext(ctx, i) } -//COMMIT the transaction -func (me *TxDatabase) Commit() error { - me.Trace("COMMIT", "") - return me.Tx.Commit() +// COMMIT the transaction +func (td *TxDatabase) Commit() error { + td.Trace("COMMIT", "") + return td.Tx.Commit() } -//ROLLBACK the transaction -func (me *TxDatabase) Rollback() error { - me.Trace("ROLLBACK", "") - return me.Tx.Rollback() +// ROLLBACK the transaction +func (td *TxDatabase) Rollback() error { + td.Trace("ROLLBACK", "") + return td.Tx.Rollback() } -//A helper method that will automatically COMMIT or ROLLBACK once the supplied function is done executing +// A helper method that will automatically COMMIT or ROLLBACK once the supplied function is done executing // // tx, err := db.Begin() // if err != nil{ -// panic(err.Error()) //you could gracefully handle the error also +// panic(err.Error()) // you could gracefully handle the error also // } // if err := tx.Wrap(func() error{ // if _, err := tx.From("test").Insert(Record{"a":1, "b": "b"}).Exec(){ -// //this error will be the return error from the Wrap call +// // this error will be the return error from the Wrap call // return err // } // return nil // }); err != nil{ -// panic(err.Error()) //you could gracefully handle the error also +// panic(err.Error()) // you could gracefully handle the error also // } -func (me *TxDatabase) Wrap(fn func() error) error { +func (td *TxDatabase) Wrap(fn func() error) error { if err := fn(); err != nil { - if rollbackErr := me.Rollback(); rollbackErr != nil { + if rollbackErr := td.Rollback(); rollbackErr != nil { return rollbackErr } return err } - return me.Commit() + return td.Commit() } diff --git a/database_example_test.go b/database_example_test.go new file mode 100644 index 00000000..dd39f021 --- /dev/null +++ b/database_example_test.go @@ -0,0 +1,90 @@ +package goqu_test + +import ( + "context" + "fmt" + "time" + + "github.com/doug-martin/goqu/v7" +) + +func ExampleDatabase_Begin() { + db := getDb() + + tx, err := db.Begin() + if err != nil { + fmt.Println("Error starting transaction", err.Error()) + } + + // use tx.From to get a dataset that will execute within this transaction + update := tx.From("goqu_user"). + Where(goqu.Ex{"last_name": "Yukon"}). + Returning("id"). + Update(goqu.Record{"last_name": "Ucon"}) + + var ids []int64 + if err := update.ScanVals(&ids); err != nil { + if rErr := tx.Rollback(); rErr != nil { + fmt.Println("An error occurred while issuing ROLLBACK\n\t", rErr.Error()) + } else { + fmt.Println("An error occurred while updating users ROLLBACK transaction\n\t", err.Error()) + } + return + } + if err := tx.Commit(); err != nil { + fmt.Println("An error occurred while issuing COMMIT\n\t", err.Error()) + } else { + fmt.Printf("Updated users in transaction [ids:=%+v]", ids) + } + // Output: + // Updated users in transaction [ids:=[1 2 3]] +} + +func ExampleDatabase_Dialect() { + db := getDb() + + fmt.Println(db.Dialect()) + + // Output: + // postgres +} + +func ExampleDatabase_Exec() { + db := getDb() + + _, err := db.Exec(`DROP TABLE "goqu_user"`) + if err != nil { + fmt.Println("Error occurred while dropping table", err.Error()) + } + fmt.Println("Dropped table goqu_user") + // Output: + // Dropped table goqu_user +} + +func ExampleDatabase_ExecContext() { + db := getDb() + d := time.Now().Add(50 * time.Millisecond) + ctx, cancel := context.WithDeadline(context.Background(), d) + defer cancel() + _, err := db.ExecContext(ctx, `DROP TABLE "goqu_user"`) + if err != nil { + fmt.Println("Error occurred while dropping table", err.Error()) + } + fmt.Println("Dropped table goqu_user") + // Output: + // Dropped table goqu_user +} + +func ExampleDatabase_From() { + + db := getDb() + var names []string + + if err := db.From("goqu_user").Select("first_name").ScanVals(&names); err != nil { + fmt.Println(err.Error()) + } else { + fmt.Println("Fetched Users names:", names) + } + // Output: + // Fetched Users names: [Bob Sally Vinita John] +} diff --git a/database_test.go b/database_test.go index 16e49fc5..5096cbcd 100644 --- a/database_test.go +++ b/database_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/doug-martin/goqu/v7/internal/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -18,25 +19,26 @@ type dbTestMockLogger struct { Messages []string } -func (me *dbTestMockLogger) Printf(format string, v ...interface{}) { - me.Messages = append(me.Messages, fmt.Sprintf(format, v...)) +func (dtml *dbTestMockLogger) Printf(format string, v ...interface{}) { + dtml.Messages = append(dtml.Messages, fmt.Sprintf(format, v...)) } -func (me *dbTestMockLogger) Reset(format string, v ...interface{}) { - me.Messages = me.Messages[0:0] +func (dtml *dbTestMockLogger) Reset(format string, v ...interface{}) { + dtml.Messages = dtml.Messages[0:0] } type databaseTest struct { suite.Suite } -func (me *databaseTest) TestLogger() { - t := me.T() +func (dt *databaseTest) TestLogger() { + t := dt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectExec(`SELECT \* FROM "items" WHERE "id" = ?`). WithArgs(1). @@ -57,13 +59,14 @@ func (me *databaseTest) TestLogger() { }) } -func (me *databaseTest) TestScanStructs() { - t := me.T() +func (dt *databaseTest) TestScanStructs() { + t := dt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectQuery(`SELECT "test" FROM "items"`). WithArgs(). @@ -80,13 +83,16 @@ func (me *databaseTest) TestScanStructs() { assert.Equal(t, items[1].Name, "Test2") items = items[0:0] - assert.EqualError(t, db.ScanStructs(items, `SELECT * FROM "items"`), "goqu: Type must be a pointer to a slice when calling ScanStructs") - assert.EqualError(t, db.ScanStructs(&testActionItem{}, `SELECT * FROM "items"`), "goqu: Type must be a pointer to a slice when calling ScanStructs") - assert.EqualError(t, db.ScanStructs(&items, `SELECT "test" FROM "items"`), `goqu: Unable to find corresponding field to column "test" returned by query`) + assert.EqualError(t, db.ScanStructs(items, `SELECT * FROM "items"`), + "goqu: type must be a pointer to a slice when scanning into structs") + assert.EqualError(t, db.ScanStructs(&testActionItem{}, `SELECT * FROM "items"`), + "goqu: type must be a pointer to a slice when scanning into structs") + assert.EqualError(t, db.ScanStructs(&items, `SELECT "test" FROM "items"`), + `goqu: unable to find corresponding field to column "test" returned by query`) } -func (me *databaseTest) TestScanStruct() { - t := me.T() +func (dt *databaseTest) TestScanStruct() { + t := dt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectQuery(`SELECT \* FROM "items" LIMIT 1`). @@ -106,15 +112,15 @@ func (me *databaseTest) TestScanStruct() { assert.Equal(t, item.Name, "Test1") _, err = db.ScanStruct(item, `SELECT * FROM "items" LIMIT 1`) - assert.EqualError(t, err, "goqu: Type must be a pointer to a struct when calling ScanStruct") + assert.EqualError(t, err, "goqu: type must be a pointer to a struct when scanning into a struct") _, err = db.ScanStruct([]testActionItem{}, `SELECT * FROM "items" LIMIT 1`) - assert.EqualError(t, err, "goqu: Type must be a pointer to a struct when calling ScanStruct") + assert.EqualError(t, err, "goqu: type must be a pointer to a struct when scanning into a struct") _, err = db.ScanStruct(&item, `SELECT "test" FROM "items" LIMIT 1`) - assert.EqualError(t, err, `goqu: Unable to find corresponding field to column "test" returned by query`) + assert.EqualError(t, err, `goqu: unable to find corresponding field to column "test" returned by query`) } -func (me *databaseTest) TestScanVals() { - t := me.T() +func (dt *databaseTest) TestScanVals() { + t := dt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectQuery(`SELECT "id" FROM "items"`). @@ -126,12 +132,14 @@ func (me *databaseTest) TestScanVals() { assert.NoError(t, db.ScanVals(&ids, `SELECT "id" FROM "items"`)) assert.Len(t, ids, 5) - assert.EqualError(t, db.ScanVals([]uint32{}, `SELECT "id" FROM "items"`), "goqu: Type must be a pointer to a slice when calling ScanVals") - assert.EqualError(t, db.ScanVals(testActionItem{}, `SELECT "id" FROM "items"`), "goqu: Type must be a pointer to a slice when calling ScanVals") + assert.EqualError(t, db.ScanVals([]uint32{}, `SELECT "id" FROM "items"`), + "goqu: type must be a pointer to a slice when scanning into vals") + assert.EqualError(t, db.ScanVals(testActionItem{}, `SELECT "id" FROM "items"`), + "goqu: type must be a pointer to a slice when scanning into vals") } -func (me *databaseTest) TestScanVal() { - t := me.T() +func (dt *databaseTest) TestScanVal() { + t := dt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectQuery(`SELECT "id" FROM "items"`). @@ -146,13 +154,15 @@ func (me *databaseTest) TestScanVal() { assert.True(t, found) found, err = db.ScanVal([]int64{}, `SELECT "id" FROM "items"`) - assert.EqualError(t, err, "goqu: Type must be a pointer when calling ScanVal") + assert.False(t, found) + assert.EqualError(t, err, "goqu: type must be a pointer when scanning into val") found, err = db.ScanVal(10, `SELECT "id" FROM "items"`) - assert.EqualError(t, err, "goqu: Type must be a pointer when calling ScanVal") + assert.False(t, found) + assert.EqualError(t, err, "goqu: type must be a pointer when scanning into val") } -func (me *databaseTest) TestExec() { - t := me.T() +func (dt *databaseTest) TestExec() { + t := dt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectExec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE \("name" IS NULL\)`). @@ -161,7 +171,7 @@ func (me *databaseTest) TestExec() { mock.ExpectExec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE \("name" IS NULL\)`). WithArgs(). - WillReturnError(NewGoquError("mock error")) + WillReturnError(errors.New("mock error")) db := New("mock", mDb) _, err = db.Exec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE ("name" IS NULL)`) @@ -170,17 +180,18 @@ func (me *databaseTest) TestExec() { assert.EqualError(t, err, "goqu: mock error") } -func (me *databaseTest) TestQuery() { - t := me.T() +func (dt *databaseTest) TestQuery() { + t := dt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnError(NewGoquError("mock error")) + WillReturnError(errors.New("mock error")) db := New("mock", mDb) _, err = db.Query(`SELECT * FROM "items"`) @@ -190,17 +201,18 @@ func (me *databaseTest) TestQuery() { assert.EqualError(t, err, "goqu: mock error") } -func (me *databaseTest) TestQueryRow() { - t := me.T() +func (dt *databaseTest) TestQueryRow() { + t := dt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnError(NewGoquError("mock error")) + WillReturnError(errors.New("mock error")) db := New("mock", mDb) rows := db.QueryRow(`SELECT * FROM "items"`) @@ -212,8 +224,8 @@ func (me *databaseTest) TestQueryRow() { assert.EqualError(t, rows.Scan(&address, &name), "goqu: mock error") } -func (me *databaseTest) TestPrepare() { - t := me.T() +func (dt *databaseTest) TestPrepare() { + t := dt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectPrepare("SELECT \\* FROM test WHERE id = \\?") @@ -223,16 +235,16 @@ func (me *databaseTest) TestPrepare() { assert.NotNil(t, stmt) } -func (me *databaseTest) TestBegin() { - t := me.T() +func (dt *databaseTest) TestBegin() { + t := dt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() - mock.ExpectBegin().WillReturnError(NewGoquError("transaction error")) + mock.ExpectBegin().WillReturnError(errors.New("transaction error")) db := New("mock", mDb) tx, err := db.Begin() assert.NoError(t, err) - assert.Equal(t, tx.Dialect, "mock") + assert.Equal(t, tx.Dialect(), "mock") _, err = db.Begin() assert.EqualError(t, err, "goqu: transaction error") @@ -246,21 +258,22 @@ type txDatabaseTest struct { suite.Suite } -func (me *txDatabaseTest) TestLogger() { - t := me.T() +func (tdt *txDatabaseTest) TestLogger() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectExec(`SELECT \* FROM "items" WHERE "id" = ?`). WithArgs(1). WillReturnResult(sqlmock.NewResult(0, 0)) mock.ExpectCommit() - tx, err := New("db-mock", mDb).Begin() + tx, err := newDatabase("db-mock", mDb).Begin() assert.NoError(t, err) logger := new(dbTestMockLogger) tx.Logger(logger) @@ -268,7 +281,7 @@ func (me *txDatabaseTest) TestLogger() { assert.NoError(t, tx.ScanStructs(&items, `SELECT * FROM "items"`)) _, err = tx.Exec(`SELECT * FROM "items" WHERE "id" = ?`, 1) assert.NoError(t, err) - tx.Commit() + assert.NoError(t, tx.Commit()) assert.Equal(t, logger.Messages, []string{ "[goqu - transaction] QUERY [query:=`SELECT * FROM \"items\"`] ", "[goqu - transaction] EXEC [query:=`SELECT * FROM \"items\" WHERE \"id\" = ?` args:=[1]] ", @@ -276,14 +289,15 @@ func (me *txDatabaseTest) TestLogger() { }) } -func (me *txDatabaseTest) TestLogger_FromDb() { - t := me.T() +func (tdt *txDatabaseTest) TestLogger_FromDb() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectExec(`SELECT \* FROM "items" WHERE "id" = ?`). WithArgs(1). @@ -300,7 +314,7 @@ func (me *txDatabaseTest) TestLogger_FromDb() { assert.NoError(t, tx.ScanStructs(&items, `SELECT * FROM "items"`)) _, err = tx.Exec(`SELECT * FROM "items" WHERE "id" = ?`, 1) assert.NoError(t, err) - tx.Commit() + assert.NoError(t, tx.Commit()) assert.Equal(t, logger.Messages, []string{ "[goqu - transaction] QUERY [query:=`SELECT * FROM \"items\"`] ", "[goqu - transaction] EXEC [query:=`SELECT * FROM \"items\" WHERE \"id\" = ?` args:=[1]] ", @@ -308,57 +322,59 @@ func (me *txDatabaseTest) TestLogger_FromDb() { }) } -func (me *txDatabaseTest) TestCommit() { - t := me.T() +func (tdt *txDatabaseTest) TestCommit() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() mock.ExpectCommit() - db := New("mock", mDb) + db := newDatabase("mock", mDb) tx, err := db.Begin() assert.NoError(t, err) assert.NoError(t, tx.Commit()) } -func (me *txDatabaseTest) TestRollback() { - t := me.T() +func (tdt *txDatabaseTest) TestRollback() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() mock.ExpectRollback() - db := New("mock", mDb) + db := newDatabase("mock", mDb) tx, err := db.Begin() assert.NoError(t, err) assert.NoError(t, tx.Rollback()) } -func (me *txDatabaseTest) TestFrom() { - t := me.T() +func (tdt *txDatabaseTest) TestFrom() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() mock.ExpectCommit() - db := New("mock", mDb) + db := newDatabase("mock", mDb) tx, err := db.Begin() assert.NoError(t, err) - assert.NotNil(t, tx.From("test")) + assert.NotNil(t, From("test")) assert.NoError(t, tx.Commit()) } -func (me *txDatabaseTest) TestScanStructs() { - t := me.T() +func (tdt *txDatabaseTest) TestScanStructs() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectQuery(`SELECT "test" FROM "items"`). WithArgs(). WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2")) mock.ExpectCommit() - tx, err := New("db-mock", mDb).Begin() + db := newDatabase("mock", mDb) + tx, err := db.Begin() assert.NoError(t, err) var items []testActionItem assert.NoError(t, tx.ScanStructs(&items, `SELECT * FROM "items"`)) @@ -370,14 +386,17 @@ func (me *txDatabaseTest) TestScanStructs() { assert.Equal(t, items[1].Name, "Test2") items = items[0:0] - assert.EqualError(t, tx.ScanStructs(items, `SELECT * FROM "items"`), "goqu: Type must be a pointer to a slice when calling ScanStructs") - assert.EqualError(t, tx.ScanStructs(&testActionItem{}, `SELECT * FROM "items"`), "goqu: Type must be a pointer to a slice when calling ScanStructs") - assert.EqualError(t, tx.ScanStructs(&items, `SELECT "test" FROM "items"`), `goqu: Unable to find corresponding field to column "test" returned by query`) + assert.EqualError(t, tx.ScanStructs(items, `SELECT * FROM "items"`), + "goqu: type must be a pointer to a slice when scanning into structs") + assert.EqualError(t, tx.ScanStructs(&testActionItem{}, `SELECT * FROM "items"`), + "goqu: type must be a pointer to a slice when scanning into structs") + assert.EqualError(t, tx.ScanStructs(&items, `SELECT "test" FROM "items"`), + `goqu: unable to find corresponding field to column "test" returned by query`) assert.NoError(t, tx.Commit()) } -func (me *txDatabaseTest) TestScanStruct() { - t := me.T() +func (tdt *txDatabaseTest) TestScanStruct() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() @@ -389,7 +408,8 @@ func (me *txDatabaseTest) TestScanStruct() { WithArgs(). WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2")) mock.ExpectCommit() - tx, err := New("mock", mDb).Begin() + db := newDatabase("mock", mDb) + tx, err := db.Begin() assert.NoError(t, err) var item testActionItem found, err := tx.ScanStruct(&item, `SELECT * FROM "items" LIMIT 1`) @@ -399,16 +419,16 @@ func (me *txDatabaseTest) TestScanStruct() { assert.Equal(t, item.Name, "Test1") _, err = tx.ScanStruct(item, `SELECT * FROM "items" LIMIT 1`) - assert.EqualError(t, err, "goqu: Type must be a pointer to a struct when calling ScanStruct") + assert.EqualError(t, err, "goqu: type must be a pointer to a struct when scanning into a struct") _, err = tx.ScanStruct([]testActionItem{}, `SELECT * FROM "items" LIMIT 1`) - assert.EqualError(t, err, "goqu: Type must be a pointer to a struct when calling ScanStruct") + assert.EqualError(t, err, "goqu: type must be a pointer to a struct when scanning into a struct") _, err = tx.ScanStruct(&item, `SELECT "test" FROM "items" LIMIT 1`) - assert.EqualError(t, err, `goqu: Unable to find corresponding field to column "test" returned by query`) + assert.EqualError(t, err, `goqu: unable to find corresponding field to column "test" returned by query`) assert.NoError(t, tx.Commit()) } -func (me *txDatabaseTest) TestScanVals() { - t := me.T() +func (tdt *txDatabaseTest) TestScanVals() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() @@ -416,19 +436,22 @@ func (me *txDatabaseTest) TestScanVals() { WithArgs(). WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("1\n2\n3\n4\n5")) mock.ExpectCommit() - tx, err := New("mock", mDb).Begin() + db := newDatabase("mock", mDb) + tx, err := db.Begin() assert.NoError(t, err) var ids []uint32 assert.NoError(t, tx.ScanVals(&ids, `SELECT "id" FROM "items"`)) assert.Len(t, ids, 5) - assert.EqualError(t, tx.ScanVals([]uint32{}, `SELECT "id" FROM "items"`), "goqu: Type must be a pointer to a slice when calling ScanVals") - assert.EqualError(t, tx.ScanVals(testActionItem{}, `SELECT "id" FROM "items"`), "goqu: Type must be a pointer to a slice when calling ScanVals") + assert.EqualError(t, tx.ScanVals([]uint32{}, `SELECT "id" FROM "items"`), + "goqu: type must be a pointer to a slice when scanning into vals") + assert.EqualError(t, tx.ScanVals(testActionItem{}, `SELECT "id" FROM "items"`), + "goqu: type must be a pointer to a slice when scanning into vals") assert.NoError(t, tx.Commit()) } -func (me *txDatabaseTest) TestScanVal() { - t := me.T() +func (tdt *txDatabaseTest) TestScanVal() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() @@ -436,7 +459,8 @@ func (me *txDatabaseTest) TestScanVal() { WithArgs(). WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("10")) mock.ExpectCommit() - tx, err := New("mock", mDb).Begin() + db := newDatabase("mock", mDb) + tx, err := db.Begin() assert.NoError(t, err) var id int64 found, err := tx.ScanVal(&id, `SELECT "id" FROM "items"`) @@ -445,14 +469,16 @@ func (me *txDatabaseTest) TestScanVal() { assert.True(t, found) found, err = tx.ScanVal([]int64{}, `SELECT "id" FROM "items"`) - assert.EqualError(t, err, "goqu: Type must be a pointer when calling ScanVal") + assert.False(t, found) + assert.EqualError(t, err, "goqu: type must be a pointer when scanning into val") found, err = tx.ScanVal(10, `SELECT "id" FROM "items"`) - assert.EqualError(t, err, "goqu: Type must be a pointer when calling ScanVal") + assert.False(t, found) + assert.EqualError(t, err, "goqu: type must be a pointer when scanning into val") assert.NoError(t, tx.Commit()) } -func (me *txDatabaseTest) TestExec() { - t := me.T() +func (tdt *txDatabaseTest) TestExec() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() @@ -462,9 +488,10 @@ func (me *txDatabaseTest) TestExec() { mock.ExpectExec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE \("name" IS NULL\)`). WithArgs(). - WillReturnError(NewGoquError("mock error")) + WillReturnError(errors.New("mock error")) mock.ExpectCommit() - tx, err := New("mock", mDb).Begin() + db := newDatabase("mock", mDb) + tx, err := db.Begin() assert.NoError(t, err) _, err = tx.Exec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE ("name" IS NULL)`) assert.NoError(t, err) @@ -473,20 +500,22 @@ func (me *txDatabaseTest) TestExec() { assert.NoError(t, tx.Commit()) } -func (me *txDatabaseTest) TestQuery() { - t := me.T() +func (tdt *txDatabaseTest) TestQuery() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnError(NewGoquError("mock error")) + WillReturnError(errors.New("mock error")) mock.ExpectCommit() - tx, err := New("mock", mDb).Begin() + db := newDatabase("mock", mDb) + tx, err := db.Begin() assert.NoError(t, err) _, err = tx.Query(`SELECT * FROM "items"`) assert.NoError(t, err, "goqu - mock error") @@ -496,20 +525,22 @@ func (me *txDatabaseTest) TestQuery() { assert.NoError(t, tx.Commit()) } -func (me *txDatabaseTest) TestQueryRow() { - t := me.T() +func (tdt *txDatabaseTest) TestQueryRow() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnError(NewGoquError("mock error")) + WillReturnError(errors.New("mock error")) mock.ExpectCommit() - tx, err := New("mock", mDb).Begin() + db := newDatabase("mock", mDb) + tx, err := db.Begin() assert.NoError(t, err) rows := tx.QueryRow(`SELECT * FROM "items"`) var address string @@ -521,23 +552,24 @@ func (me *txDatabaseTest) TestQueryRow() { assert.NoError(t, tx.Commit()) } -func (me *txDatabaseTest) TestWrap() { - t := me.T() +func (tdt *txDatabaseTest) TestWrap() { + t := tdt.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectBegin() mock.ExpectCommit() mock.ExpectBegin() mock.ExpectRollback() - tx, err := New("mock", mDb).Begin() + db := newDatabase("mock", mDb) + tx, err := db.Begin() assert.NoError(t, err) assert.NoError(t, tx.Wrap(func() error { return nil })) - tx, err = New("mock", mDb).Begin() + tx, err = db.Begin() assert.NoError(t, err) assert.EqualError(t, tx.Wrap(func() error { - return NewGoquError("tx error") + return errors.New("tx error") }), "goqu: tx error") } diff --git a/dataset.go b/dataset.go index 3b01b10a..008fb434 100644 --- a/dataset.go +++ b/dataset.go @@ -1,326 +1,770 @@ package goqu import ( - "database/sql/driver" + "context" "fmt" - "reflect" - "sort" - "strings" - "time" + + "github.com/doug-martin/goqu/v7/exec" + "github.com/doug-martin/goqu/v7/exp" + "github.com/doug-martin/goqu/v7/internal/errors" + "github.com/doug-martin/goqu/v7/internal/sb" ) type ( - countResult struct { - Count int64 `db:"count"` - } - valueSlice []reflect.Value - Logger interface { - Printf(format string, v ...interface{}) - } - clauses struct { - CommonTables []CommonTableExpression - Select ColumnList - SelectDistinct ColumnList - From ColumnList - Joins JoiningClauses - Where ExpressionList - Alias IdentifierExpression - GroupBy ColumnList - Having ExpressionList - Order ColumnList - Limit interface{} - Offset uint - Returning ColumnList - Compounds []CompoundExpression - Lock Lock - } - //A Dataset is used to build up an SQL statement, each method returns a copy of the current Dataset with options added to it. - //Once done building up your Dataset you can either call an action method on it to execute the statement or use one of the SQL generation methods. + // A Dataset is used to build up an SQL statement, each method returns a copy of the current Dataset with options + // added to it. // - //Common SQL clauses are represented as methods on the Dataset (e.g. Where, From, Select, Limit...) - // * Sql() - Returns a SELECT statement - // * UpdateSql() - Returns an UPDATE statement - // * InsertSql() - Returns an INSERT statement - // * DeleteSql() - Returns a DELETE statement - // * TruncateSql() - Returns a TRUNCATE statement. + // Once done building up your Dataset you can either call an action method on it to execute the statement or use + // one of the SQL generation methods. // - //Each SQL generation method returns an interpolated statement. Without interpolation each SQL statement could cause two calls to the database: + // Common SQL clauses are represented as methods on the Dataset (e.g. Where, From, Select, Limit...) + // * ToSQL() - Returns a SELECT statement + // * ToUpdateSQL() - Returns an UPDATE statement + // * ToInsertSQL(rows ...interface{}) - Returns an INSERT statement + // * ToDeleteSQL() - Returns a DELETE statement + // * ToTruncateSQL() - Returns a TRUNCATE statement. + // + // Each SQL generation method returns an interpolated statement. Without interpolation each SQL statement could + // cause two calls to the database: // 1. Prepare the statement - // 2. Execute the statment with arguments - //Instead with interpolation the database just executes the statement - // sql, err := From("test").Where(I("a").Eq(10).Sql() //SELECT * FROM "test" WHERE "a" = 10 + // 2. Execute the statement with arguments + // + // Instead with interpolation the database just executes the statement + // sql, _, err := goqu.From("test").Where(goqu.C("a").Eq(10)).ToSQL() + // fmt.Println(sql) + // + // // Output: + // // SELECT * FROM "test" WHERE "a" = 10 // - //Sometimes you might want to generated a prepared statement in which case you would use one of the "To" SQL generation methods, with the isPrepared argument set to true. - // * ToSql(true) - generates a SELECT statement without the arguments interpolated - // * ToUpdateSql(true, update) - generates an UPDATE statement without the arguments interpolated - // * ToInsertSql(true, rows....) - generates an INSERT statement without the arguments interpolated - // * ToDeleteSql(true) - generates a DELETE statement without arguments interpolated - // * ToTruncateSql(true, opts) - generates a TRUNCATE statement without arguments interpolated + // Sometimes you might want to generated a prepared statement in which case you would use one of the "Prepared" + // method on the dataset + // sql, args, err := From("test").Prepared(true).Where(I("a").Eq(10)).ToSQL() + // fmt.Println(sql, args) // - // sql, args, err := From("test").Where(I("a").Eq(10).ToSql(true) //sql := SELECT * FROM "test" WHERE "a" = ? args:=[]interface{}{10} + // // Output: + // // SELECT * FROM "test" WHERE "a" = ? [10] // - //A Dataset can also execute statements directly. By calling: + // A Dataset can also execute statements directly. By calling: // // * ScanStructs(i interface{}) - Scans returned rows into a slice of structs - // * ScanStruct(i interface{}) - Scans a single rom into a struct, if no struct is found this method will return false + // * ScanStruct(i interface{}) - Scans a single rom into a struct, if no struct is found this method will return + // false // * ScanVals(i interface{}) - Scans rows of one columns into a slice of primitive values // * ScanVal(i interface{}) - Scans a single row of one column into a primitive value // * Count() - Returns a count of rows - // * Pluck(i interface{}, col string) - Retrives a columns from rows and scans the resules into a slice of primitive values. + // * Pluck(i interface{}, col string) - Retrives a columns from rows and scans the resules into a slice of + // primitive values. // - //Update, Delete, and Insert return an CrudExec struct which can be used to scan values or just execute the statment. You might - //use the scan methods if the database supports return values. For example + // Update, Delete, and Insert return an CrudExec struct which can be used to scan values or just execute the + // statement. You might + // use the scan methods if the database supports return values. For example // UPDATE "items" SET updated = NOW RETURNING "items".* - //Could be executed with ScanStructs. + // Could be executed with ScanStructs. Dataset struct { - adapter Adapter - clauses clauses - database database - isPrepared bool + dialect SQLDialect + clauses exp.Clauses + isPrepared bool + queryFactory exec.QueryFactory } ) -func (me valueSlice) Len() int { return len(me) } -func (me valueSlice) Less(i, j int) bool { return me[i].String() < me[j].String() } -func (me valueSlice) Swap(i, j int) { me[i], me[j] = me[j], me[i] } +var ( + errQueryFactoryNotFoundError = errors.New( + "unable to execute query did you use goqu.Database#From to create the dataset", + ) +) -func (me valueSlice) Equal(other valueSlice) bool { - sort.Sort(other) - for i, key := range me { - if other[i].String() != key.String() { - return false - } +// used internally by database to create a database with a specific adapter +func newDataset(d string, queryFactory exec.QueryFactory) *Dataset { + return &Dataset{ + clauses: exp.NewClauses(), + dialect: GetDialect(d), + queryFactory: queryFactory, } - return true } -func (me valueSlice) String() string { - vals := make([]string, me.Len()) - for i, key := range me { - vals[i] = fmt.Sprintf(`"%s"`, key.String()) - } - sort.Strings(vals) - return fmt.Sprintf("[%s]", strings.Join(vals, ",")) +func From(table ...interface{}) *Dataset { + return newDataset("default", nil).From(table...) } -//Returns a dataset with the DefaultAdapter. Typically you would use Database#From. -func From(table ...interface{}) *Dataset { - ret := new(Dataset) - ret.adapter = NewDefaultAdapter(ret) - ret.clauses = clauses{ - Select: cols(Star()), +// Sets the adapter used to serialize values and create the SQL statement +func (d *Dataset) WithDialect(dl string) *Dataset { + ds := d.copy(d.GetClauses()) + ds.dialect = GetDialect(dl) + return ds +} + +// Set the parameter interpolation behavior. See examples +// +// prepared: If true the dataset WILL NOT interpolate the parameters. +func (d *Dataset) Prepared(prepared bool) *Dataset { + ret := d.copy(d.clauses) + ret.isPrepared = prepared + return ret +} + +func (d *Dataset) IsPrepared() bool { + return d.isPrepared +} + +// Returns the current adapter on the dataset +func (d *Dataset) Dialect() SQLDialect { + return d.dialect +} + +// Returns the current adapter on the dataset +func (d *Dataset) SetDialect(dialect SQLDialect) *Dataset { + cd := d.copy(d.GetClauses()) + cd.dialect = dialect + return cd +} + +func (d *Dataset) Expression() exp.Expression { + return d +} + +// Clones the dataset +func (d *Dataset) Clone() exp.Expression { + return d.copy(d.clauses) +} + +// Returns the current clauses on the dataset. +func (d *Dataset) GetClauses() exp.Clauses { + return d.clauses +} + +// used interally to copy the dataset +func (d *Dataset) copy(clauses exp.Clauses) *Dataset { + return &Dataset{ + dialect: d.dialect, + clauses: clauses, + isPrepared: d.isPrepared, + queryFactory: d.queryFactory, } - return ret.From(table...) } -//Creates a WITH clause for a common table expression (CTE). +// Creates a WITH clause for a common table expression (CTE). // -//The name will be available to SELECT from in the associated query; and can optionally -//contain a list of column names "name(col1, col2, col3)". +// The name will be available to SELECT from in the associated query; and can optionally +// contain a list of column names "name(col1, col2, col3)". // -//The name will refer to the results of the specified subquery. -func (me *Dataset) With(name string, subquery *Dataset) *Dataset { - ret := me.copy() - ret.clauses.CommonTables = append(ret.clauses.CommonTables, With(false, name, subquery)) - return ret +// The name will refer to the results of the specified subquery. +func (d *Dataset) With(name string, subquery exp.Expression) *Dataset { + return d.copy(d.clauses.CommonTablesAppend(exp.NewCommonTableExpression(false, name, subquery))) } -//Creates a WITH RECURSIVE clause for a common table expression (CTE) +// Creates a WITH RECURSIVE clause for a common table expression (CTE) // -//The name will be available to SELECT from in the associated query; and must -//contain a list of column names "name(col1, col2, col3)" for a recursive clause. +// The name will be available to SELECT from in the associated query; and must +// contain a list of column names "name(col1, col2, col3)" for a recursive clause. // -//The name will refer to the results of the specified subquery. The subquery for -//a recursive query will always end with a UNION or UNION ALL with a clause that -//refers to the CTE by name. -func (me *Dataset) WithRecursive(name string, subquery *Dataset) *Dataset { - ret := me.copy() - ret.clauses.CommonTables = append(ret.clauses.CommonTables, With(true, name, subquery)) - return ret +// The name will refer to the results of the specified subquery. The subquery for +// a recursive query will always end with a UNION or UNION ALL with a clause that +// refers to the CTE by name. +func (d *Dataset) WithRecursive(name string, subquery exp.Expression) *Dataset { + return d.copy(d.clauses.CommonTablesAppend(exp.NewCommonTableExpression(true, name, subquery))) +} + +// Adds columns to the SELECT clause. See examples +// You can pass in the following. +// string: Will automatically be turned into an identifier +// Dataset: Will use the SQL generated from that Dataset. If the dataset is aliased it will use that alias as the +// column name. +// LiteralExpression: (See Literal) Will use the literal SQL +// SQLFunction: (See Func, MIN, MAX, COUNT....) +// Struct: If passing in an instance of a struct, we will parse the struct for the column names to select. +// See examples +func (d *Dataset) Select(selects ...interface{}) *Dataset { + return d.copy(d.clauses.SetSelect(exp.NewColumnListExpression(selects...))) +} + +// Adds columns to the SELECT DISTINCT clause. See examples +// You can pass in the following. +// string: Will automatically be turned into an identifier +// Dataset: Will use the SQL generated from that Dataset. If the dataset is aliased it will use that alias as the +// column name. +// LiteralExpression: (See Literal) Will use the literal SQL +// SQLFunction: (See Func, MIN, MAX, COUNT....) +// Struct: If passing in an instance of a struct, we will parse the struct for the column names to select. +// See examples +func (d *Dataset) SelectDistinct(selects ...interface{}) *Dataset { + return d.copy(d.clauses.SetSelectDistinct(exp.NewColumnListExpression(selects...))) +} + +// Resets to SELECT *. If the SelectDistinct was used the returned Dataset will have the the dataset set to SELECT *. +// See examples. +func (d *Dataset) ClearSelect() *Dataset { + return d.copy(d.clauses.SetSelect(exp.NewColumnListExpression(exp.Star()))) +} + +// Adds columns to the SELECT clause. See examples +// You can pass in the following. +// string: Will automatically be turned into an identifier +// Dataset: Will use the SQL generated from that Dataset. If the dataset is aliased it will use that alias as the +// column name. +// LiteralExpression: (See Literal) Will use the literal SQL +// SQLFunction: (See Func, MIN, MAX, COUNT....) +func (d *Dataset) SelectAppend(selects ...interface{}) *Dataset { + return d.copy(d.clauses.SelectAppend(exp.NewColumnListExpression(selects...))) +} + +// Adds a FROM clause. This return a new dataset with the original sources replaced. See examples. +// You can pass in the following. +// string: Will automatically be turned into an identifier +// Dataset: Will be added as a sub select. If the Dataset is not aliased it will automatically be aliased +// LiteralExpression: (See Literal) Will use the literal SQL +func (d *Dataset) From(from ...interface{}) *Dataset { + var sources []interface{} + numSources := 0 + for _, source := range from { + if sd, ok := source.(*Dataset); ok && !sd.clauses.HasAlias() { + numSources++ + sources = append(sources, sd.As(fmt.Sprintf("t%d", numSources))) + } else { + sources = append(sources, source) + } + } + return d.copy(d.clauses.SetFrom(exp.NewColumnListExpression(sources...))) +} + +// Returns a new Dataset with the current one as an source. If the current Dataset is not aliased (See Dataset#As) then +// it will automatically be aliased. See examples. +func (d *Dataset) FromSelf() *Dataset { + builder := Dataset{ + dialect: d.dialect, + clauses: exp.NewClauses(), + } + return builder.From(d) + +} + +// Alias to InnerJoin. See examples. +func (d *Dataset) Join(table exp.Expression, condition exp.JoinCondition) *Dataset { + return d.InnerJoin(table, condition) +} + +// Adds an INNER JOIN clause. See examples. +func (d *Dataset) InnerJoin(table exp.Expression, condition exp.JoinCondition) *Dataset { + return d.joinTable(exp.NewConditionedJoinExpression(exp.InnerJoinType, table, condition)) +} + +// Adds a FULL OUTER JOIN clause. See examples. +func (d *Dataset) FullOuterJoin(table exp.Expression, condition exp.JoinCondition) *Dataset { + return d.joinTable(exp.NewConditionedJoinExpression(exp.FullOuterJoinType, table, condition)) +} + +// Adds a RIGHT OUTER JOIN clause. See examples. +func (d *Dataset) RightOuterJoin(table exp.Expression, condition exp.JoinCondition) *Dataset { + return d.joinTable(exp.NewConditionedJoinExpression(exp.RightOuterJoinType, table, condition)) +} + +// Adds a LEFT OUTER JOIN clause. See examples. +func (d *Dataset) LeftOuterJoin(table exp.Expression, condition exp.JoinCondition) *Dataset { + return d.joinTable(exp.NewConditionedJoinExpression(exp.LeftOuterJoinType, table, condition)) +} + +// Adds a FULL JOIN clause. See examples. +func (d *Dataset) FullJoin(table exp.Expression, condition exp.JoinCondition) *Dataset { + return d.joinTable(exp.NewConditionedJoinExpression(exp.FullJoinType, table, condition)) +} + +// Adds a RIGHT JOIN clause. See examples. +func (d *Dataset) RightJoin(table exp.Expression, condition exp.JoinCondition) *Dataset { + return d.joinTable(exp.NewConditionedJoinExpression(exp.RightJoinType, table, condition)) +} + +// Adds a LEFT JOIN clause. See examples. +func (d *Dataset) LeftJoin(table exp.Expression, condition exp.JoinCondition) *Dataset { + return d.joinTable(exp.NewConditionedJoinExpression(exp.LeftJoinType, table, condition)) +} + +// Adds a NATURAL JOIN clause. See examples. +func (d *Dataset) NaturalJoin(table exp.Expression) *Dataset { + return d.joinTable(exp.NewUnConditionedJoinExpression(exp.NaturalJoinType, table)) +} + +// Adds a NATURAL LEFT JOIN clause. See examples. +func (d *Dataset) NaturalLeftJoin(table exp.Expression) *Dataset { + return d.joinTable(exp.NewUnConditionedJoinExpression(exp.NaturalLeftJoinType, table)) +} + +// Adds a NATURAL RIGHT JOIN clause. See examples. +func (d *Dataset) NaturalRightJoin(table exp.Expression) *Dataset { + return d.joinTable(exp.NewUnConditionedJoinExpression(exp.NaturalRightJoinType, table)) +} + +// Adds a NATURAL FULL JOIN clause. See examples. +func (d *Dataset) NaturalFullJoin(table exp.Expression) *Dataset { + return d.joinTable(exp.NewUnConditionedJoinExpression(exp.NaturalFullJoinType, table)) +} + +// Adds a CROSS JOIN clause. See examples. +func (d *Dataset) CrossJoin(table exp.Expression) *Dataset { + return d.joinTable(exp.NewUnConditionedJoinExpression(exp.CrossJoinType, table)) +} + +// Joins this Datasets table with another +func (d *Dataset) joinTable(join exp.JoinExpression) *Dataset { + return d.copy(d.clauses.JoinsAppend(join)) +} + +// Adds a WHERE clause. See examples. +func (d *Dataset) Where(expressions ...exp.Expression) *Dataset { + return d.copy(d.clauses.WhereAppend(expressions...)) +} + +// Removes the WHERE clause. See examples. +func (d *Dataset) ClearWhere() *Dataset { + return d.copy(d.clauses.ClearWhere()) +} + +// Adds a FOR UPDATE clause. See examples. +func (d *Dataset) ForUpdate(waitOption exp.WaitOption) *Dataset { + return d.withLock(exp.ForUpdate, waitOption) +} + +// Adds a FOR NO KEY UPDATE clause. See examples. +func (d *Dataset) ForNoKeyUpdate(waitOption exp.WaitOption) *Dataset { + return d.withLock(exp.ForNoKeyUpdate, waitOption) +} + +// Adds a FOR KEY SHARE clause. See examples. +func (d *Dataset) ForKeyShare(waitOption exp.WaitOption) *Dataset { + return d.withLock(exp.ForKeyShare, waitOption) +} + +// Adds a FOR SHARE clause. See examples. +func (d *Dataset) ForShare(waitOption exp.WaitOption) *Dataset { + return d.withLock(exp.ForShare, waitOption) +} + +func (d *Dataset) withLock(strength exp.LockStrength, option exp.WaitOption) *Dataset { + return d.copy(d.clauses.SetLock(exp.NewLock(strength, option))) +} + +// Adds a GROUP BY clause. See examples. +func (d *Dataset) GroupBy(groupBy ...interface{}) *Dataset { + return d.copy(d.clauses.SetGroupBy(exp.NewColumnListExpression(groupBy...))) +} + +// Adds a HAVING clause. See examples. +func (d *Dataset) Having(expressions ...exp.Expression) *Dataset { + return d.copy(d.clauses.HavingAppend(expressions...)) +} + +// Adds a ORDER clause. If the ORDER is currently set it replaces it. See examples. +func (d *Dataset) Order(order ...exp.OrderedExpression) *Dataset { + return d.copy(d.clauses.SetOrder(order...)) +} + +// Adds a more columns to the current ORDER BY clause. If no order has be previously specified it is the same as +// calling Order. See examples. +func (d *Dataset) OrderAppend(order ...exp.OrderedExpression) *Dataset { + return d.copy(d.clauses.OrderAppend(order...)) +} + +// Removes the ORDER BY clause. See examples. +func (d *Dataset) ClearOrder() *Dataset { + return d.copy(d.clauses.ClearOrder()) } -//used internally by database to create a database with a specific adapter -func withDatabase(db database) *Dataset { - ret := new(Dataset) - ret.database = db - ret.clauses = clauses{ - Select: cols(Star()), +// Adds a LIMIT clause. If the LIMIT is currently set it replaces it. See examples. +func (d *Dataset) Limit(limit uint) *Dataset { + if limit > 0 { + return d.copy(d.clauses.SetLimit(limit)) } - ret.adapter = db.queryAdapter(ret) + return d.copy(d.clauses.ClearLimit()) +} + +// Adds a LIMIT ALL clause. If the LIMIT is currently set it replaces it. See examples. +func (d *Dataset) LimitAll() *Dataset { + return d.copy(d.clauses.SetLimit(L("ALL"))) +} + +// Removes the LIMIT clause. +func (d *Dataset) ClearLimit() *Dataset { + return d.copy(d.clauses.ClearLimit()) +} + +// Adds an OFFSET clause. If the OFFSET is currently set it replaces it. See examples. +func (d *Dataset) Offset(offset uint) *Dataset { + return d.copy(d.clauses.SetOffset(offset)) +} + +// Removes the OFFSET clause from the Dataset +func (d *Dataset) ClearOffset() *Dataset { + return d.copy(d.clauses.ClearOffset()) +} + +// Creates an UNION statement with another dataset. +// If this or the other dataset has a limit or offset it will use that dataset as a subselect in the FROM clause. +// See examples. +func (d *Dataset) Union(other *Dataset) *Dataset { + return d.withCompound(exp.UnionCompoundType, other.CompoundFromSelf()) +} + +// Creates an UNION ALL statement with another dataset. +// If this or the other dataset has a limit or offset it will use that dataset as a subselect in the FROM clause. +// See examples. +func (d *Dataset) UnionAll(other *Dataset) *Dataset { + return d.withCompound(exp.UnionAllCompoundType, other.CompoundFromSelf()) +} + +// Creates an INTERSECT statement with another dataset. +// If this or the other dataset has a limit or offset it will use that dataset as a subselect in the FROM clause. +// See examples. +func (d *Dataset) Intersect(other *Dataset) *Dataset { + return d.withCompound(exp.IntersectCompoundType, other.CompoundFromSelf()) +} + +// Creates an INTERSECT ALL statement with another dataset. +// If this or the other dataset has a limit or offset it will use that dataset as a subselect in the FROM clause. +// See examples. +func (d *Dataset) IntersectAll(other *Dataset) *Dataset { + return d.withCompound(exp.IntersectAllCompoundType, other.CompoundFromSelf()) +} + +func (d *Dataset) withCompound(ct exp.CompoundType, other Expression) *Dataset { + ce := exp.NewCompoundExpression(ct, other) + ret := d.CompoundFromSelf() + ret.clauses = ret.clauses.CompoundsAppend(ce) return ret } -//Sets the adapter used to serialize values and create the SQL statement -func (me *Dataset) SetAdapter(adapter Adapter) *Dataset { - me.adapter = adapter - return me +// Used internally to determine if the dataset needs to use iteself as a source. +// If the dataset has an order or limit it will select from itself +func (d *Dataset) CompoundFromSelf() *Dataset { + if d.clauses.HasOrder() || d.clauses.HasLimit() { + return d.FromSelf() + } + return d.copy(d.clauses) } -//Set the parameter interpolation behavior. See examples +// Adds a RETURNING clause to the dataset if the adapter supports it. Typically used for INSERT, UPDATE or DELETE. +// See examples. +func (d *Dataset) Returning(returning ...interface{}) *Dataset { + return d.copy(d.clauses.SetReturning(exp.NewColumnListExpression(returning...))) +} + +// Sets the alias for this dataset. This is typically used when using a Dataset as a subselect. See examples. +func (d *Dataset) As(alias string) *Dataset { + return d.copy(d.clauses.SetAlias(T(alias))) +} + +// Generates a SELECT sql statement, if Prepared has been called with true then the parameters will not be interpolated. +// See examples. // -//prepared: If true the dataset WILL NOT interpolate the parameters. -func (me *Dataset) Prepared(prepared bool) *Dataset { - ret := me.copy() - ret.isPrepared = prepared - return ret +// Errors: +// * There is an error generating the SQL +func (d *Dataset) ToSQL() (sql string, params []interface{}, err error) { + return d.selectSQLBuilder().ToSQL() } -//Returns the current adapter on the dataset -func (me *Dataset) Adapter() Adapter { - return me.adapter +// Appends this Dataset's SELECT statement to the SQLBuilder +// This is used internally for sub-selects by the dialect +func (d *Dataset) AppendSQL(b sb.SQLBuilder) { + d.dialect.ToSelectSQL(b, d.GetClauses()) } -func (me *Dataset) Expression() Expression { - return me +// Generates the default INSERT statement. If Prepared has been called with true then the statement will not be +// interpolated. See examples. When using structs you may specify a column to be skipped in the insert, (e.g. id) by +// specifying a goqu tag with `skipinsert` +// type Item struct{ +// Id uint32 `db:"id" goqu:"skipinsert"` +// Name string `db:"name"` +// } +// +// rows: variable number arguments of either map[string]interface, Record, struct, or a single slice argument of the +// accepted types. +// +// Errors: +// * There is no FROM clause +// * Different row types passed in, all rows must be of the same type +// * Maps with different numbers of K/V pairs +// * Rows of different lengths, (i.e. (Record{"name": "a"}, Record{"name": "a", "age": 10}) +// * Error generating SQL +func (d *Dataset) ToInsertSQL(rows ...interface{}) (sql string, params []interface{}, err error) { + return d.toInsertSQL(nil, rows...) } -//Clones the dataset -func (me *Dataset) Clone() Expression { - return me.copy() +// Generates the default INSERT IGNORE/ INSERT ... ON CONFLICT DO NOTHING statement. If Prepared has been called with +// true then the statement will not be interpolated. See examples. +// +// c: ConflictExpression action. Can be DoNothing/Ignore or DoUpdate/DoUpdateWhere. +// rows: variable number arguments of either map[string]interface, Record, struct, or a single slice argument of the +// accepted types. +// +// Errors: +// * There is no FROM clause +// * Different row types passed in, all rows must be of the same type +// * Maps with different numbers of K/V pairs +// * Rows of different lengths, (i.e. (Record{"name": "a"}, Record{"name": "a", "age": 10}) +// * Error generating SQL +func (d *Dataset) ToInsertIgnoreSQL(rows ...interface{}) (sql string, params []interface{}, err error) { + return d.toInsertSQL(DoNothing(), rows...) } -//Returns the current clauses on the dataset. -func (me *Dataset) GetClauses() clauses { - return me.clauses +// Generates the INSERT [IGNORE] ... ON CONFLICT/DUPLICATE KEY. If Prepared has been called with true then the statement +// will not be interpolated. See examples. +// +// rows: variable number arguments of either map[string]interface, Record, struct, or a single slice argument of the +// accepted types. +// +// Errors: +// * There is no FROM clause +// * Different row types passed in, all rows must be of the same type +// * Maps with different numbers of K/V pairs +// * Rows of different lengths, (i.e. (Record{"name": "a"}, Record{"name": "a", "age": 10}) +// * Error generating SQL +func (d *Dataset) ToInsertConflictSQL(o exp.ConflictExpression, rows ...interface{}) (sql string, params []interface{}, err error) { + return d.toInsertSQL(o, rows) } -//used interally to copy the dataset -func (me Dataset) copy() *Dataset { - return &me +// Generates an UPDATE statement. If `Prepared` has been called with true then the statement will not be interpolated. +// When using structs you may specify a column to be skipped in the update, (e.g. created) by specifying a goqu tag with `skipupdate` +// type Item struct{ +// Id uint32 `db:"id" +// Created time.Time `db:"created" goqu:"skipupdate"` +// Name string `db:"name"` +// } +// +// update: can either be a a map[string]interface{}, Record or a struct +// +// Errors: +// * The update is not a of type struct, Record, or map[string]interface{} +// * The update statement has no FROM clause +// * There is an error generating the SQL +func (d *Dataset) ToUpdateSQL(update interface{}) (sql string, params []interface{}, err error) { + return d.updateSQLBuilder(update).ToSQL() } -//Returns true if the dataset has a FROM clause -func (me *Dataset) hasSources() bool { - return me.clauses.From != nil && len(me.clauses.From.Columns()) > 0 +// Generates a DELETE statement, if Prepared has been called with true then the statement will not be interpolated. See examples. +// +// isPrepared: Set to true to true to ensure values are NOT interpolated +// +// Errors: +// * There is no FROM clause +// * Error generating SQL +func (d *Dataset) ToDeleteSQL() (sql string, params []interface{}, err error) { + return d.deleteSQLBuilder().ToSQL() } -//This method is used to serialize: -// * Primitive Values (e.g. float64, int64, string, bool, time.Time, or nil) -// * Expressions +// Generates the default TRUNCATE statement. See examples. // -//buf: The SqlBuilder to write the generated SQL to +// Errors: +// * There is no FROM clause +// * Error generating SQL +func (d *Dataset) ToTruncateSQL() (sql string, params []interface{}, err error) { + return d.ToTruncateWithOptsSQL(exp.TruncateOptions{}) +} + +// Generates the default TRUNCATE statement with the specified options. See examples. // -//val: The value to serialize +// opts: Options to use when generating the TRUNCATE statement // -//Errors: -// * If there is an error generating the SQL -func (me *Dataset) Literal(buf *SqlBuilder, val interface{}) error { - if val == nil { - return me.adapter.LiteralNil(buf) +// Errors: +// * There is no FROM clause +// * Error generating SQL +func (d *Dataset) ToTruncateWithOptsSQL(opts exp.TruncateOptions) (sql string, params []interface{}, err error) { + return d.truncateSQLBuilder(opts).ToSQL() +} + +// Generates the SELECT sql for this dataset and uses Exec#ScanStructs to scan the results into a slice of structs. +// +// ScanStructs will only select the columns that can be scanned in to the struct unless you have explicitly selected +// certain columns. See examples. +// +// i: A pointer to a slice of structs +func (d *Dataset) ScanStructs(i interface{}) error { + return d.ScanStructsContext(context.Background(), i) +} + +// Generates the SELECT sql for this dataset and uses Exec#ScanStructsContext to scan the results into a slice of +// structs. +// +// ScanStructsContext will only select the columns that can be scanned in to the struct unless you have explicitly +// selected certain columns. See examples. +// +// i: A pointer to a slice of structs +func (d *Dataset) ScanStructsContext(ctx context.Context, i interface{}) error { + if d.queryFactory == nil { + return errQueryFactoryNotFoundError } - if v, ok := val.(Expression); ok { - return me.expressionSql(buf, v) - } else if v, ok := val.(int); ok { - return me.adapter.LiteralInt(buf, int64(v)) - } else if v, ok := val.(int32); ok { - return me.adapter.LiteralInt(buf, int64(v)) - } else if v, ok := val.(int64); ok { - return me.adapter.LiteralInt(buf, v) - } else if v, ok := val.(float32); ok { - return me.adapter.LiteralFloat(buf, float64(v)) - } else if v, ok := val.(float64); ok { - return me.adapter.LiteralFloat(buf, v) - } else if v, ok := val.(string); ok { - return me.adapter.LiteralString(buf, v) - } else if v, ok := val.([]byte); ok { - return me.adapter.LiteralBytes(buf, v) - } else if v, ok := val.(bool); ok { - return me.adapter.LiteralBool(buf, v) - } else if v, ok := val.(time.Time); ok { - return me.adapter.LiteralTime(buf, v) - } else if v, ok := val.(*time.Time); ok { - if v == nil { - return me.adapter.LiteralNil(buf) - } - return me.adapter.LiteralTime(buf, *v) - } else if v, ok := val.(driver.Valuer); ok { - dVal, err := v.Value() - if err != nil { - return NewGoquError(err.Error()) - } - return me.Literal(buf, dVal) + ds := d + if d.GetClauses().IsDefaultSelect() { + ds = d.Select(i) } - return me.reflectSql(buf, val) + return d.queryFactory.FromSQLBuilder(ds.selectSQLBuilder()).ScanStructsContext(ctx, i) } -func (me *Dataset) isUint(k reflect.Kind) bool { - return (k == reflect.Uint) || - (k == reflect.Uint8) || - (k == reflect.Uint16) || - (k == reflect.Uint32) || - (k == reflect.Uint64) +// Generates the SELECT sql for this dataset and uses Exec#ScanStruct to scan the result into a slice of structs +// +// ScanStruct will only select the columns that can be scanned in to the struct unless you have explicitly selected +// certain columns. See examples. +// +// i: A pointer to a structs +func (d *Dataset) ScanStruct(i interface{}) (bool, error) { + return d.ScanStructContext(context.Background(), i) } -func (me *Dataset) isInt(k reflect.Kind) bool { - return (k == reflect.Int) || - (k == reflect.Int8) || - (k == reflect.Int16) || - (k == reflect.Int32) || - (k == reflect.Int64) +// Generates the SELECT sql for this dataset and uses Exec#ScanStructContext to scan the result into a slice of structs +// +// ScanStructContext will only select the columns that can be scanned in to the struct unless you have explicitly +// selected certain columns. See examples. +// +// i: A pointer to a structs +func (d *Dataset) ScanStructContext(ctx context.Context, i interface{}) (bool, error) { + if d.queryFactory == nil { + return false, errQueryFactoryNotFoundError + } + ds := d + if d.GetClauses().IsDefaultSelect() { + ds = d.Select(i) + } + return d.queryFactory.FromSQLBuilder(ds.Limit(1).selectSQLBuilder()).ScanStructContext(ctx, i) } -func (me *Dataset) isFloat(k reflect.Kind) bool { - return (k == reflect.Float32) || - (k == reflect.Float64) +// Generates the SELECT sql for this dataset and uses Exec#ScanVals to scan the results into a slice of primitive values +// +// i: A pointer to a slice of primitive values +func (d *Dataset) ScanVals(i interface{}) error { + return d.ScanValsContext(context.Background(), i) } -func (me *Dataset) reflectSql(buf *SqlBuilder, val interface{}) error { - v := reflect.Indirect(reflect.ValueOf(val)) - valKind := v.Kind() - if valKind == reflect.Invalid { - return me.adapter.LiteralNil(buf) - } else if valKind == reflect.Slice { - if b, ok := val.([]byte); ok { - return me.Literal(buf, b) - } - return me.adapter.SliceValueSql(buf, v) - } else if me.isInt(valKind) { - return me.Literal(buf, v.Int()) - } else if me.isUint(valKind) { - return me.Literal(buf, int64(v.Uint())) - } else if me.isFloat(valKind) { - return me.Literal(buf, v.Float()) - } else if valKind == reflect.String { - return me.Literal(buf, v.String()) - } else if valKind == reflect.Bool { - return me.Literal(buf, v.Bool()) +// Generates the SELECT sql for this dataset and uses Exec#ScanValsContext to scan the results into a slice of primitive +// values +// +// i: A pointer to a slice of primitive values +func (d *Dataset) ScanValsContext(ctx context.Context, i interface{}) error { + if d.queryFactory == nil { + return errQueryFactoryNotFoundError } - return newEncodeError(fmt.Sprintf("Unable to encode value %+v", val)) -} - -func (me *Dataset) expressionSql(buf *SqlBuilder, expression Expression) error { - if e, ok := expression.(ColumnList); ok { - return me.adapter.ColumnListSql(buf, e) - } else if e, ok := expression.(ExpressionList); ok { - return me.adapter.ExpressionListSql(buf, e) - } else if e, ok := expression.(LiteralExpression); ok { - return me.adapter.LiteralExpressionSql(buf, e) - } else if e, ok := expression.(IdentifierExpression); ok { - return me.adapter.QuoteIdentifier(buf, e) - } else if e, ok := expression.(AliasedExpression); ok { - return me.adapter.AliasedExpressionSql(buf, e) - } else if e, ok := expression.(BooleanExpression); ok { - return me.adapter.BooleanExpressionSql(buf, e) - } else if e, ok := expression.(RangeExpression); ok { - return me.adapter.RangeExpressionSql(buf, e) - } else if e, ok := expression.(OrderedExpression); ok { - return me.adapter.OrderedExpressionSql(buf, e) - } else if e, ok := expression.(UpdateExpression); ok { - return me.adapter.UpdateExpressionSql(buf, e) - } else if e, ok := expression.(SqlFunctionExpression); ok { - return me.adapter.SqlFunctionExpressionSql(buf, e) - } else if e, ok := expression.(CastExpression); ok { - return me.adapter.CastExpressionSql(buf, e) - } else if e, ok := expression.(*Dataset); ok { - return me.adapter.DatasetSql(buf, *e) - } else if e, ok := expression.(CommonTableExpression); ok { - return me.adapter.CommonTableExpressionSql(buf, e) - } else if e, ok := expression.(CompoundExpression); ok { - return me.adapter.CompoundExpressionSql(buf, e) - } else if e, ok := expression.(Ex); ok { - return me.adapter.ExpressionMapSql(buf, e) - } else if e, ok := expression.(ExOr); ok { - return me.adapter.ExpressionOrMapSql(buf, e) + return d.queryFactory.FromSQLBuilder(d.selectSQLBuilder()).ScanValsContext(ctx, i) +} + +// Generates the SELECT sql for this dataset and uses Exec#ScanVal to scan the result into a primitive value +// +// i: A pointer to a primitive value +func (d *Dataset) ScanVal(i interface{}) (bool, error) { + return d.ScanValContext(context.Background(), i) +} + +// Generates the SELECT sql for this dataset and uses Exec#ScanValContext to scan the result into a primitive value +// +// i: A pointer to a primitive value +func (d *Dataset) ScanValContext(ctx context.Context, i interface{}) (bool, error) { + if d.queryFactory == nil { + return false, errQueryFactoryNotFoundError + } + b := d.Limit(1).selectSQLBuilder() + return d.queryFactory.FromSQLBuilder(b).ScanValContext(ctx, i) +} + +// Generates the SELECT COUNT(*) sql for this dataset and uses Exec#ScanVal to scan the result into an int64. +func (d *Dataset) Count() (int64, error) { + return d.CountContext(context.Background()) +} + +// Generates the SELECT COUNT(*) sql for this dataset and uses Exec#ScanValContext to scan the result into an int64. +func (d *Dataset) CountContext(ctx context.Context) (int64, error) { + var count int64 + _, err := d.Select(COUNT(Star()).As("count")).ScanValContext(ctx, &count) + return count, err +} + +// Generates the SELECT sql only selecting the passed in column and uses Exec#ScanVals to scan the result into a slice +// of primitive values. +// +// i: A slice of primitive values +// +// col: The column to select when generative the SQL +func (d *Dataset) Pluck(i interface{}, col string) error { + return d.PluckContext(context.Background(), i, col) +} + +// Generates the SELECT sql only selecting the passed in column and uses Exec#ScanValsContext to scan the result into a +// slice of primitive values. +// +// i: A slice of primitive values +// +// col: The column to select when generative the SQL +func (d *Dataset) PluckContext(ctx context.Context, i interface{}, col string) error { + return d.Select(col).ScanValsContext(ctx, i) +} + +// Generates the UPDATE sql, and returns an Exec struct with the sql set to the UPDATE statement +// db.From("test").Update(Record{"name":"Bob", update: time.Now()}).Exec() +// +// See Dataset#ToUpdateSQL for arguments +func (d *Dataset) Update(i interface{}) exec.QueryExecutor { + return d.queryFactory.FromSQLBuilder(d.updateSQLBuilder(i)) +} + +// Generates the INSERT sql, and returns an Exec struct with the sql set to the INSERT statement +// db.From("test").Insert(Record{"name":"Bob"}).Exec() +// +// See Dataset#ToInsertSQL for arguments +func (d *Dataset) Insert(i ...interface{}) exec.QueryExecutor { + return d.InsertConflict(nil, i...) +} + +// Generates the INSERT IGNORE (mysql) or INSERT ... ON CONFLICT DO NOTHING (postgres) and returns an Exec struct. +// db.From("test").InsertIgnore(DoNothing(), Record{"name":"Bob"}).Exec() +// +// See Dataset#ToInsertConflictSQL for arguments +func (d *Dataset) InsertIgnore(i ...interface{}) exec.QueryExecutor { + return d.InsertConflict(DoNothing(), i...) +} + +// Generates the INSERT sql with (ON CONFLICT/ON DUPLICATE KEY) clause, and returns an Exec struct with the sql set to +// the INSERT statement +// db.From("test").InsertConflict(DoNothing(), Record{"name":"Bob"}).Exec() +// +// See Dataset#Upsert for arguments +func (d *Dataset) InsertConflict(c exp.ConflictExpression, i ...interface{}) exec.QueryExecutor { + return d.queryFactory.FromSQLBuilder(d.insertSQLBuilder(c, i...)) +} + +// Generates the DELETE sql, and returns an Exec struct with the sql set to the DELETE statement +// db.From("test").Where(I("id").Gt(10)).Exec() +func (d *Dataset) Delete() exec.QueryExecutor { + return d.queryFactory.FromSQLBuilder(d.deleteSQLBuilder()) +} + +func (d *Dataset) selectSQLBuilder() sb.SQLBuilder { + buf := sb.NewSQLBuilder(d.isPrepared) + d.dialect.ToSelectSQL(buf, d.GetClauses()) + return buf +} + +func (d *Dataset) toInsertSQL(ce exp.ConflictExpression, rows ...interface{}) (sql string, params []interface{}, err error) { + return d.insertSQLBuilder(ce, rows...).ToSQL() +} + +func (d *Dataset) insertSQLBuilder(ce exp.ConflictExpression, rows ...interface{}) sb.SQLBuilder { + buf := sb.NewSQLBuilder(d.isPrepared) + ie, err := exp.NewInsertExpression(rows...) + if err != nil { + return buf.SetError(err) } - return NewGoquError("Unsupported expression type %T", expression) + d.dialect.ToInsertSQL(buf, d.clauses, ie.SetOnConflict(ce)) + return buf +} + +func (d *Dataset) updateSQLBuilder(update interface{}) sb.SQLBuilder { + buf := sb.NewSQLBuilder(d.isPrepared) + d.dialect.ToUpdateSQL(buf, d.clauses, update) + return buf +} + +func (d *Dataset) deleteSQLBuilder() sb.SQLBuilder { + buf := sb.NewSQLBuilder(d.isPrepared) + d.dialect.ToDeleteSQL(buf, d.clauses) + return buf +} + +func (d *Dataset) truncateSQLBuilder(opts exp.TruncateOptions) sb.SQLBuilder { + buf := sb.NewSQLBuilder(d.isPrepared) + d.dialect.ToTruncateSQL(buf, d.clauses, opts) + return buf } diff --git a/dataset_actions.go b/dataset_actions.go deleted file mode 100644 index b1d8ad59..00000000 --- a/dataset_actions.go +++ /dev/null @@ -1,166 +0,0 @@ -package goqu - -import "context" - -//Generates the SELECT sql for this dataset and uses Exec#ScanStructs to scan the results into a slice of structs. -// -//ScanStructs will only select the columns that can be scanned in to the struct unless you have explicitly selected certain columns. See examples. -// -//i: A pointer to a slice of structs -func (me *Dataset) ScanStructs(i interface{}) error { - ds := me - if me.isDefaultSelect() { - ds = ds.Select(i) - } - sql, args, err := ds.ToSql() - return newCrudExec(me.database, err, sql, args...).ScanStructs(i) -} - -//Generates the SELECT sql for this dataset and uses Exec#ScanStructsContext to scan the results into a slice of structs. -// -//ScanStructsContext will only select the columns that can be scanned in to the struct unless you have explicitly selected certain columns. See examples. -// -//i: A pointer to a slice of structs -func (me *Dataset) ScanStructsContext(ctx context.Context, i interface{}) error { - ds := me - if me.isDefaultSelect() { - ds = ds.Select(i) - } - sql, args, err := ds.ToSql() - return newCrudExec(me.database, err, sql, args...).ScanStructsContext(ctx, i) -} - -//Generates the SELECT sql for this dataset and uses Exec#ScanStruct to scan the result into a slice of structs -// -//ScanStruct will only select the columns that can be scanned in to the struct unless you have explicitly selected certain columns. See examples. -// -//i: A pointer to a structs -func (me *Dataset) ScanStruct(i interface{}) (bool, error) { - ds := me.Limit(1) - if me.isDefaultSelect() { - ds = ds.Select(i) - } - sql, args, err := ds.ToSql() - return newCrudExec(me.database, err, sql, args...).ScanStruct(i) -} - -//Generates the SELECT sql for this dataset and uses Exec#ScanStructContext to scan the result into a slice of structs -// -//ScanStructContext will only select the columns that can be scanned in to the struct unless you have explicitly selected certain columns. See examples. -// -//i: A pointer to a structs -func (me *Dataset) ScanStructContext(ctx context.Context, i interface{}) (bool, error) { - ds := me.Limit(1) - if me.isDefaultSelect() { - ds = ds.Select(i) - } - sql, args, err := ds.ToSql() - return newCrudExec(me.database, err, sql, args...).ScanStructContext(ctx, i) -} - -//Generates the SELECT sql for this dataset and uses Exec#ScanVals to scan the results into a slice of primitive values -// -//i: A pointer to a slice of primitive values -func (me *Dataset) ScanVals(i interface{}) error { - sql, args, err := me.ToSql() - return newCrudExec(me.database, err, sql, args...).ScanVals(i) -} - -//Generates the SELECT sql for this dataset and uses Exec#ScanValsContext to scan the results into a slice of primitive values -// -//i: A pointer to a slice of primitive values -func (me *Dataset) ScanValsContext(ctx context.Context, i interface{}) error { - sql, args, err := me.ToSql() - return newCrudExec(me.database, err, sql, args...).ScanValsContext(ctx, i) -} - -//Generates the SELECT sql for this dataset and uses Exec#ScanVal to scan the result into a primitive value -// -//i: A pointer to a primitive value -func (me *Dataset) ScanVal(i interface{}) (bool, error) { - sql, args, err := me.Limit(1).ToSql() - return newCrudExec(me.database, err, sql, args...).ScanVal(i) -} - -//Generates the SELECT sql for this dataset and uses Exec#ScanValContext to scan the result into a primitive value -// -//i: A pointer to a primitive value -func (me *Dataset) ScanValContext(ctx context.Context, i interface{}) (bool, error) { - sql, args, err := me.Limit(1).ToSql() - return newCrudExec(me.database, err, sql, args...).ScanValContext(ctx, i) -} - -//Generates the SELECT COUNT(*) sql for this dataset and uses Exec#ScanVal to scan the result into an int64. -func (me *Dataset) Count() (int64, error) { - var count int64 - _, err := me.Select(COUNT(Star()).As("count")).ScanVal(&count) - return count, err -} - -//Generates the SELECT COUNT(*) sql for this dataset and uses Exec#ScanValContext to scan the result into an int64. -func (me *Dataset) CountContext(ctx context.Context) (int64, error) { - var count int64 - _, err := me.Select(COUNT(Star()).As("count")).ScanValContext(ctx, &count) - return count, err -} - -//Generates the SELECT sql only selecting the passed in column and uses Exec#ScanVals to scan the result into a slice of primitive values. -// -//i: A slice of primitive values -// -//col: The column to select when generative the SQL -func (me *Dataset) Pluck(i interface{}, col string) error { - return me.Select(col).ScanVals(i) -} - -//Generates the SELECT sql only selecting the passed in column and uses Exec#ScanValsContext to scan the result into a slice of primitive values. -// -//i: A slice of primitive values -// -//col: The column to select when generative the SQL -func (me *Dataset) PluckContext(ctx context.Context, i interface{}, col string) error { - return me.Select(col).ScanValsContext(ctx, i) -} - -//Generates the UPDATE sql, and returns an Exec struct with the sql set to the UPDATE statement -// db.From("test").Update(Record{"name":"Bob", update: time.Now()}).Exec() -// -//See Dataset#UpdateSql for arguments -func (me *Dataset) Update(i interface{}) *CrudExec { - sql, args, err := me.ToUpdateSql(i) - return newCrudExec(me.database, err, sql, args...) -} - -//Generates the INSERT sql, and returns an Exec struct with the sql set to the INSERT statement -// db.From("test").Insert(Record{"name":"Bob"}).Exec() -// -//See Dataset#InsertSql for arguments -func (me *Dataset) Insert(i ...interface{}) *CrudExec { - sql, args, err := me.ToInsertSql(i...) - return newCrudExec(me.database, err, sql, args...) -} - -//Generates the INSERT IGNORE (mysql) or INSERT ... ON CONFLICT DO NOTHING (postgres) and returns an Exec struct. -// db.From("test").InsertIgnore(DoNothing(), Record{"name":"Bob"}).Exec() -// -//See Dataset#InsertIgnore for arguments -func (me *Dataset) InsertIgnore(i ...interface{}) *CrudExec { - sql, args, err := me.ToInsertConflictSql(DoNothing(), i...) - return newCrudExec(me.database, err, sql, args...) -} - -//Generates the INSERT sql with (ON CONFLICT/ON DUPLICATE KEY) clause, and returns an Exec struct with the sql set to the INSERT statement -// db.From("test").InsertConflict(DoNothing(), Record{"name":"Bob"}).Exec() -// -//See Dataset#Upsert for arguments -func (me *Dataset) InsertConflict(c ConflictExpression, i ...interface{}) *CrudExec { - sql, args, err := me.ToInsertConflictSql(c, i...) - return newCrudExec(me.database, err, sql, args...) -} - -//Generates the DELETE sql, and returns an Exec struct with the sql set to the DELETE statement -// db.From("test").Where(I("id").Gt(10)).Exec() -func (me *Dataset) Delete() *CrudExec { - sql, args, err := me.ToDeleteSql() - return newCrudExec(me.database, err, sql, args...) -} diff --git a/dataset_actions_test.go b/dataset_actions_test.go deleted file mode 100644 index e41ceba3..00000000 --- a/dataset_actions_test.go +++ /dev/null @@ -1,389 +0,0 @@ -package goqu - -import ( - "github.com/DATA-DOG/go-sqlmock" - "github.com/stretchr/testify/assert" -) - -type dsTestActionItem struct { - Address string `db:"address"` - Name string `db:"name"` -} - -func (me *datasetTest) TestScanStructs() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectQuery(`SELECT "address", "name" FROM "items"`). - WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) - - mock.ExpectQuery(`SELECT DISTINCT "name" FROM "items"`). - WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) - - mock.ExpectQuery(`SELECT "test" FROM "items"`). - WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2")) - - db := New("mock", mDb) - var items []dsTestActionItem - assert.NoError(t, db.From("items").ScanStructs(&items)) - assert.Len(t, items, 2) - assert.Equal(t, items[0].Address, "111 Test Addr") - assert.Equal(t, items[0].Name, "Test1") - - assert.Equal(t, items[1].Address, "211 Test Addr") - assert.Equal(t, items[1].Name, "Test2") - - items = items[0:0] - assert.NoError(t, db.From("items").SelectDistinct("name").ScanStructs(&items)) - assert.Len(t, items, 2) - assert.Equal(t, items[0].Address, "111 Test Addr") - assert.Equal(t, items[0].Name, "Test1") - - assert.Equal(t, items[1].Address, "211 Test Addr") - assert.Equal(t, items[1].Name, "Test2") - - items = items[0:0] - assert.EqualError(t, db.From("items").ScanStructs(items), "goqu: Type must be a pointer to a slice when calling ScanStructs") - assert.EqualError(t, db.From("items").ScanStructs(&dsTestActionItem{}), "goqu: Type must be a pointer to a slice when calling ScanStructs") - assert.EqualError(t, db.From("items").Select("test").ScanStructs(&items), `goqu: Unable to find corresponding field to column "test" returned by query`) -} - -func (me *datasetTest) TestScanStructs_WithPreparedStatements() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectQuery(`SELECT "address", "name" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\)`). - WithArgs("111 Test Addr", "Bob", "Sally", "Billy"). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) - - mock.ExpectQuery(`SELECT "test" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\)`). - WithArgs("111 Test Addr", "Bob", "Sally", "Billy"). - WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2")) - - db := New("mock", mDb) - var items []dsTestActionItem - assert.NoError(t, db.From("items").Prepared(true).Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}).ScanStructs(&items)) - assert.Len(t, items, 2) - assert.Equal(t, items[0].Address, "111 Test Addr") - assert.Equal(t, items[0].Name, "Test1") - - assert.Equal(t, items[1].Address, "211 Test Addr") - assert.Equal(t, items[1].Name, "Test2") - - items = items[0:0] - assert.EqualError(t, db.From("items").ScanStructs(items), "goqu: Type must be a pointer to a slice when calling ScanStructs") - assert.EqualError(t, db.From("items").ScanStructs(&dsTestActionItem{}), "goqu: Type must be a pointer to a slice when calling ScanStructs") - assert.EqualError(t, db.From("items"). - Prepared(true). - Select("test"). - Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). - ScanStructs(&items), `goqu: Unable to find corresponding field to column "test" returned by query`) -} - -func (me *datasetTest) TestScanStruct() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectQuery(`SELECT "address", "name" FROM "items" LIMIT 1`). - WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1")) - - mock.ExpectQuery(`SELECT DISTINCT "name" FROM "items" LIMIT 1`). - WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1")) - - mock.ExpectQuery(`SELECT "test" FROM "items" LIMIT 1`). - WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2")) - - db := New("mock", mDb) - var item dsTestActionItem - found, err := db.From("items").ScanStruct(&item) - assert.NoError(t, err) - assert.True(t, found) - assert.Equal(t, item.Address, "111 Test Addr") - assert.Equal(t, item.Name, "Test1") - - item = dsTestActionItem{} - found, err = db.From("items").SelectDistinct("name").ScanStruct(&item) - assert.NoError(t, err) - assert.True(t, found) - assert.Equal(t, item.Address, "111 Test Addr") - assert.Equal(t, item.Name, "Test1") - - _, err = db.From("items").ScanStruct(item) - assert.EqualError(t, err, "goqu: Type must be a pointer to a struct when calling ScanStruct") - _, err = db.From("items").ScanStruct([]dsTestActionItem{}) - assert.EqualError(t, err, "goqu: Type must be a pointer to a struct when calling ScanStruct") - _, err = db.From("items").Select("test").ScanStruct(&item) - assert.EqualError(t, err, `goqu: Unable to find corresponding field to column "test" returned by query`) -} - -func (me *datasetTest) TestScanStruct_WithPreparedStatements() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectQuery(`SELECT "address", "name" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\) LIMIT \?`). - WithArgs("111 Test Addr", "Bob", "Sally", "Billy", 1). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1")) - - mock.ExpectQuery(`SELECT "test" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\) LIMIT \?`). - WithArgs("111 Test Addr", "Bob", "Sally", "Billy", 1). - WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2")) - - db := New("mock", mDb) - var item dsTestActionItem - found, err := db.From("items").Prepared(true).Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}).ScanStruct(&item) - assert.NoError(t, err) - assert.True(t, found) - assert.Equal(t, item.Address, "111 Test Addr") - assert.Equal(t, item.Name, "Test1") - - _, err = db.From("items").ScanStruct(item) - assert.EqualError(t, err, "goqu: Type must be a pointer to a struct when calling ScanStruct") - _, err = db.From("items").ScanStruct([]dsTestActionItem{}) - assert.EqualError(t, err, "goqu: Type must be a pointer to a struct when calling ScanStruct") - _, err = db.From("items"). - Prepared(true). - Select("test"). - Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). - ScanStruct(&item) - assert.EqualError(t, err, `goqu: Unable to find corresponding field to column "test" returned by query`) -} - -func (me *datasetTest) TestScanVals() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectQuery(`SELECT "id" FROM "items"`). - WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("1\n2\n3\n4\n5")) - - db := New("mock", mDb) - var ids []uint32 - assert.NoError(t, db.From("items").Select("id").ScanVals(&ids)) - assert.Len(t, ids, 5) - - assert.EqualError(t, db.From("items").ScanVals([]uint32{}), "goqu: Type must be a pointer to a slice when calling ScanVals") - assert.EqualError(t, db.From("items").ScanVals(dsTestActionItem{}), "goqu: Type must be a pointer to a slice when calling ScanVals") -} - -func (me *datasetTest) TestScanVals_WithPreparedStatment() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectQuery(`SELECT "id" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\)`). - WithArgs("111 Test Addr", "Bob", "Sally", "Billy"). - WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("1\n2\n3\n4\n5")) - - db := New("mock", mDb) - var ids []uint32 - assert.NoError(t, db.From("items"). - Prepared(true). - Select("id"). - Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). - ScanVals(&ids)) - assert.Len(t, ids, 5) - - assert.EqualError(t, db.From("items").ScanVals([]uint32{}), "goqu: Type must be a pointer to a slice when calling ScanVals") - assert.EqualError(t, db.From("items").ScanVals(dsTestActionItem{}), "goqu: Type must be a pointer to a slice when calling ScanVals") -} - -func (me *datasetTest) TestScanVal() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectQuery(`SELECT "id" FROM "items" LIMIT 1`). - WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("10")) - - db := New("mock", mDb) - var id int64 - found, err := db.From("items").Select("id").ScanVal(&id) - assert.NoError(t, err) - assert.Equal(t, id, int64(10)) - assert.True(t, found) - - found, err = db.From("items").ScanVal([]int64{}) - assert.EqualError(t, err, "goqu: Type must be a pointer when calling ScanVal") - found, err = db.From("items").ScanVal(10) - assert.EqualError(t, err, "goqu: Type must be a pointer when calling ScanVal") -} - -func (me *datasetTest) TestScanVal_WithPreparedStatement() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectQuery(`SELECT "id" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\) LIMIT ?`). - WithArgs("111 Test Addr", "Bob", "Sally", "Billy", 1). - WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("10")) - - db := New("mock", mDb) - var id int64 - found, err := db.From("items"). - Prepared(true). - Select("id"). - Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). - ScanVal(&id) - assert.NoError(t, err) - assert.Equal(t, id, int64(10)) - assert.True(t, found) - - found, err = db.From("items").ScanVal([]int64{}) - assert.EqualError(t, err, "goqu: Type must be a pointer when calling ScanVal") - found, err = db.From("items").ScanVal(10) - assert.EqualError(t, err, "goqu: Type must be a pointer when calling ScanVal") -} - -func (me *datasetTest) TestCount() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectQuery(`SELECT COUNT\(\*\) AS "count" FROM "items"`). - WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"count"}).FromCSVString("10")) - - db := New("mock", mDb) - count, err := db.From("items").Count() - assert.NoError(t, err) - assert.Equal(t, count, int64(10)) -} - -func (me *datasetTest) TestCount_WithPreparedStatement() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectQuery(`SELECT COUNT\(\*\) AS "count" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\)`). - WithArgs("111 Test Addr", "Bob", "Sally", "Billy", 1). - WillReturnRows(sqlmock.NewRows([]string{"count"}).FromCSVString("10")) - - db := New("mock", mDb) - count, err := db.From("items"). - Prepared(true). - Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). - Count() - assert.NoError(t, err) - assert.Equal(t, count, int64(10)) -} - -func (me *datasetTest) TestPluck() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectQuery(`SELECT "name" FROM "items"`). - WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"name"}).FromCSVString("test1\ntest2\ntest3\ntest4\ntest5")) - - db := New("mock", mDb) - var names []string - assert.NoError(t, db.From("items").Pluck(&names, "name")) - assert.Equal(t, names, []string{"test1", "test2", "test3", "test4", "test5"}) -} - -func (me *datasetTest) TestPluck_WithPreparedStatement() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectQuery(`SELECT "name" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\)`). - WithArgs("111 Test Addr", "Bob", "Sally", "Billy"). - WillReturnRows(sqlmock.NewRows([]string{"name"}).FromCSVString("Bob\nSally\nBilly")) - - db := New("mock", mDb) - var names []string - assert.NoError(t, db.From("items"). - Prepared(true). - Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). - Pluck(&names, "name")) - assert.Equal(t, names, []string{"Bob", "Sally", "Billy"}) -} - -func (me *datasetTest) TestUpdate() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectExec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE \("name" IS NULL\)`). - WithArgs(). - WillReturnResult(sqlmock.NewResult(0, 0)) - - db := New("mock", mDb) - _, err = db.From("items").Where(I("name").IsNull()).Update(Record{"address": "111 Test Addr", "name": "Test1"}).Exec() - assert.NoError(t, err) -} - -func (me *datasetTest) TestUpdate_WithPreparedStatement() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectExec(`UPDATE "items" SET "address"=\?,"name"=\? WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\)`). - WithArgs("112 Test Addr", "Test1", "111 Test Addr", "Bob", "Sally", "Billy"). - WillReturnResult(sqlmock.NewResult(0, 0)) - - db := New("mock", mDb) - _, err = db.From("items"). - Prepared(true). - Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). - Update(Record{"address": "112 Test Addr", "name": "Test1"}). - Exec() - assert.NoError(t, err) -} - -func (me *datasetTest) TestInsert() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectExec(`INSERT INTO "items" \("address", "name"\) VALUES \('111 Test Addr', 'Test1'\)`). - WithArgs(). - WillReturnResult(sqlmock.NewResult(0, 0)) - - db := New("mock", mDb) - _, err = db.From("items").Insert(Record{"address": "111 Test Addr", "name": "Test1"}).Exec() - assert.NoError(t, err) -} - -func (me *datasetTest) TestInsert_WithPreparedStatment() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectExec(`INSERT INTO "items" \("address", "name"\) VALUES \(\?, \?\), \(\?, \?\)`). - WithArgs("111 Test Addr", "Test1", "112 Test Addr", "Test2"). - WillReturnResult(sqlmock.NewResult(0, 0)) - - db := New("mock", mDb) - _, err = db.From("items"). - Prepared(true). - Insert( - Record{"address": "111 Test Addr", "name": "Test1"}, - Record{"address": "112 Test Addr", "name": "Test2"}, - ). - Exec() - assert.NoError(t, err) -} - -func (me *datasetTest) TestDelete() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectExec(`DELETE FROM "items" WHERE \("id" > 10\)`). - WithArgs(). - WillReturnResult(sqlmock.NewResult(0, 0)) - - db := New("mock", mDb) - _, err = db.From("items").Where(I("id").Gt(10)).Delete().Exec() - assert.NoError(t, err) -} - -func (me *datasetTest) TestDelete_WithPreparedStatment() { - t := me.T() - mDb, mock, err := sqlmock.New() - assert.NoError(t, err) - mock.ExpectExec(`DELETE FROM "items" WHERE \("id" > \?\)`). - WithArgs(10). - WillReturnResult(sqlmock.NewResult(0, 0)) - - db := New("mock", mDb) - _, err = db.From("items").Prepared(true).Where(Ex{"id": Op{"gt": 10}}).Delete().Exec() - assert.NoError(t, err) -} diff --git a/dataset_delete.go b/dataset_delete.go deleted file mode 100644 index 603c8997..00000000 --- a/dataset_delete.go +++ /dev/null @@ -1,99 +0,0 @@ -package goqu - -type ( - //Options to use when generating a TRUNCATE statement - TruncateOptions struct { - //Set to true to add CASCADE to the TRUNCATE statement - Cascade bool - //Set to true to add RESTRICT to the TRUNCATE statement - Restrict bool - //Set to true to specify IDENTITY options, (e.g. RESTART, CONTINUE) to the TRUNCATE statement - Identity string - } -) - -//Generates a DELETE statement, if Prepared has been called with true then the statement will not be interpolated. See examples. -// -//isPrepared: Set to true to true to ensure values are NOT interpolated -// -//Errors: -// * There is no FROM clause -// * Error generating SQL -func (me *Dataset) ToDeleteSql() (string, []interface{}, error) { - buf := NewSqlBuilder(me.isPrepared) - if !me.hasSources() { - return "", nil, NewGoquError("No source found when generating delete sql") - } - if err := me.adapter.CommonTablesSql(buf, me.clauses.CommonTables); err != nil { - return "", nil, err - } - if err := me.adapter.DeleteBeginSql(buf); err != nil { - return "", nil, err - } - if err := me.adapter.FromSql(buf, me.clauses.From); err != nil { - return "", nil, err - } - if err := me.adapter.WhereSql(buf, me.clauses.Where); err != nil { - return "", nil, err - } - if me.adapter.SupportsOrderByOnDelete() { - if err := me.adapter.OrderSql(buf, me.clauses.Order); err != nil { - return "", nil, err - } - } - if me.adapter.SupportsLimitOnDelete() { - if err := me.adapter.LimitSql(buf, me.clauses.Limit); err != nil { - return "", nil, err - } - } - if me.adapter.SupportsReturn() { - if err := me.adapter.ReturningSql(buf, me.clauses.Returning); err != nil { - return "", nil, NewGoquError(err.Error()) - } - } else if me.clauses.Returning != nil { - return "", nil, NewGoquError("Adapter does not support RETURNING clause") - } - sql, args := buf.ToSql() - return sql, args, nil -} - -//Generates the default TRUNCATE statement. See examples. -// -//Errors: -// * There is no FROM clause -// * Error generating SQL -func (me *Dataset) ToTruncateSql() (string, []interface{}, error) { - return me.ToTruncateWithOptsSql(TruncateOptions{}) -} - -//Generates the default TRUNCATE statement with the specified options. See examples. -// -//opts: Options to use when generating the TRUNCATE statement -// -//Errors: -// * There is no FROM clause -// * Error generating SQL -func (me *Dataset) ToTruncateWithOptsSql(opts TruncateOptions) (string, []interface{}, error) { - return me.toTruncateSql(opts) -} - -//Generates a TRUNCATE statement. -// -//isPrepared: Set to true to true to ensure values are NOT interpolated. See examples. -// -//opts: Options to use when generating the TRUNCATE statement -// -//Errors: -// * There is no FROM clause -// * Error generating SQL -func (me *Dataset) toTruncateSql(opts TruncateOptions) (string, []interface{}, error) { - if !me.hasSources() { - return "", nil, NewGoquError("No source found when generating truncate sql") - } - buf := NewSqlBuilder(me.isPrepared) - if err := me.adapter.TruncateSql(buf, me.clauses.From, opts); err != nil { - return "", nil, err - } - sql, args := buf.ToSql() - return sql, args, nil -} diff --git a/dataset_delete_test.go b/dataset_delete_test.go deleted file mode 100644 index ebf64865..00000000 --- a/dataset_delete_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package goqu - -import ( - "github.com/DATA-DOG/go-sqlmock" - "github.com/stretchr/testify/assert" -) - -func (me *datasetTest) TestDeleteSqlNoReturning() { - t := me.T() - mDb, _, _ := sqlmock.New() - ds1 := New("no-return", mDb).From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - _, _, err := ds1.Returning("id").ToDeleteSql() - assert.EqualError(t, err, "goqu: Adapter does not support RETURNING clause") -} - -func (me *datasetTest) TestDeleteSqlWithLimit() { - t := me.T() - mDb, _, _ := sqlmock.New() - ds1 := New("limit", mDb).From("items") - sql, _, err := ds1.Limit(10).ToDeleteSql() - assert.Nil(t, err) - assert.Equal(t, sql, `DELETE FROM "items" LIMIT 10`) -} - -func (me *datasetTest) TestDeleteSqlWithOrder() { - t := me.T() - mDb, _, _ := sqlmock.New() - ds1 := New("order", mDb).From("items") - sql, _, err := ds1.Order(I("name").Desc()).ToDeleteSql() - assert.Nil(t, err) - assert.Equal(t, sql, `DELETE FROM "items" ORDER BY "name" DESC`) -} - -func (me *datasetTest) TestToDeleteSql() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.ToDeleteSql() - assert.NoError(t, err) - assert.Equal(t, sql, `DELETE FROM "items"`) -} - -func (me *datasetTest) TestDeleteSqlNoSources() { - t := me.T() - ds1 := From("items") - _, _, err := ds1.From().ToDeleteSql() - assert.EqualError(t, err, "goqu: No source found when generating delete sql") -} - -func (me *datasetTest) TestDeleteSqlWithWhere() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.Where(I("id").IsNotNull()).ToDeleteSql() - assert.NoError(t, err) - assert.Equal(t, sql, `DELETE FROM "items" WHERE ("id" IS NOT NULL)`) -} - -func (me *datasetTest) TestDeleteSqlWithReturning() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.Returning("id").ToDeleteSql() - assert.NoError(t, err) - assert.Equal(t, sql, `DELETE FROM "items" RETURNING "id"`) - - sql, _, err = ds1.Returning("id").Where(I("id").IsNotNull()).ToDeleteSql() - assert.NoError(t, err) - assert.Equal(t, sql, `DELETE FROM "items" WHERE ("id" IS NOT NULL) RETURNING "id"`) -} - -func (me *datasetTest) TestTruncateSql() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.ToTruncateSql() - assert.NoError(t, err) - assert.Equal(t, sql, `TRUNCATE "items"`) -} - -func (me *datasetTest) TestTruncateSqlNoSources() { - t := me.T() - ds1 := From("items") - _, _, err := ds1.From().ToTruncateSql() - assert.EqualError(t, err, "goqu: No source found when generating truncate sql") -} - -func (me *datasetTest) TestTruncateSqlWithOpts() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.ToTruncateWithOptsSql(TruncateOptions{Cascade: true}) - assert.NoError(t, err) - assert.Equal(t, sql, `TRUNCATE "items" CASCADE`) - - sql, _, err = ds1.ToTruncateWithOptsSql(TruncateOptions{Restrict: true}) - assert.NoError(t, err) - assert.Equal(t, sql, `TRUNCATE "items" RESTRICT`) - - sql, _, err = ds1.ToTruncateWithOptsSql(TruncateOptions{Identity: "restart"}) - assert.NoError(t, err) - assert.Equal(t, sql, `TRUNCATE "items" RESTART IDENTITY`) - - sql, _, err = ds1.ToTruncateWithOptsSql(TruncateOptions{Identity: "continue"}) - assert.NoError(t, err) - assert.Equal(t, sql, `TRUNCATE "items" CONTINUE IDENTITY`) -} - -func (me *datasetTest) TestPreparedToDeleteSql() { - t := me.T() - ds1 := From("items") - sql, args, err := ds1.Prepared(true).ToDeleteSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{}) - assert.Equal(t, sql, `DELETE FROM "items"`) - - sql, args, err = ds1.Where(I("id").Eq(1)).Prepared(true).ToDeleteSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `DELETE FROM "items" WHERE ("id" = ?)`) - - sql, args, err = ds1.Returning("id").Where(I("id").Eq(1)).Prepared(true).ToDeleteSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `DELETE FROM "items" WHERE ("id" = ?) RETURNING "id"`) -} - -func (me *datasetTest) TestPreparedTruncateSql() { - t := me.T() - ds1 := From("items") - sql, args, err := ds1.ToTruncateSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{}) - assert.Equal(t, sql, `TRUNCATE "items"`) -} diff --git a/dataset_insert.go b/dataset_insert.go deleted file mode 100644 index 48d43412..00000000 --- a/dataset_insert.go +++ /dev/null @@ -1,231 +0,0 @@ -package goqu - -import ( - "reflect" - "sort" -) - -//Generates the default INSERT statement. If Prepared has been called with true then the statement will not be interpolated. See examples. -//When using structs you may specify a column to be skipped in the insert, (e.g. id) by specifying a goqu tag with `skipinsert` -// type Item struct{ -// Id uint32 `db:"id" goqu:"skipinsert"` -// Name string `db:"name"` -// } -// -//rows: variable number arguments of either map[string]interface, Record, struct, or a single slice argument of the accepted types. -// -//Errors: -// * There is no FROM clause -// * Different row types passed in, all rows must be of the same type -// * Maps with different numbers of K/V pairs -// * Rows of different lengths, (i.e. (Record{"name": "a"}, Record{"name": "a", "age": 10}) -// * Error generating SQL -func (me *Dataset) ToInsertSql(rows ...interface{}) (string, []interface{}, error) { - return me.toInsertSql(nil, rows) -} - -//Generates the default INSERT IGNORE/ INSERT ... ON CONFLICT DO NOTHING statement. If Prepared has been called with true then the statement will not be interpolated. See examples. -// -//c: ConflictExpression action. Can be DoNothing/Ignore or DoUpdate/DoUpdateWhere. -//rows: variable number arguments of either map[string]interface, Record, struct, or a single slice argument of the accepted types. -// -//Errors: -// * There is no FROM clause -// * Different row types passed in, all rows must be of the same type -// * Maps with different numbers of K/V pairs -// * Rows of different lengths, (i.e. (Record{"name": "a"}, Record{"name": "a", "age": 10}) -// * Error generating SQL -func (me *Dataset) ToInsertIgnoreSql(rows ...interface{}) (string, []interface{}, error) { - return me.toInsertSql(DoNothing(), rows) -} - -// Generates the INSERT [IGNORE] ... ON CONFLICT/DUPLICATE KEY. If Prepared has been called with true then the statement will -// not be interpolated. See examples. -// -//rows: variable number arguments of either map[string]interface, Record, struct, or a single slice argument of the accepted types. -// -//Errors: -// * There is no FROM clause -// * Different row types passed in, all rows must be of the same type -// * Maps with different numbers of K/V pairs -// * Rows of different lengths, (i.e. (Record{"name": "a"}, Record{"name": "a", "age": 10}) -// * Error generating SQL -func (me *Dataset) ToInsertConflictSql(o ConflictExpression, rows ...interface{}) (string, []interface{}, error) { - return me.toInsertSql(o, rows) -} - -func (me *Dataset) toInsertSql(o ConflictExpression, rows ...interface{}) (string, []interface{}, error) { - if !me.hasSources() { - return "", nil, NewGoquError("No source found when generating insert sql") - } - switch len(rows) { - case 0: - return me.insertSql(nil, nil, me.isPrepared, o) - case 1: - val := reflect.ValueOf(rows[0]) - if val.Kind() == reflect.Slice { - vals := make([]interface{}, val.Len()) - for i := 0; i < val.Len(); i++ { - vals[i] = val.Index(i).Interface() - } - return me.toInsertSql(o, vals...) - } - switch rows[0].(type) { - case *Dataset: - return me.insertFromSql(*rows[0].(*Dataset), me.isPrepared, o) - } - - } - columns, vals, err := me.getInsertColsAndVals(rows...) - if err != nil { - return "", nil, err - } - return me.insertSql(columns, vals, me.isPrepared, o) -} - -func (me *Dataset) canInsertField(field reflect.StructField) bool { - goquTag, dbTag := tagOptions(field.Tag.Get("goqu")), field.Tag.Get("db") - return !goquTag.Contains("skipinsert") && dbTag != "" && dbTag != "-" -} - -//parses the rows gathering and sorting unique columns and values for each record -func (me *Dataset) getInsertColsAndVals(rows ...interface{}) (columns ColumnList, vals [][]interface{}, err error) { - var mapKeys valueSlice - rowValue := reflect.Indirect(reflect.ValueOf(rows[0])) - rowType := rowValue.Type() - rowKind := rowValue.Kind() - vals = make([][]interface{}, len(rows)) - for i, row := range rows { - if rowType != reflect.Indirect(reflect.ValueOf(row)).Type() { - return nil, nil, NewGoquError("Rows must be all the same type expected %+v got %+v", rowType, reflect.Indirect(reflect.ValueOf(row)).Type()) - } - newRowValue := reflect.Indirect(reflect.ValueOf(row)) - switch rowKind { - case reflect.Map: - if columns == nil { - mapKeys = valueSlice(newRowValue.MapKeys()) - sort.Sort(mapKeys) - colKeys := make([]interface{}, len(mapKeys)) - for j, key := range mapKeys { - colKeys[j] = key.Interface() - } - columns = cols(colKeys...) - } - newMapKeys := valueSlice(newRowValue.MapKeys()) - if len(newMapKeys) != len(mapKeys) { - return nil, nil, NewGoquError("Rows with different value length expected %d got %d", len(mapKeys), len(newMapKeys)) - } - if !mapKeys.Equal(newMapKeys) { - return nil, nil, NewGoquError("Rows with different keys expected %s got %s", mapKeys.String(), newMapKeys.String()) - } - rowVals := make([]interface{}, len(mapKeys)) - for j, key := range mapKeys { - rowVals[j] = newRowValue.MapIndex(key).Interface() - } - vals[i] = rowVals - case reflect.Struct: - var ( - rowCols []interface{} - rowVals []interface{} - ) - rowCols, rowVals = me.getFieldsValues(newRowValue) - if columns == nil { - columns = cols(rowCols...) - } - vals[i] = rowVals - default: - return nil, nil, NewGoquError("Unsupported insert must be map, goqu.Record, or struct type got: %T", row) - } - } - return columns, vals, nil -} - -func (me *Dataset) getFieldsValues(value reflect.Value) (rowCols []interface{}, rowVals []interface{}) { - if value.IsValid() { - for i := 0; i < value.NumField(); i++ { - v := value.Field(i) - t := value.Type().Field(i) - if !t.Anonymous { - if me.canInsertField(t) { - rowCols = append(rowCols, t.Tag.Get("db")) - rowVals = append(rowVals, v.Interface()) - } - } else { - cols, vals := me.getFieldsValues(reflect.Indirect(reflect.ValueOf(v.Interface()))) - rowCols = append(rowCols, cols...) - rowVals = append(rowVals, vals...) - } - } - } - - return rowCols, rowVals -} - -//Creates an INSERT statement with the columns and values passed in -func (me *Dataset) insertSql(cols ColumnList, values [][]interface{}, prepared bool, c ConflictExpression) (string, []interface{}, error) { - buf := NewSqlBuilder(prepared) - if err := me.adapter.CommonTablesSql(buf, me.clauses.CommonTables); err != nil { - return "", nil, err - } - if err := me.adapter.InsertBeginSql(buf, c); err != nil { - return "", nil, err - } - if err := me.adapter.SourcesSql(buf, me.clauses.From); err != nil { - return "", nil, NewGoquError(err.Error()) - } - if cols == nil { - if err := me.adapter.DefaultValuesSql(buf); err != nil { - return "", nil, NewGoquError(err.Error()) - } - } else { - if err := me.adapter.InsertColumnsSql(buf, cols); err != nil { - return "", nil, NewGoquError(err.Error()) - } - if err := me.adapter.InsertValuesSql(buf, values); err != nil { - return "", nil, NewGoquError(err.Error()) - } - } - if err := me.adapter.OnConflictSql(buf, c); err != nil { - return "", nil, err - } - if me.adapter.SupportsReturn() { - if err := me.adapter.ReturningSql(buf, me.clauses.Returning); err != nil { - return "", nil, err - } - } else if me.clauses.Returning != nil { - return "", nil, NewGoquError("Adapter does not support RETURNING clause") - } - - sql, args := buf.ToSql() - return sql, args, nil -} - -//Creates an insert statement with values coming from another dataset -func (me *Dataset) insertFromSql(other Dataset, prepared bool, c ConflictExpression) (string, []interface{}, error) { - buf := NewSqlBuilder(prepared) - if err := me.adapter.CommonTablesSql(buf, me.clauses.CommonTables); err != nil { - return "", nil, err - } - if err := me.adapter.InsertBeginSql(buf, nil); err != nil { - return "", nil, err - } - if err := me.adapter.SourcesSql(buf, me.clauses.From); err != nil { - return "", nil, NewGoquError(err.Error()) - } - buf.WriteString(" ") - if err := other.selectSqlWriteTo(buf); err != nil { - return "", nil, err - } - if err := me.adapter.OnConflictSql(buf, c); err != nil { - return "", nil, err - } - if me.adapter.SupportsReturn() { - if err := me.adapter.ReturningSql(buf, me.clauses.Returning); err != nil { - return "", nil, err - } - } else if me.clauses.Returning != nil { - return "", nil, NewGoquError("Adapter does not support RETURNING clause") - } - sql, args := buf.ToSql() - return sql, args, nil -} diff --git a/dataset_insert_test.go b/dataset_insert_test.go deleted file mode 100644 index 57a9e0a8..00000000 --- a/dataset_insert_test.go +++ /dev/null @@ -1,692 +0,0 @@ -package goqu - -import ( - "github.com/DATA-DOG/go-sqlmock" - "github.com/stretchr/testify/assert" - - "database/sql" - "time" -) - -func (me *datasetTest) TestInsertNullTime() { - t := me.T() - ds1 := From("items") - type item struct { - CreatedAt *time.Time `db:"created_at"` - } - sql, _, err := ds1.ToInsertSql(item{CreatedAt: nil}) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("created_at") VALUES (NULL)`) -} - -func (me *datasetTest) TestInsertSqlNoReturning() { - t := me.T() - mDb, _, _ := sqlmock.New() - ds1 := New("no-return", mDb).From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - _, _, err := ds1.Returning("id").ToInsertSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.EqualError(t, err, "goqu: Adapter does not support RETURNING clause") - - _, _, err = ds1.Returning("id").ToInsertSql(From("test2")) - assert.EqualError(t, err, "goqu: Adapter does not support RETURNING clause") -} - -func (me *datasetTest) TestInsert_InvalidValue() { - t := me.T() - mDb, _, _ := sqlmock.New() - ds1 := New("no-return", mDb).From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - _, _, err := ds1.ToInsertSql(true) - assert.EqualError(t, err, "goqu: Unsupported insert must be map, goqu.Record, or struct type got: bool") -} - -func (me *datasetTest) TestInsertSqlWithStructs() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - Created time.Time `db:"created"` - } - created, _ := time.Parse("2006-01-02", "2015-01-01") - sql, _, err := ds1.ToInsertSql(item{Name: "Test", Address: "111 Test Addr", Created: created}) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name", "created") VALUES ('111 Test Addr', 'Test', '`+created.Format(time.RFC3339Nano)+`')`) - - sql, _, err = ds1.ToInsertSql( - item{Address: "111 Test Addr", Name: "Test1", Created: created}, - item{Address: "211 Test Addr", Name: "Test2", Created: created}, - item{Address: "311 Test Addr", Name: "Test3", Created: created}, - item{Address: "411 Test Addr", Name: "Test4", Created: created}, - ) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name", "created") VALUES ('111 Test Addr', 'Test1', '`+created.Format(time.RFC3339Nano)+`'), ('211 Test Addr', 'Test2', '`+created.Format(time.RFC3339Nano)+`'), ('311 Test Addr', 'Test3', '`+created.Format(time.RFC3339Nano)+`'), ('411 Test Addr', 'Test4', '`+created.Format(time.RFC3339Nano)+`')`) -} - -func (me *datasetTest) TestInsertSqlWithEmbeddedStruct() { - t := me.T() - ds1 := From("items") - type Phone struct { - Primary string `db:"primary_phone"` - Home string `db:"home_phone"` - } - type item struct { - Phone - Address string `db:"address"` - Name string `db:"name"` - } - sql, _, err := ds1.ToInsertSql(item{Name: "Test", Address: "111 Test Addr", Phone: Phone{Home: "123123", Primary: "456456"}}) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name") VALUES ('456456', '123123', '111 Test Addr', 'Test')`) - - sql, _, err = ds1.ToInsertSql( - item{Address: "111 Test Addr", Name: "Test1", Phone: Phone{Home: "123123", Primary: "456456"}}, - item{Address: "211 Test Addr", Name: "Test2", Phone: Phone{Home: "123123", Primary: "456456"}}, - item{Address: "311 Test Addr", Name: "Test3", Phone: Phone{Home: "123123", Primary: "456456"}}, - item{Address: "411 Test Addr", Name: "Test4", Phone: Phone{Home: "123123", Primary: "456456"}}, - ) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name") VALUES ('456456', '123123', '111 Test Addr', 'Test1'), ('456456', '123123', '211 Test Addr', 'Test2'), ('456456', '123123', '311 Test Addr', 'Test3'), ('456456', '123123', '411 Test Addr', 'Test4')`) -} - -func (me *datasetTest) TestInsertSqlWithEmbeddedStructPtr() { - t := me.T() - ds1 := From("items") - type Phone struct { - Primary string `db:"primary_phone"` - Home string `db:"home_phone"` - } - type item struct { - *Phone - Address string `db:"address"` - Name string `db:"name"` - Valuer sql.NullInt64 `db:"valuer"` - } - sql, _, err := ds1.ToInsertSql(item{Name: "Test", Address: "111 Test Addr", Valuer: sql.NullInt64{Int64: 10, Valid: true}, Phone: &Phone{Home: "123123", Primary: "456456"}}) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name", "valuer") VALUES ('456456', '123123', '111 Test Addr', 'Test', 10)`) - - sql, _, err = ds1.ToInsertSql( - item{Address: "111 Test Addr", Name: "Test1", Phone: &Phone{Home: "123123", Primary: "456456"}}, - item{Address: "211 Test Addr", Name: "Test2", Phone: &Phone{Home: "123123", Primary: "456456"}}, - item{Address: "311 Test Addr", Name: "Test3", Phone: &Phone{Home: "123123", Primary: "456456"}}, - item{Address: "411 Test Addr", Name: "Test4", Phone: &Phone{Home: "123123", Primary: "456456"}}, - ) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name", "valuer") VALUES ('456456', '123123', '111 Test Addr', 'Test1', NULL), ('456456', '123123', '211 Test Addr', 'Test2', NULL), ('456456', '123123', '311 Test Addr', 'Test3', NULL), ('456456', '123123', '411 Test Addr', 'Test4', NULL)`) -} - -func (me *datasetTest) TestInsertSqlWithValuer() { - t := me.T() - ds1 := From("items") - - type item struct { - Address string `db:"address"` - Name string `db:"name"` - Valuer sql.NullInt64 `db:"valuer"` - } - sqlString, _, err := ds1.ToInsertSql(item{Name: "Test", Address: "111 Test Addr", Valuer: sql.NullInt64{Int64: 10, Valid: true}}) - assert.NoError(t, err) - assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES ('111 Test Addr', 'Test', 10)`) - - sqlString, _, err = ds1.ToInsertSql( - item{Address: "111 Test Addr", Name: "Test1", Valuer: sql.NullInt64{Int64: 10, Valid: true}}, - item{Address: "211 Test Addr", Name: "Test2", Valuer: sql.NullInt64{Int64: 10, Valid: true}}, - item{Address: "311 Test Addr", Name: "Test3", Valuer: sql.NullInt64{Int64: 10, Valid: true}}, - item{Address: "411 Test Addr", Name: "Test4", Valuer: sql.NullInt64{Int64: 10, Valid: true}}, - ) - assert.NoError(t, err) - assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES ('111 Test Addr', 'Test1', 10), ('211 Test Addr', 'Test2', 10), ('311 Test Addr', 'Test3', 10), ('411 Test Addr', 'Test4', 10)`) -} - -func (me *datasetTest) TestInsertSqlWithValuerNull() { - t := me.T() - ds1 := From("items") - - type item struct { - Address string `db:"address"` - Name string `db:"name"` - Valuer sql.NullInt64 `db:"valuer"` - } - sqlString, _, err := ds1.ToInsertSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES ('111 Test Addr', 'Test', NULL)`) - - sqlString, _, err = ds1.ToInsertSql( - item{Address: "111 Test Addr", Name: "Test1"}, - item{Address: "211 Test Addr", Name: "Test2"}, - item{Address: "311 Test Addr", Name: "Test3"}, - item{Address: "411 Test Addr", Name: "Test4"}, - ) - assert.NoError(t, err) - assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES ('111 Test Addr', 'Test1', NULL), ('211 Test Addr', 'Test2', NULL), ('311 Test Addr', 'Test3', NULL), ('411 Test Addr', 'Test4', NULL)`) -} - -func (me *datasetTest) TestInsertSqlWithMaps() { - t := me.T() - ds1 := From("items") - - sql, _, err := ds1.ToInsertSql(map[string]interface{}{"name": "Test", "address": "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test')`) - - sql, _, err = ds1.ToInsertSql( - map[string]interface{}{"address": "111 Test Addr", "name": "Test1"}, - map[string]interface{}{"address": "211 Test Addr", "name": "Test2"}, - map[string]interface{}{"address": "311 Test Addr", "name": "Test3"}, - map[string]interface{}{"address": "411 Test Addr", "name": "Test4"}, - ) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('211 Test Addr', 'Test2'), ('311 Test Addr', 'Test3'), ('411 Test Addr', 'Test4')`) - - _, _, err = ds1.ToInsertSql( - map[string]interface{}{"address": "111 Test Addr", "name": "Test1"}, - map[string]interface{}{"address": "211 Test Addr"}, - map[string]interface{}{"address": "311 Test Addr", "name": "Test3"}, - map[string]interface{}{"address": "411 Test Addr", "name": "Test4"}, - ) - assert.EqualError(t, err, "goqu: Rows with different value length expected 2 got 1") -} - -func (me *datasetTest) TestInsertSqlWitSqlBuilder() { - t := me.T() - ds1 := From("items") - - sql, _, err := ds1.ToInsertSql(From("other_items")) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" SELECT * FROM "other_items"`) -} - -func (me *datasetTest) TestInsertReturning() { - t := me.T() - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - ds1 := From("items").Returning("id") - - sql, _, err := ds1.Returning("id").ToInsertSql(From("other_items")) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" SELECT * FROM "other_items" RETURNING "id"`) - - sql, _, err = ds1.ToInsertSql(map[string]interface{}{"name": "Test", "address": "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test') RETURNING "id"`) - - sql, _, err = ds1.ToInsertSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test') RETURNING "id"`) -} - -func (me *datasetTest) TestInsertSqlWithNoFrom() { - t := me.T() - ds1 := From("test").From() - _, _, err := ds1.ToInsertSql(map[string]interface{}{"address": "111 Test Addr", "name": "Test1"}) - assert.EqualError(t, err, "goqu: No source found when generating insert sql") -} - -func (me *datasetTest) TestInsertSqlWithMapsWithDifferentLengths() { - t := me.T() - ds1 := From("items") - _, _, err := ds1.ToInsertSql( - map[string]interface{}{"address": "111 Test Addr", "name": "Test1"}, - map[string]interface{}{"address": "211 Test Addr"}, - map[string]interface{}{"address": "311 Test Addr", "name": "Test3"}, - map[string]interface{}{"address": "411 Test Addr", "name": "Test4"}, - ) - assert.EqualError(t, err, "goqu: Rows with different value length expected 2 got 1") -} - -func (me *datasetTest) TestInsertSqlWitDifferentKeys() { - t := me.T() - ds1 := From("items") - _, _, err := ds1.ToInsertSql( - map[string]interface{}{"address": "111 Test Addr", "name": "test"}, - map[string]interface{}{"phoneNumber": 10, "address": "111 Test Addr"}, - ) - assert.EqualError(t, err, `goqu: Rows with different keys expected ["address","name"] got ["address","phoneNumber"]`) -} - -func (me *datasetTest) TestInsertSqlDifferentTypes() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - type item2 struct { - Address string `db:"address"` - Name string `db:"name"` - } - _, _, err := ds1.ToInsertSql( - item{Address: "111 Test Addr", Name: "Test1"}, - item2{Address: "211 Test Addr", Name: "Test2"}, - item{Address: "311 Test Addr", Name: "Test3"}, - item2{Address: "411 Test Addr", Name: "Test4"}, - ) - assert.EqualError(t, err, "goqu: Rows must be all the same type expected goqu.item got goqu.item2") - - _, _, err = ds1.ToInsertSql( - item{Address: "111 Test Addr", Name: "Test1"}, - map[string]interface{}{"address": "211 Test Addr", "name": "Test2"}, - item{Address: "311 Test Addr", Name: "Test3"}, - map[string]interface{}{"address": "411 Test Addr", "name": "Test4"}, - ) - assert.EqualError(t, err, "goqu: Rows must be all the same type expected goqu.item got map[string]interface {}") -} - -func (me *datasetTest) TestInsertWithGoquPkTagSql() { - t := me.T() - ds1 := From("items") - type item struct { - Id uint32 `db:"id" goqu:"pk,skipinsert"` - Address string `db:"address"` - Name string `db:"name"` - } - sql, _, err := ds1.ToInsertSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test')`) - - sql, _, err = ds1.ToInsertSql(map[string]interface{}{"name": "Test", "address": "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test')`) - - sql, _, err = ds1.ToInsertSql( - item{Name: "Test1", Address: "111 Test Addr"}, - item{Name: "Test2", Address: "211 Test Addr"}, - item{Name: "Test3", Address: "311 Test Addr"}, - item{Name: "Test4", Address: "411 Test Addr"}, - ) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('211 Test Addr', 'Test2'), ('311 Test Addr', 'Test3'), ('411 Test Addr', 'Test4')`) -} - -func (me *datasetTest) TestInsertWithGoquSkipInsertTagSql() { - t := me.T() - ds1 := From("items") - type item struct { - Id uint32 `db:"id" goqu:"skipinsert"` - Address string `db:"address"` - Name string `db:"name"` - } - sql, _, err := ds1.ToInsertSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test')`) - - sql, _, err = ds1.ToInsertSql( - item{Name: "Test1", Address: "111 Test Addr"}, - item{Name: "Test2", Address: "211 Test Addr"}, - item{Name: "Test3", Address: "311 Test Addr"}, - item{Name: "Test4", Address: "411 Test Addr"}, - ) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('211 Test Addr', 'Test2'), ('311 Test Addr', 'Test3'), ('411 Test Addr', 'Test4')`) -} - -func (me *datasetTest) TestInsertDefaultValues() { - t := me.T() - ds1 := From("items") - - sql, _, err := ds1.ToInsertSql() - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" DEFAULT VALUES`) - - sql, _, err = ds1.ToInsertSql(map[string]interface{}{"name": Default(), "address": Default()}) - assert.NoError(t, err) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (DEFAULT, DEFAULT)`) - -} - -func (me *datasetTest) TestPreparedInsertSqlWithStructs() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - sql, args, err := ds1.Prepared(true).ToInsertSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (?, ?)`) - - sql, args, err = ds1.Prepared(true).ToInsertSql( - item{Address: "111 Test Addr", Name: "Test1"}, - item{Address: "211 Test Addr", Name: "Test2"}, - item{Address: "311 Test Addr", Name: "Test3"}, - item{Address: "411 Test Addr", Name: "Test4"}, - ) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test1", "211 Test Addr", "Test2", "311 Test Addr", "Test3", "411 Test Addr", "Test4"}) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?), (?, ?), (?, ?)`) -} - -func (me *datasetTest) TestPreparedInsertSqlWithMaps() { - t := me.T() - ds1 := From("items") - - sql, args, err := ds1.Prepared(true).ToInsertSql(map[string]interface{}{"name": "Test", "address": "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (?, ?)`) - - sql, args, err = ds1.Prepared(true).ToInsertSql( - map[string]interface{}{"address": "111 Test Addr", "name": "Test1"}, - map[string]interface{}{"address": "211 Test Addr", "name": "Test2"}, - map[string]interface{}{"address": "311 Test Addr", "name": "Test3"}, - map[string]interface{}{"address": "411 Test Addr", "name": "Test4"}, - ) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test1", "211 Test Addr", "Test2", "311 Test Addr", "Test3", "411 Test Addr", "Test4"}) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?), (?, ?), (?, ?)`) -} - -func (me *datasetTest) TestPreparedInsertSqlWitSqlBuilder() { - t := me.T() - ds1 := From("items") - - sql, args, err := ds1.Prepared(true).ToInsertSql(From("other_items").Where(I("b").Gt(10))) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(10)}) - assert.Equal(t, sql, `INSERT INTO "items" SELECT * FROM "other_items" WHERE ("b" > ?)`) -} - -func (me *datasetTest) TestPreparedInsertReturning() { - t := me.T() - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - ds1 := From("items").Returning("id") - - sql, args, err := ds1.Returning("id").Prepared(true).ToInsertSql(From("other_items").Where(I("b").Gt(10))) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(10)}) - assert.Equal(t, sql, `INSERT INTO "items" SELECT * FROM "other_items" WHERE ("b" > ?) RETURNING "id"`) - - sql, args, err = ds1.Prepared(true).ToInsertSql(map[string]interface{}{"name": "Test", "address": "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (?, ?) RETURNING "id"`) - - sql, args, err = ds1.Prepared(true).ToInsertSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (?, ?) RETURNING "id"`) -} - -func (me *datasetTest) TestPreparedInsertWithGoquPkTagSql() { - t := me.T() - ds1 := From("items") - type item struct { - Id uint32 `db:"id" goqu:"pk,skipinsert"` - Address string `db:"address"` - Name string `db:"name"` - } - sql, args, err := ds1.Prepared(true).ToInsertSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (?, ?)`) - - sql, args, err = ds1.Prepared(true).ToInsertSql(map[string]interface{}{"name": "Test", "address": "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (?, ?)`) - - sql, args, err = ds1.Prepared(true).ToInsertSql( - item{Name: "Test1", Address: "111 Test Addr"}, - item{Name: "Test2", Address: "211 Test Addr"}, - item{Name: "Test3", Address: "311 Test Addr"}, - item{Name: "Test4", Address: "411 Test Addr"}, - ) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test1", "211 Test Addr", "Test2", "311 Test Addr", "Test3", "411 Test Addr", "Test4"}) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?), (?, ?), (?, ?)`) -} - -func (me *datasetTest) TestPreparedInsertWithGoquSkipInsertTagSql() { - t := me.T() - ds1 := From("items") - type item struct { - Id uint32 `db:"id" goqu:"skipinsert"` - Address string `db:"address"` - Name string `db:"name"` - } - sql, args, err := ds1.Prepared(true).ToInsertSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (?, ?)`) - - sql, args, err = ds1.Prepared(true).ToInsertSql( - item{Name: "Test1", Address: "111 Test Addr"}, - item{Name: "Test2", Address: "211 Test Addr"}, - item{Name: "Test3", Address: "311 Test Addr"}, - item{Name: "Test4", Address: "411 Test Addr"}, - ) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test1", "211 Test Addr", "Test2", "311 Test Addr", "Test3", "411 Test Addr", "Test4"}) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?), (?, ?), (?, ?)`) -} - -func (me *datasetTest) TestPreparedInsertDefaultValues() { - t := me.T() - ds1 := From("items") - - sql, args, err := ds1.Prepared(true).ToInsertSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{}) - assert.Equal(t, sql, `INSERT INTO "items" DEFAULT VALUES`) - - sql, args, err = ds1.Prepared(true).ToInsertSql(map[string]interface{}{"name": Default(), "address": Default()}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{}) - assert.Equal(t, sql, `INSERT INTO "items" ("address", "name") VALUES (DEFAULT, DEFAULT)`) - -} - -func (me *datasetTest) TestPreparedInsertSqlWithEmbeddedStruct() { - t := me.T() - ds1 := From("items") - type Phone struct { - Primary string `db:"primary_phone"` - Home string `db:"home_phone"` - } - type item struct { - Phone - Address string `db:"address"` - Name string `db:"name"` - } - sql, args, err := ds1.Prepared(true).ToInsertSql(item{Name: "Test", Address: "111 Test Addr", Phone: Phone{Home: "123123", Primary: "456456"}}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"456456", "123123", "111 Test Addr", "Test"}) - assert.Equal(t, sql, `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name") VALUES (?, ?, ?, ?)`) - - sql, args, err = ds1.Prepared(true).ToInsertSql( - item{Address: "111 Test Addr", Name: "Test1", Phone: Phone{Home: "123123", Primary: "456456"}}, - item{Address: "211 Test Addr", Name: "Test2", Phone: Phone{Home: "123123", Primary: "456456"}}, - item{Address: "311 Test Addr", Name: "Test3", Phone: Phone{Home: "123123", Primary: "456456"}}, - item{Address: "411 Test Addr", Name: "Test4", Phone: Phone{Home: "123123", Primary: "456456"}}, - ) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{ - "456456", "123123", "111 Test Addr", "Test1", - "456456", "123123", "211 Test Addr", "Test2", - "456456", "123123", "311 Test Addr", "Test3", - "456456", "123123", "411 Test Addr", "Test4", - }) - assert.Equal(t, sql, `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name") VALUES (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?)`) -} - -func (me *datasetTest) TestPreparedInsertSqlWithEmbeddedStructPtr() { - t := me.T() - ds1 := From("items") - type Phone struct { - Primary string `db:"primary_phone"` - Home string `db:"home_phone"` - } - type item struct { - *Phone - Address string `db:"address"` - Name string `db:"name"` - } - sql, args, err := ds1.Prepared(true).ToInsertSql(item{Name: "Test", Address: "111 Test Addr", Phone: &Phone{Home: "123123", Primary: "456456"}}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"456456", "123123", "111 Test Addr", "Test"}) - assert.Equal(t, sql, `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name") VALUES (?, ?, ?, ?)`) - - sql, args, err = ds1.Prepared(true).ToInsertSql( - item{Address: "111 Test Addr", Name: "Test1", Phone: &Phone{Home: "123123", Primary: "456456"}}, - item{Address: "211 Test Addr", Name: "Test2", Phone: &Phone{Home: "123123", Primary: "456456"}}, - item{Address: "311 Test Addr", Name: "Test3", Phone: &Phone{Home: "123123", Primary: "456456"}}, - item{Address: "411 Test Addr", Name: "Test4", Phone: &Phone{Home: "123123", Primary: "456456"}}, - ) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{ - "456456", "123123", "111 Test Addr", "Test1", - "456456", "123123", "211 Test Addr", "Test2", - "456456", "123123", "311 Test Addr", "Test3", - "456456", "123123", "411 Test Addr", "Test4", - }) - assert.Equal(t, sql, `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name") VALUES (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?)`) -} - -func (me *datasetTest) TestPreparedInsertSqlWithValuer() { - t := me.T() - ds1 := From("items") - - type item struct { - Address string `db:"address"` - Name string `db:"name"` - Valuer sql.NullInt64 `db:"valuer"` - } - sqlString, args, err := ds1.Prepared(true).ToInsertSql(item{Name: "Test", Address: "111 Test Addr", Valuer: sql.NullInt64{Int64: 10, Valid: true}}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{ - "111 Test Addr", "Test", int64(10), - }) - assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES (?, ?, ?)`) - - sqlString, args, err = ds1.Prepared(true).ToInsertSql( - item{Address: "111 Test Addr", Name: "Test1", Valuer: sql.NullInt64{Int64: 10, Valid: true}}, - item{Address: "211 Test Addr", Name: "Test2", Valuer: sql.NullInt64{Int64: 20, Valid: true}}, - item{Address: "311 Test Addr", Name: "Test3", Valuer: sql.NullInt64{Int64: 30, Valid: true}}, - item{Address: "411 Test Addr", Name: "Test4", Valuer: sql.NullInt64{Int64: 40, Valid: true}}, - ) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{ - "111 Test Addr", "Test1", int64(10), - "211 Test Addr", "Test2", int64(20), - "311 Test Addr", "Test3", int64(30), - "411 Test Addr", "Test4", int64(40), - }) - assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?)`) -} - -func (me *datasetTest) TestPreparedInsertSqlWithValuerNull() { - t := me.T() - ds1 := From("items") - - type item struct { - Address string `db:"address"` - Name string `db:"name"` - Valuer sql.NullInt64 `db:"valuer"` - } - sqlString, args, err := ds1.Prepared(true).ToInsertSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{ - "111 Test Addr", "Test", - }) - assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES (?, ?, NULL)`) - - sqlString, args, err = ds1.Prepared(true).ToInsertSql( - item{Address: "111 Test Addr", Name: "Test1"}, - item{Address: "211 Test Addr", Name: "Test2"}, - item{Address: "311 Test Addr", Name: "Test3"}, - item{Address: "411 Test Addr", Name: "Test4"}, - ) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{ - "111 Test Addr", "Test1", - "211 Test Addr", "Test2", - "311 Test Addr", "Test3", - "411 Test Addr", "Test4", - }) - assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES (?, ?, NULL), (?, ?, NULL), (?, ?, NULL), (?, ?, NULL)`) -} - -func (me *datasetTest) TestInsertConflictSql__OnConflictIsNil() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - sql, _, err := ds1.ToInsertConflictSql(nil, item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test')`, sql) -} - -func (me *datasetTest) TestInsertConflictSql__OnConflictDoUpdate() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - i := item{Name: "Test", Address: "111 Test Addr"} - sql, _, err := ds1.ToInsertConflictSql(DoUpdate("name", Record{"address": L("excluded.address")}), i) - assert.NoError(t, err) - assert.Equal(t, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test') ON CONFLICT (name) DO UPDATE SET "address"=excluded.address`, sql) -} - -func (me *datasetTest) TestInsertConflictSql__OnConflictDoUpdateWhere() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - i := item{Name: "Test", Address: "111 Test Addr"} - - sql, _, err := ds1.ToInsertConflictSql(DoUpdate("name", Record{"address": L("excluded.address")}).Where(I("name").Eq("Test")), i) - assert.NoError(t, err) - assert.Equal(t, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test') ON CONFLICT (name) DO UPDATE SET "address"=excluded.address WHERE ("name" = 'Test')`, sql) -} - -func (me *datasetTest) TestInsertConflictSqlWithDataset__OnConflictDoUpdateWhere() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - - ds2 := From("ds2") - - sql, _, err := ds1.ToInsertConflictSql(DoUpdate("name", Record{"address": L("excluded.address")}).Where(I("name").Eq("Test")), ds2) - assert.NoError(t, err) - assert.Equal(t, `INSERT INTO "items" SELECT * FROM "ds2" ON CONFLICT (name) DO UPDATE SET "address"=excluded.address WHERE ("name" = 'Test')`, sql) -} - -func (me *datasetTest) TestInsertIgnoreSql() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - sql, _, err := ds1.ToInsertIgnoreSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test') ON CONFLICT DO NOTHING`, sql) -} - -func (me *datasetTest) TestInsertConflict__ImplementsConflictExpressionInterface() { - t := me.T() - assert.Implements(t, (*ConflictExpression)(nil), DoNothing()) - assert.Implements(t, (*ConflictExpression)(nil), DoUpdate("", nil)) -} diff --git a/dataset_query_example_test.go b/dataset_query_example_test.go new file mode 100644 index 00000000..84a81b45 --- /dev/null +++ b/dataset_query_example_test.go @@ -0,0 +1,374 @@ +package goqu_test + +import ( + "database/sql" + "fmt" + "os" + "time" + + "github.com/doug-martin/goqu/v7" + _ "github.com/doug-martin/goqu/v7/dialect/postgres" + "github.com/lib/pq" +) + +const schema = ` + DROP TABLE IF EXISTS "goqu_user"; + CREATE TABLE "goqu_user" ( + "id" SERIAL PRIMARY KEY NOT NULL, + "first_name" VARCHAR(45) NOT NULL, + "last_name" VARCHAR(45) NOT NULL, + "created" TIMESTAMP NOT NULL DEFAULT now() + ); + INSERT INTO "goqu_user" ("first_name", "last_name") VALUES + ('Bob', 'Yukon'), + ('Sally', 'Yukon'), + ('Vinita', 'Yukon'), + ('John', 'Doe') + ` + +const defaultDbURI = "postgres://postgres:@localhost:5435/goqupostgres?sslmode=disable" + +var goquDb *goqu.Database + +func getDb() *goqu.Database { + if goquDb == nil { + dbURI := os.Getenv("PG_URI") + if dbURI == "" { + dbURI = defaultDbURI + } + uri, err := pq.ParseURL(dbURI) + if err != nil { + panic(err) + } + pdb, err := sql.Open("postgres", uri) + if err != nil { + panic(err) + } + goquDb = goqu.New("postgres", pdb) + } + // reset the db + if _, err := goquDb.Exec(schema); err != nil { + panic(err) + } + return goquDb +} + +func ExampleDataset_ScanStructs() { + type User struct { + FirstName string `db:"first_name"` + LastName string `db:"last_name"` + } + db := getDb() + var users []User + if err := db.From("goqu_user").ScanStructs(&users); err != nil { + fmt.Println(err.Error()) + return + } + fmt.Printf("\n%+v", users) + + users = users[0:0] + if err := db.From("goqu_user").Select("first_name").ScanStructs(&users); err != nil { + fmt.Println(err.Error()) + return + } + fmt.Printf("\n%+v", users) + + // Output: + // [{FirstName:Bob LastName:Yukon} {FirstName:Sally LastName:Yukon} {FirstName:Vinita LastName:Yukon} {FirstName:John LastName:Doe}] + // [{FirstName:Bob LastName:} {FirstName:Sally LastName:} {FirstName:Vinita LastName:} {FirstName:John LastName:}] +} + +func ExampleDataset_ScanStructs_prepared() { + type User struct { + FirstName string `db:"first_name"` + LastName string `db:"last_name"` + } + db := getDb() + + ds := db.From("goqu_user"). + Prepared(true). + Where(goqu.Ex{ + "last_name": "Yukon", + }) + + var users []User + if err := ds.ScanStructs(&users); err != nil { + fmt.Println(err.Error()) + return + } + fmt.Printf("\n%+v", users) + + // Output: + // [{FirstName:Bob LastName:Yukon} {FirstName:Sally LastName:Yukon} {FirstName:Vinita LastName:Yukon}] +} + +func ExampleDataset_ScanStruct() { + type User struct { + FirstName string `db:"first_name"` + LastName string `db:"last_name"` + } + db := getDb() + findUserByName := func(name string) { + var user User + ds := db.From("goqu_user").Where(goqu.C("first_name").Eq(name)) + found, err := ds.ScanStruct(&user) + switch { + case err != nil: + fmt.Println(err.Error()) + case !found: + fmt.Printf("No user found for first_name %s\n", name) + default: + fmt.Printf("Found user: %+v\n", user) + } + } + + findUserByName("Bob") + findUserByName("Zeb") + + // Output: + // Found user: {FirstName:Bob LastName:Yukon} + // No user found for first_name Zeb +} + +func ExampleDataset_ScanVals() { + var ids []int64 + if err := getDb().From("goqu_user").Select("id").ScanVals(&ids); err != nil { + fmt.Println(err.Error()) + return + } + fmt.Printf("UserIds = %+v", ids) + + // Output: + // UserIds = [1 2 3 4] +} + +func ExampleDataset_ScanVal() { + + db := getDb() + findUserIDByName := func(name string) { + var id int64 + ds := db.From("goqu_user"). + Select("id"). + Where(goqu.C("first_name").Eq(name)) + + found, err := ds.ScanVal(&id) + switch { + case err != nil: + fmt.Println(err.Error()) + case !found: + fmt.Printf("No id found for user %s", name) + default: + fmt.Printf("\nFound userId: %+v\n", id) + } + } + + findUserIDByName("Bob") + findUserIDByName("Zeb") + // Output: + // Found userId: 1 + // No id found for user Zeb +} + +func ExampleDataset_Count() { + + if count, err := getDb().From("goqu_user").Count(); err != nil { + fmt.Println(err.Error()) + } else { + fmt.Printf("\nCount:= %d", count) + } + + // Output: + // Count:= 4 +} + +func ExampleDataset_Pluck() { + var lastNames []string + if err := getDb().From("goqu_user").Pluck(&lastNames, "last_name"); err != nil { + fmt.Println(err.Error()) + return + } + fmt.Printf("LastNames := %+v", lastNames) + + // Output: + // LastNames := [Yukon Yukon Yukon Doe] +} + +func ExampleDataset_Insert_recordExec() { + db := getDb() + insert := db.From("goqu_user").Insert( + goqu.Record{"first_name": "Jed", "last_name": "Riley", "created": time.Now()}, + ) + if _, err := insert.Exec(); err != nil { + fmt.Println(err.Error()) + } else { + fmt.Println("Inserted 1 user") + } + + users := []goqu.Record{ + {"first_name": "Greg", "last_name": "Farley", "created": time.Now()}, + {"first_name": "Jimmy", "last_name": "Stewart", "created": time.Now()}, + {"first_name": "Jeff", "last_name": "Jeffers", "created": time.Now()}, + } + if _, err := db.From("goqu_user").Insert(users).Exec(); err != nil { + fmt.Println(err.Error()) + } else { + fmt.Printf("Inserted %d users", len(users)) + } + + // Output: + // Inserted 1 user + // Inserted 3 users +} + +func ExampleDataset_Insert_recordReturning() { + db := getDb() + + type User struct { + ID sql.NullInt64 `db:"id"` + FirstName string `db:"first_name"` + LastName string `db:"last_name"` + Created time.Time `db:"created"` + } + + insert := db.From("goqu_user").Returning(goqu.C("id")).Insert( + goqu.Record{"first_name": "Jed", "last_name": "Riley", "created": time.Now()}, + ) + var id int64 + if _, err := insert.ScanVal(&id); err != nil { + fmt.Println(err.Error()) + } else { + fmt.Printf("Inserted 1 user id:=%d\n", id) + } + + insert = db.From("goqu_user").Returning(goqu.Star()).Insert([]goqu.Record{ + {"first_name": "Greg", "last_name": "Farley", "created": time.Now()}, + {"first_name": "Jimmy", "last_name": "Stewart", "created": time.Now()}, + {"first_name": "Jeff", "last_name": "Jeffers", "created": time.Now()}, + }) + var insertedUsers []User + if err := insert.ScanStructs(&insertedUsers); err != nil { + fmt.Println(err.Error()) + } else { + for _, u := range insertedUsers { + fmt.Printf("Inserted user: [ID=%d], [FirstName=%+s] [LastName=%s]\n", u.ID.Int64, u.FirstName, u.LastName) + } + + } + + // Output: + // Inserted 1 user id:=5 + // Inserted user: [ID=6], [FirstName=Greg] [LastName=Farley] + // Inserted user: [ID=7], [FirstName=Jimmy] [LastName=Stewart] + // Inserted user: [ID=8], [FirstName=Jeff] [LastName=Jeffers] +} + +func ExampleDataset_Insert_scanStructs() { + db := getDb() + + type User struct { + ID sql.NullInt64 `db:"id" goqu:"skipinsert"` + FirstName string `db:"first_name"` + LastName string `db:"last_name"` + Created time.Time `db:"created"` + } + + insert := db.From("goqu_user").Returning("id").Insert( + User{FirstName: "Jed", LastName: "Riley"}, + ) + var id int64 + if _, err := insert.ScanVal(&id); err != nil { + fmt.Println(err.Error()) + } else { + fmt.Printf("Inserted 1 user id:=%d\n", id) + } + + insert = db.From("goqu_user").Returning(goqu.Star()).Insert([]User{ + {FirstName: "Greg", LastName: "Farley", Created: time.Now()}, + {FirstName: "Jimmy", LastName: "Stewart", Created: time.Now()}, + {FirstName: "Jeff", LastName: "Jeffers", Created: time.Now()}, + }) + var insertedUsers []User + if err := insert.ScanStructs(&insertedUsers); err != nil { + fmt.Println(err.Error()) + } else { + for _, u := range insertedUsers { + fmt.Printf("Inserted user: [ID=%d], [FirstName=%+s] [LastName=%s]\n", u.ID.Int64, u.FirstName, u.LastName) + } + + } + + // Output: + // Inserted 1 user id:=5 + // Inserted user: [ID=6], [FirstName=Greg] [LastName=Farley] + // Inserted user: [ID=7], [FirstName=Jimmy] [LastName=Stewart] + // Inserted user: [ID=8], [FirstName=Jeff] [LastName=Jeffers] +} + +func ExampleDataset_Update() { + db := getDb() + update := db.From("goqu_user"). + Where(goqu.C("first_name").Eq("Bob")). + Update(goqu.Record{"first_name": "Bobby"}) + + if r, err := update.Exec(); err != nil { + fmt.Println(err.Error()) + } else { + c, _ := r.RowsAffected() + fmt.Printf("Updated %d users", c) + } + + // Output: + // Updated 1 users +} + +func ExampleDataset_Update_returning() { + db := getDb() + var ids []int64 + update := db.From("goqu_user"). + Where(goqu.Ex{"last_name": "Yukon"}). + Returning("id"). + Update(goqu.Record{"last_name": "ucon"}) + if err := update.ScanVals(&ids); err != nil { + fmt.Println(err.Error()) + } else { + fmt.Printf("Updated users with ids %+v", ids) + } + + // Output: + // Updated users with ids [1 2 3] +} +func ExampleDataset_Delete() { + db := getDb() + + de := db.From("goqu_user"). + Where(goqu.Ex{"first_name": "Bob"}). + Delete() + if r, err := de.Exec(); err != nil { + fmt.Println(err.Error()) + } else { + c, _ := r.RowsAffected() + fmt.Printf("Deleted %d users", c) + } + + // Output: + // Deleted 1 users +} + +func ExampleDataset_Delete_returning() { + db := getDb() + + de := db.From("goqu_user"). + Where(goqu.C("last_name").Eq("Yukon")). + Returning(goqu.C("id")). + Delete() + + var ids []int64 + if err := de.ScanVals(&ids); err != nil { + fmt.Println(err.Error()) + } else { + fmt.Printf("Deleted users [ids:=%+v]", ids) + } + + // Output: + // Deleted users [ids:=[1 2 3]] +} diff --git a/dataset_query_test.go b/dataset_query_test.go new file mode 100644 index 00000000..d66f4315 --- /dev/null +++ b/dataset_query_test.go @@ -0,0 +1,458 @@ +package goqu + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/doug-martin/goqu/v7/exec" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type dsTestActionItem struct { + Address string `db:"address"` + Name string `db:"name"` +} + +type datasetQuerySuite struct { + suite.Suite +} + +func (dqs *datasetQuerySuite) queryFactory(db exec.DbExecutor) exec.QueryFactory { + return exec.NewQueryFactory(db) +} + +func (dqs *datasetQuerySuite) TestScanStructs() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectQuery(`SELECT "address", "name" FROM "items"`). + WithArgs(). + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + + mock.ExpectQuery(`SELECT DISTINCT "name" FROM "items"`). + WithArgs(). + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + + mock.ExpectQuery(`SELECT "test" FROM "items"`). + WithArgs(). + WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2")) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + var items []dsTestActionItem + assert.NoError(t, ds.From("items").ScanStructs(&items)) + assert.Equal(t, items, []dsTestActionItem{ + {Address: "111 Test Addr", Name: "Test1"}, + {Address: "211 Test Addr", Name: "Test2"}, + }) + + items = items[0:0] + assert.NoError(t, ds.From("items").SelectDistinct("name").ScanStructs(&items)) + assert.Equal(t, items, []dsTestActionItem{ + {Address: "111 Test Addr", Name: "Test1"}, + {Address: "211 Test Addr", Name: "Test2"}, + }) + + items = items[0:0] + assert.EqualError(t, ds.From("items").ScanStructs(items), + "goqu: type must be a pointer to a slice when scanning into structs") + assert.EqualError(t, ds.From("items").ScanStructs(&dsTestActionItem{}), + "goqu: type must be a pointer to a slice when scanning into structs") + assert.EqualError(t, ds.From("items").Select("test").ScanStructs(&items), + `goqu: unable to find corresponding field to column "test" returned by query`) +} + +func (dqs *datasetQuerySuite) TestScanStructs_WithPreparedStatements() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectQuery( + `SELECT "address", "name" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\)`, + ). + WithArgs("111 Test Addr", "Bob", "Sally", "Billy"). + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + + mock.ExpectQuery( + `SELECT "test" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\)`, + ). + WithArgs("111 Test Addr", "Bob", "Sally", "Billy"). + WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2")) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + var items []dsTestActionItem + assert.NoError(t, ds.From("items").Prepared(true).Where(Ex{ + "name": []string{"Bob", "Sally", "Billy"}, + "address": "111 Test Addr", + }).ScanStructs(&items)) + assert.Equal(t, items, []dsTestActionItem{ + {Address: "111 Test Addr", Name: "Test1"}, + {Address: "211 Test Addr", Name: "Test2"}, + }) + + items = items[0:0] + assert.EqualError(t, ds.From("items").ScanStructs(items), + "goqu: type must be a pointer to a slice when scanning into structs") + assert.EqualError(t, ds.From("items").ScanStructs(&dsTestActionItem{}), + "goqu: type must be a pointer to a slice when scanning into structs") + assert.EqualError(t, ds.From("items"). + Prepared(true). + Select("test"). + Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). + ScanStructs(&items), `goqu: unable to find corresponding field to column "test" returned by query`) +} + +func (dqs *datasetQuerySuite) TestScanStruct() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectQuery(`SELECT "address", "name" FROM "items" LIMIT 1`). + WithArgs(). + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1")) + + mock.ExpectQuery(`SELECT DISTINCT "name" FROM "items" LIMIT 1`). + WithArgs(). + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1")) + + mock.ExpectQuery(`SELECT "test" FROM "items" LIMIT 1`). + WithArgs(). + WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2")) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + var item dsTestActionItem + found, err := ds.From("items").ScanStruct(&item) + assert.NoError(t, err) + assert.True(t, found) + assert.Equal(t, item.Address, "111 Test Addr") + assert.Equal(t, item.Name, "Test1") + + item = dsTestActionItem{} + found, err = ds.From("items").SelectDistinct("name").ScanStruct(&item) + assert.NoError(t, err) + assert.True(t, found) + assert.Equal(t, item.Address, "111 Test Addr") + assert.Equal(t, item.Name, "Test1") + + _, err = ds.From("items").ScanStruct(item) + assert.EqualError(t, err, "goqu: type must be a pointer to a struct when scanning into a struct") + _, err = ds.From("items").ScanStruct([]dsTestActionItem{}) + assert.EqualError(t, err, "goqu: type must be a pointer to a struct when scanning into a struct") + _, err = ds.From("items").Select("test").ScanStruct(&item) + assert.EqualError(t, err, `goqu: unable to find corresponding field to column "test" returned by query`) +} + +func (dqs *datasetQuerySuite) TestScanStruct_WithPreparedStatements() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectQuery( + `SELECT "address", "name" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\) LIMIT \?`, + ). + WithArgs("111 Test Addr", "Bob", "Sally", "Billy", 1). + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1")) + + mock.ExpectQuery(`SELECT "test" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\) LIMIT \?`). + WithArgs("111 Test Addr", "Bob", "Sally", "Billy", 1). + WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2")) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + var item dsTestActionItem + found, err := ds.From("items").Prepared(true).Where(Ex{ + "name": []string{"Bob", "Sally", "Billy"}, + "address": "111 Test Addr", + }).ScanStruct(&item) + assert.NoError(t, err) + assert.True(t, found) + assert.Equal(t, item.Address, "111 Test Addr") + assert.Equal(t, item.Name, "Test1") + + _, err = ds.From("items").ScanStruct(item) + assert.EqualError(t, err, "goqu: type must be a pointer to a struct when scanning into a struct") + _, err = ds.From("items").ScanStruct([]dsTestActionItem{}) + assert.EqualError(t, err, "goqu: type must be a pointer to a struct when scanning into a struct") + _, err = ds.From("items"). + Prepared(true). + Select("test"). + Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). + ScanStruct(&item) + assert.EqualError(t, err, `goqu: unable to find corresponding field to column "test" returned by query`) +} + +func (dqs *datasetQuerySuite) TestScanVals() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectQuery(`SELECT "id" FROM "items"`). + WithArgs(). + WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("1\n2\n3\n4\n5")) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + var ids []uint32 + assert.NoError(t, ds.From("items").Select("id").ScanVals(&ids)) + assert.Equal(t, ids, []uint32{1, 2, 3, 4, 5}) + + assert.EqualError(t, ds.From("items").ScanVals([]uint32{}), + "goqu: type must be a pointer to a slice when scanning into vals") + assert.EqualError(t, ds.From("items").ScanVals(dsTestActionItem{}), + "goqu: type must be a pointer to a slice when scanning into vals") +} + +func (dqs *datasetQuerySuite) TestScanVals_WithPreparedStatment() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectQuery( + `SELECT "id" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\)`, + ). + WithArgs("111 Test Addr", "Bob", "Sally", "Billy"). + WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("1\n2\n3\n4\n5")) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + var ids []uint32 + assert.NoError(t, ds.From("items"). + Prepared(true). + Select("id"). + Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). + ScanVals(&ids)) + assert.Equal(t, ids, []uint32{1, 2, 3, 4, 5}) + + assert.EqualError(t, ds.From("items").ScanVals([]uint32{}), + "goqu: type must be a pointer to a slice when scanning into vals") + assert.EqualError(t, ds.From("items").ScanVals(dsTestActionItem{}), + "goqu: type must be a pointer to a slice when scanning into vals") +} + +func (dqs *datasetQuerySuite) TestScanVal() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectQuery(`SELECT "id" FROM "items" LIMIT 1`). + WithArgs(). + WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("10")) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + var id int64 + found, err := ds.From("items").Select("id").ScanVal(&id) + assert.NoError(t, err) + assert.Equal(t, id, int64(10)) + assert.True(t, found) + + found, err = ds.From("items").ScanVal([]int64{}) + assert.False(t, found) + assert.EqualError(t, err, "goqu: type must be a pointer when scanning into val") + found, err = ds.From("items").ScanVal(10) + assert.False(t, found) + assert.EqualError(t, err, "goqu: type must be a pointer when scanning into val") +} + +func (dqs *datasetQuerySuite) TestScanVal_WithPreparedStatement() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectQuery( + `SELECT "id" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\) LIMIT ?`, + ). + WithArgs("111 Test Addr", "Bob", "Sally", "Billy", 1). + WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("10")) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + var id int64 + found, err := ds.From("items"). + Prepared(true). + Select("id"). + Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). + ScanVal(&id) + assert.NoError(t, err) + assert.Equal(t, id, int64(10)) + assert.True(t, found) + + found, err = ds.From("items").ScanVal([]int64{}) + assert.False(t, found) + assert.EqualError(t, err, "goqu: type must be a pointer when scanning into val") + found, err = ds.From("items").ScanVal(10) + assert.False(t, found) + assert.EqualError(t, err, "goqu: type must be a pointer when scanning into val") +} + +func (dqs *datasetQuerySuite) TestCount() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectQuery(`SELECT COUNT\(\*\) AS "count" FROM "items"`). + WithArgs(). + WillReturnRows(sqlmock.NewRows([]string{"count"}).FromCSVString("10")) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + count, err := ds.From("items").Count() + assert.NoError(t, err) + assert.Equal(t, count, int64(10)) +} + +func (dqs *datasetQuerySuite) TestCount_WithPreparedStatement() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectQuery( + `SELECT COUNT\(\*\) AS "count" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\)`, + ). + WithArgs("111 Test Addr", "Bob", "Sally", "Billy", 1). + WillReturnRows(sqlmock.NewRows([]string{"count"}).FromCSVString("10")) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + count, err := ds.From("items"). + Prepared(true). + Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). + Count() + assert.NoError(t, err) + assert.Equal(t, count, int64(10)) +} + +func (dqs *datasetQuerySuite) TestPluck() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectQuery(`SELECT "name" FROM "items"`). + WithArgs(). + WillReturnRows(sqlmock.NewRows([]string{"name"}).FromCSVString("test1\ntest2\ntest3\ntest4\ntest5")) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + var names []string + assert.NoError(t, ds.From("items").Pluck(&names, "name")) + assert.Equal(t, names, []string{"test1", "test2", "test3", "test4", "test5"}) +} + +func (dqs *datasetQuerySuite) TestPluck_WithPreparedStatement() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectQuery( + `SELECT "name" FROM "items" WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\)`, + ). + WithArgs("111 Test Addr", "Bob", "Sally", "Billy"). + WillReturnRows(sqlmock.NewRows([]string{"name"}).FromCSVString("Bob\nSally\nBilly")) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + var names []string + assert.NoError(t, ds.From("items"). + Prepared(true). + Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). + Pluck(&names, "name")) + assert.Equal(t, names, []string{"Bob", "Sally", "Billy"}) +} + +func (dqs *datasetQuerySuite) TestUpdate() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectExec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE \("name" IS NULL\)`). + WithArgs(). + WillReturnResult(sqlmock.NewResult(0, 0)) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + _, err = ds.From("items").Where(C("name").IsNull()).Update(Record{ + "address": "111 Test Addr", + "name": "Test1", + }).Exec() + assert.NoError(t, err) +} + +func (dqs *datasetQuerySuite) TestUpdate_WithPreparedStatement() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectExec( + `UPDATE "items" SET "address"=\?,"name"=\? WHERE \(\("address" = \?\) AND \("name" IN \(\?, \?, \?\)\)\)`, + ). + WithArgs("112 Test Addr", "Test1", "111 Test Addr", "Bob", "Sally", "Billy"). + WillReturnResult(sqlmock.NewResult(0, 0)) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + _, err = ds.From("items"). + Prepared(true). + Where(Ex{"name": []string{"Bob", "Sally", "Billy"}, "address": "111 Test Addr"}). + Update(Record{"address": "112 Test Addr", "name": "Test1"}). + Exec() + assert.NoError(t, err) +} + +func (dqs *datasetQuerySuite) TestInsert() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectExec(`INSERT INTO "items" \("address", "name"\) VALUES \('111 Test Addr', 'Test1'\)`). + WithArgs(). + WillReturnResult(sqlmock.NewResult(0, 0)) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + _, err = ds.From("items").Insert(Record{"address": "111 Test Addr", "name": "Test1"}).Exec() + assert.NoError(t, err) +} + +func (dqs *datasetQuerySuite) TestInsert_WithPreparedStatment() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectExec(`INSERT INTO "items" \("address", "name"\) VALUES \(\?, \?\), \(\?, \?\)`). + WithArgs("111 Test Addr", "Test1", "112 Test Addr", "Test2"). + WillReturnResult(sqlmock.NewResult(0, 0)) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + _, err = ds.From("items"). + Prepared(true). + Insert( + Record{"address": "111 Test Addr", "name": "Test1"}, + Record{"address": "112 Test Addr", "name": "Test2"}, + ). + Exec() + assert.NoError(t, err) +} + +func (dqs *datasetQuerySuite) TestDelete() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectExec(`DELETE FROM "items" WHERE \("id" > 10\)`). + WithArgs(). + WillReturnResult(sqlmock.NewResult(0, 0)) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + _, err = ds.From("items").Where(C("id").Gt(10)).Delete().Exec() + assert.NoError(t, err) +} + +func (dqs *datasetQuerySuite) TestDelete_WithPreparedStatment() { + t := dqs.T() + mDb, mock, err := sqlmock.New() + assert.NoError(t, err) + mock.ExpectExec(`DELETE FROM "items" WHERE \("id" > \?\)`). + WithArgs(10). + WillReturnResult(sqlmock.NewResult(0, 0)) + + qf := dqs.queryFactory(mDb) + ds := newDataset("mock", qf) + _, err = ds.From("items").Prepared(true).Where(Ex{"id": Op{"gt": 10}}).Delete().Exec() + assert.NoError(t, err) +} + +func TestDatasetQuerySuite(t *testing.T) { + suite.Run(t, new(datasetQuerySuite)) +} diff --git a/dataset_select.go b/dataset_select.go deleted file mode 100644 index 50ad6ac2..00000000 --- a/dataset_select.go +++ /dev/null @@ -1,447 +0,0 @@ -package goqu - -import ( - "fmt" -) - -var ( - conditioned_join_types = map[JoinType]bool{ - INNER_JOIN: true, - FULL_OUTER_JOIN: true, - RIGHT_OUTER_JOIN: true, - LEFT_OUTER_JOIN: true, - FULL_JOIN: true, - RIGHT_JOIN: true, - LEFT_JOIN: true, - } -) - -//Adds columns to the SELECT clause. See examples -//You can pass in the following. -// string: Will automatically be turned into an identifier -// Dataset: Will use the SQL generated from that Dataset. If the dataset is aliased it will use that alias as the column name. -// LiteralExpression: (See Literal) Will use the literal SQL -// SqlFunction: (See Func, MIN, MAX, COUNT....) -// Struct: If passing in an instance of a struct, we will parse the struct for the column names to select. See examples -func (me *Dataset) Select(selects ...interface{}) *Dataset { - ret := me.copy() - ret.clauses.SelectDistinct = nil - ret.clauses.Select = cols(selects...) - return ret -} - -//Adds columns to the SELECT DISTINCT clause. See examples -//You can pass in the following. -// string: Will automatically be turned into an identifier -// Dataset: Will use the SQL generated from that Dataset. If the dataset is aliased it will use that alias as the column name. -// LiteralExpression: (See Literal) Will use the literal SQL -// SqlFunction: (See Func, MIN, MAX, COUNT....) -// Struct: If passing in an instance of a struct, we will parse the struct for the column names to select. See examples -func (me *Dataset) SelectDistinct(selects ...interface{}) *Dataset { - ret := me.copy() - ret.clauses.Select = nil - ret.clauses.SelectDistinct = cols(selects...) - return ret -} - -//Resets to SELECT *. If the SelectDistinct was used the returned Dataset will have the the dataset set to SELECT *. See examples. -func (me *Dataset) ClearSelect() *Dataset { - ret := me.copy() - ret.clauses.Select = cols(Literal("*")) - ret.clauses.SelectDistinct = nil - return ret -} - -//Returns true if using default SELECT * -func (me *Dataset) isDefaultSelect() bool { - ret := false - if me.clauses.Select != nil { - selects := me.clauses.Select.Columns() - if len(selects) == 1 { - if l, ok := selects[0].(LiteralExpression); ok && l.Literal() == "*" { - ret = true - } - } - } - return ret -} - -//Adds columns to the SELECT clause. See examples -//You can pass in the following. -// string: Will automatically be turned into an identifier -// Dataset: Will use the SQL generated from that Dataset. If the dataset is aliased it will use that alias as the column name. -// LiteralExpression: (See Literal) Will use the literal SQL -// SqlFunction: (See Func, MIN, MAX, COUNT....) -func (me *Dataset) SelectAppend(selects ...interface{}) *Dataset { - ret := me.copy() - if ret.clauses.SelectDistinct != nil { - ret.clauses.SelectDistinct = ret.clauses.SelectDistinct.Append(cols(selects...).Columns()...) - } else { - ret.clauses.Select = ret.clauses.Select.Append(cols(selects...).Columns()...) - } - return ret -} - -//Adds a FROM clause. This return a new dataset with the original sources replaced. See examples. -//You can pass in the following. -// string: Will automatically be turned into an identifier -// Dataset: Will be added as a sub select. If the Dataset is not aliased it will automatically be aliased -// LiteralExpression: (See Literal) Will use the literal SQL -func (me *Dataset) From(from ...interface{}) *Dataset { - ret := me.copy() - var sources []interface{} - numSources := 0 - for _, source := range from { - if d, ok := source.(*Dataset); ok && d.clauses.Alias == nil { - numSources++ - sources = append(sources, d.As(fmt.Sprintf("t%d", numSources))) - } else { - sources = append(sources, source) - } - } - ret.clauses.From = cols(sources...) - return ret -} - -//Returns a new Dataset with the current one as an source. If the current Dataset is not aliased (See Dataset#As) then it will automatically be aliased. See examples. -func (me *Dataset) FromSelf() *Dataset { - builder := Dataset{} - builder.database = me.database - builder.adapter = me.adapter - builder.clauses = clauses{ - Select: cols(Star()), - } - return builder.From(me) - -} - -//Alias to InnerJoin. See examples. -func (me *Dataset) Join(table Expression, condition joinExpression) *Dataset { - return me.InnerJoin(table, condition) -} - -//Adds an INNER JOIN clause. See examples. -func (me *Dataset) InnerJoin(table Expression, condition joinExpression) *Dataset { - return me.joinTable(INNER_JOIN, table, condition) -} - -//Adds a FULL OUTER JOIN clause. See examples. -func (me *Dataset) FullOuterJoin(table Expression, condition joinExpression) *Dataset { - return me.joinTable(FULL_OUTER_JOIN, table, condition) -} - -//Adds a RIGHT OUTER JOIN clause. See examples. -func (me *Dataset) RightOuterJoin(table Expression, condition joinExpression) *Dataset { - return me.joinTable(RIGHT_OUTER_JOIN, table, condition) -} - -//Adds a LEFT OUTER JOIN clause. See examples. -func (me *Dataset) LeftOuterJoin(table Expression, condition joinExpression) *Dataset { - return me.joinTable(LEFT_OUTER_JOIN, table, condition) -} - -//Adds a FULL JOIN clause. See examples. -func (me *Dataset) FullJoin(table Expression, condition joinExpression) *Dataset { - return me.joinTable(FULL_JOIN, table, condition) -} - -//Adds a RIGHT JOIN clause. See examples. -func (me *Dataset) RightJoin(table Expression, condition joinExpression) *Dataset { - return me.joinTable(RIGHT_JOIN, table, condition) -} - -//Adds a LEFT JOIN clause. See examples. -func (me *Dataset) LeftJoin(table Expression, condition joinExpression) *Dataset { - return me.joinTable(LEFT_JOIN, table, condition) -} - -//Adds a NATURAL JOIN clause. See examples. -func (me *Dataset) NaturalJoin(table Expression) *Dataset { - return me.joinTable(NATURAL_JOIN, table, nil) -} - -//Adds a NATURAL LEFT JOIN clause. See examples. -func (me *Dataset) NaturalLeftJoin(table Expression) *Dataset { - return me.joinTable(NATURAL_LEFT_JOIN, table, nil) -} - -//Adds a NATURAL RIGHT JOIN clause. See examples. -func (me *Dataset) NaturalRightJoin(table Expression) *Dataset { - return me.joinTable(NATURAL_RIGHT_JOIN, table, nil) -} - -//Adds a NATURAL FULL JOIN clause. See examples. -func (me *Dataset) NaturalFullJoin(table Expression) *Dataset { - return me.joinTable(NATURAL_FULL_JOIN, table, nil) -} - -//Adds a CROSS JOIN clause. See examples. -func (me *Dataset) CrossJoin(table Expression) *Dataset { - return me.joinTable(CROSS_JOIN, table, nil) -} - -//Joins this Datasets table with another -func (me *Dataset) joinTable(joinType JoinType, table Expression, condition joinExpression) *Dataset { - ret := me.copy() - isConditioned := conditioned_join_types[joinType] - ret.clauses.Joins = append(ret.clauses.Joins, JoiningClause{JoinType: joinType, IsConditioned: isConditioned, Table: table, Condition: condition}) - return ret -} - -//Adds a WHERE clause. See examples. -func (me *Dataset) Where(expressions ...Expression) *Dataset { - expLen := len(expressions) - if expLen > 0 { - ret := me.copy() - if ret.clauses.Where == nil { - ret.clauses.Where = And(expressions...) - } else { - ret.clauses.Where = ret.clauses.Where.Append(expressions...) - } - return ret - } - return me -} - -//Removes the WHERE clause. See examples. -func (me *Dataset) ClearWhere() *Dataset { - ret := me.copy() - ret.clauses.Where = nil - return ret -} - -//Adds a FOR UPDATE clause. See examples. -func (me *Dataset) ForUpdate(waitOption WaitOption) *Dataset { - ret := me.copy() - ret.clauses.Lock = Lock{ - Strength: FOR_UPDATE, - WaitOption: waitOption, - } - return ret -} - -//Adds a FOR NO KEY UPDATE clause. See examples. -func (me *Dataset) ForNoKeyUpdate(waitOption WaitOption) *Dataset { - ret := me.copy() - ret.clauses.Lock = Lock{ - Strength: FOR_NO_KEY_UPDATE, - WaitOption: waitOption, - } - return ret -} - -//Adds a FOR KEY SHARE clause. See examples. -func (me *Dataset) ForKeyShare(waitOption WaitOption) *Dataset { - ret := me.copy() - ret.clauses.Lock = Lock{ - Strength: FOR_KEY_SHARE, - WaitOption: waitOption, - } - return ret -} - -//Adds a FOR SHARE clause. See examples. -func (me *Dataset) ForShare(waitOption WaitOption) *Dataset { - ret := me.copy() - ret.clauses.Lock = Lock{ - Strength: FOR_SHARE, - WaitOption: waitOption, - } - return ret -} - -//Adds a GROUP BY clause. See examples. -func (me *Dataset) GroupBy(groupBy ...interface{}) *Dataset { - ret := me.copy() - ret.clauses.GroupBy = cols(groupBy...) - return ret -} - -//Adds a HAVING clause. See examples. -func (me *Dataset) Having(expressions ...Expression) *Dataset { - expLen := len(expressions) - if expLen > 0 { - ret := me.copy() - if ret.clauses.Having == nil { - ret.clauses.Having = And(expressions...) - } else { - ret.clauses.Having = ret.clauses.Having.Append(expressions...) - } - return ret - } - return me -} - -//Adds a ORDER clause. If the ORDER is currently set it replaces it. See examples. -func (me *Dataset) Order(order ...OrderedExpression) *Dataset { - ret := me.copy() - ret.clauses.Order = orderList(order...) - return ret -} - -//Adds a more columns to the current ORDER BY clause. If no order has be previously specified it is the same as calling Order. See examples. -func (me *Dataset) OrderAppend(order ...OrderedExpression) *Dataset { - if me.clauses.Order == nil { - return me.Order(order...) - } else { - ret := me.copy() - ret.clauses.Order = ret.clauses.Order.Append(orderList(order...).Columns()...) - return ret - } - return me - -} - -//Removes the ORDER BY clause. See examples. -func (me *Dataset) ClearOrder() *Dataset { - ret := me.copy() - ret.clauses.Order = nil - return ret -} - -//Adds a LIMIT clause. If the LIMIT is currently set it replaces it. See examples. -func (me *Dataset) Limit(limit uint) *Dataset { - ret := me.copy() - if limit > 0 { - ret.clauses.Limit = limit - } else { - ret.clauses.Limit = nil - } - return ret -} - -//Adds a LIMIT ALL clause. If the LIMIT is currently set it replaces it. See examples. -func (me *Dataset) LimitAll() *Dataset { - ret := me.copy() - ret.clauses.Limit = Literal("ALL") - return ret -} - -//Removes the LIMIT clause. -func (me *Dataset) ClearLimit() *Dataset { - return me.Limit(0) -} - -//Adds an OFFSET clause. If the OFFSET is currently set it replaces it. See examples. -func (me *Dataset) Offset(offset uint) *Dataset { - ret := me.copy() - ret.clauses.Offset = offset - return ret -} - -//Removes the OFFSET clause from the Dataset -func (me *Dataset) ClearOffset() *Dataset { - return me.Offset(0) -} - -//Creates an UNION statement with another dataset. -// If this or the other dataset has a limit or offset it will use that dataset as a subselect in the FROM clause. See examples. -func (me *Dataset) Union(other *Dataset) *Dataset { - ret := me.compoundFromSelf() - ret.clauses.Compounds = append(ret.clauses.Compounds, Union(other.compoundFromSelf())) - return ret -} - -//Creates an UNION ALL statement with another dataset. -// If this or the other dataset has a limit or offset it will use that dataset as a subselect in the FROM clause. See examples. -func (me *Dataset) UnionAll(other *Dataset) *Dataset { - ret := me.compoundFromSelf() - ret.clauses.Compounds = append(ret.clauses.Compounds, UnionAll(other.compoundFromSelf())) - return ret -} - -//Creates an INTERSECT statement with another dataset. -// If this or the other dataset has a limit or offset it will use that dataset as a subselect in the FROM clause. See examples. -func (me *Dataset) Intersect(other *Dataset) *Dataset { - ret := me.compoundFromSelf() - ret.clauses.Compounds = append(ret.clauses.Compounds, Intersect(other.compoundFromSelf())) - return ret -} - -//Creates an INTERSECT ALL statement with another dataset. -// If this or the other dataset has a limit or offset it will use that dataset as a subselect in the FROM clause. See examples. -func (me *Dataset) IntersectAll(other *Dataset) *Dataset { - ret := me.compoundFromSelf() - ret.clauses.Compounds = append(ret.clauses.Compounds, IntersectAll(other.compoundFromSelf())) - return ret -} - -//Used internally to determine if the dataset needs to use iteself as a source. -//If the dataset has an order or limit it will select from itself -func (me *Dataset) compoundFromSelf() *Dataset { - if me.clauses.Order != nil || me.clauses.Limit != nil { - return me.FromSelf() - } - return me.copy() -} - -//Adds a RETURNING clause to the dataset if the adapter supports it. Typically used for INSERT, UPDATE or DELETE. See examples. -func (me *Dataset) Returning(returning ...interface{}) *Dataset { - ret := me.copy() - ret.clauses.Returning = cols(returning...) - return ret -} - -//Sets the alias for this dataset. This is typically used when using a Dataset as a subselect. See examples. -func (me *Dataset) As(alias string) *Dataset { - ret := me.copy() - ret.clauses.Alias = I(alias) - return ret -} - -//Generates a SELECT sql statement, if Prepared has been called with true then the parameters will not be interpolated. See examples. -// -//Errors: -// * There is an error generating the SQL -func (me *Dataset) ToSql() (string, []interface{}, error) { - buf := NewSqlBuilder(me.isPrepared) - if err := me.selectSqlWriteTo(buf); err != nil { - return "", nil, err - } - sql, args := buf.ToSql() - return sql, args, nil -} - -//Does actual sql generation of sql, accepts an sql builder so other methods can call when creating subselects and needing prepared sql. -func (me *Dataset) selectSqlWriteTo(buf *SqlBuilder) error { - if err := me.adapter.CommonTablesSql(buf, me.clauses.CommonTables); err != nil { - return err - } - if me.clauses.SelectDistinct != nil { - if err := me.adapter.SelectDistinctSql(buf, me.clauses.SelectDistinct); err != nil { - return err - } - } else { - if err := me.adapter.SelectSql(buf, me.clauses.Select); err != nil { - return err - } - } - if err := me.adapter.FromSql(buf, me.clauses.From); err != nil { - return err - } - if err := me.adapter.JoinSql(buf, me.clauses.Joins); err != nil { - return err - } - if err := me.adapter.WhereSql(buf, me.clauses.Where); err != nil { - return err - } - if err := me.adapter.GroupBySql(buf, me.clauses.GroupBy); err != nil { - return err - } - if err := me.adapter.HavingSql(buf, me.clauses.Having); err != nil { - return err - } - if err := me.adapter.CompoundsSql(buf, me.clauses.Compounds); err != nil { - return err - } - if err := me.adapter.OrderSql(buf, me.clauses.Order); err != nil { - return err - } - if err := me.adapter.LimitSql(buf, me.clauses.Limit); err != nil { - return err - } - if err := me.adapter.OffsetSql(buf, me.clauses.Offset); err != nil { - return err - } - return me.adapter.ForSql(buf, me.clauses.Lock) -} diff --git a/dataset_select_test.go b/dataset_select_test.go deleted file mode 100644 index fc5582b3..00000000 --- a/dataset_select_test.go +++ /dev/null @@ -1,1177 +0,0 @@ -package goqu - -import ( - "github.com/stretchr/testify/assert" -) - -func (me *datasetTest) TestSelect() { - t := me.T() - ds1 := From("test") - - sql, _, err := ds1.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) - - sql, _, err = ds1.Select().ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) - - sql, _, err = ds1.Select("id").ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id" FROM "test"`) - - sql, _, err = ds1.Select("id", "name").ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "name" FROM "test"`) - - sql, _, err = ds1.Select(Literal("COUNT(*)").As("count")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT COUNT(*) AS "count" FROM "test"`) - - sql, _, err = ds1.Select(I("id").As("other_id"), Literal("COUNT(*)").As("count")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id" AS "other_id", COUNT(*) AS "count" FROM "test"`) - - sql, _, err = ds1.From().Select(ds1.From("test_1").Select("id")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT (SELECT "id" FROM "test_1")`) - - sql, _, err = ds1.From().Select(ds1.From("test_1").Select("id").As("test_id")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT (SELECT "id" FROM "test_1") AS "test_id"`) - - sql, _, err = ds1.From(). - Select( - DISTINCT("a").As("distinct"), - COUNT("a").As("count"), - L("CASE WHEN ? THEN ? ELSE ? END", MIN("a").Eq(10), true, false), - L("CASE WHEN ? THEN ? ELSE ? END", AVG("a").Neq(10), true, false), - L("CASE WHEN ? THEN ? ELSE ? END", FIRST("a").Gt(10), true, false), - L("CASE WHEN ? THEN ? ELSE ? END", FIRST("a").Gte(10), true, false), - L("CASE WHEN ? THEN ? ELSE ? END", LAST("a").Lt(10), true, false), - L("CASE WHEN ? THEN ? ELSE ? END", LAST("a").Lte(10), true, false), - SUM("a").As("sum"), - COALESCE(I("a"), "a").As("colaseced"), - ).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT DISTINCT("a") AS "distinct", COUNT("a") AS "count", CASE WHEN (MIN("a") = 10) THEN TRUE ELSE FALSE END, CASE WHEN (AVG("a") != 10) THEN TRUE ELSE FALSE END, CASE WHEN (FIRST("a") > 10) THEN TRUE ELSE FALSE END, CASE WHEN (FIRST("a") >= 10) THEN TRUE ELSE FALSE END, CASE WHEN (LAST("a") < 10) THEN TRUE ELSE FALSE END, CASE WHEN (LAST("a") <= 10) THEN TRUE ELSE FALSE END, SUM("a") AS "sum", COALESCE("a", 'a') AS "colaseced"`) - - type MyStruct struct { - Name string - Address string `db:"address"` - EmailAddress string `db:"email_address"` - FakeCol string `db:"-"` - } - sql, _, err = ds1.Select(&MyStruct{}).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "address", "email_address", "name" FROM "test"`) - - sql, _, err = ds1.Select(MyStruct{}).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "address", "email_address", "name" FROM "test"`) - - type myStruct2 struct { - MyStruct - Zipcode string `db:"zipcode"` - } - - sql, _, err = ds1.Select(&myStruct2{}).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "address", "email_address", "name", "zipcode" FROM "test"`) - - sql, _, err = ds1.Select(myStruct2{}).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "address", "email_address", "name", "zipcode" FROM "test"`) - - var myStructs []MyStruct - sql, _, err = ds1.Select(&myStructs).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "address", "email_address", "name" FROM "test"`) - - sql, _, err = ds1.Select(myStructs).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "address", "email_address", "name" FROM "test"`) - - //should not change original - sql, _, err = ds1.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) -} - -func (me *datasetTest) TestSelectDistinct() { - t := me.T() - ds1 := From("test") - - sql, _, err := ds1.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) - - sql, _, err = ds1.SelectDistinct("id").ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT DISTINCT "id" FROM "test"`) - - sql, _, err = ds1.SelectDistinct("id", "name").ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT DISTINCT "id", "name" FROM "test"`) - - sql, _, err = ds1.SelectDistinct(Literal("COUNT(*)").As("count")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT DISTINCT COUNT(*) AS "count" FROM "test"`) - - sql, _, err = ds1.SelectDistinct(I("id").As("other_id"), Literal("COUNT(*)").As("count")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT DISTINCT "id" AS "other_id", COUNT(*) AS "count" FROM "test"`) - - type MyStruct struct { - Name string - Address string `db:"address"` - EmailAddress string `db:"email_address"` - FakeCol string `db:"-"` - } - sql, _, err = ds1.SelectDistinct(&MyStruct{}).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT DISTINCT "address", "email_address", "name" FROM "test"`) - - sql, _, err = ds1.SelectDistinct(MyStruct{}).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT DISTINCT "address", "email_address", "name" FROM "test"`) - - type myStruct2 struct { - MyStruct - Zipcode string `db:"zipcode"` - } - - sql, _, err = ds1.SelectDistinct(&myStruct2{}).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT DISTINCT "address", "email_address", "name", "zipcode" FROM "test"`) - - sql, _, err = ds1.SelectDistinct(myStruct2{}).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT DISTINCT "address", "email_address", "name", "zipcode" FROM "test"`) - - var myStructs []MyStruct - sql, _, err = ds1.SelectDistinct(&myStructs).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT DISTINCT "address", "email_address", "name" FROM "test"`) - - sql, _, err = ds1.SelectDistinct(myStructs).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT DISTINCT "address", "email_address", "name" FROM "test"`) - - //should not change original - sql, _, err = ds1.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) - - //should not change original - sql, _, err = ds1.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) -} - -func (me *datasetTest) TestClearSelect() { - t := me.T() - ds1 := From("test") - - sql, _, err := ds1.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) - - b := ds1.Select("a").ClearSelect() - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) -} - -func (me *datasetTest) TestSelectAppend() { - t := me.T() - ds1 := From("test") - - sql, _, err := ds1.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) - - b := ds1.Select("a").SelectAppend("b").SelectAppend("c") - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "a", "b", "c" FROM "test"`) -} - -func (me *datasetTest) TestFrom() { - t := me.T() - ds1 := From("test") - - sql, _, err := ds1.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) - - ds2 := ds1.From("test2") - sql, _, err = ds2.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test2"`) - - ds2 = ds1.From("test2", "test3") - sql, _, err = ds2.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test2", "test3"`) - - ds2 = ds1.From(I("test2").As("test_2"), "test3") - sql, _, err = ds2.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test2" AS "test_2", "test3"`) - - ds2 = ds1.From(ds1.From("test2"), "test3") - sql, _, err = ds2.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT * FROM "test2") AS "t1", "test3"`) - - ds2 = ds1.From(ds1.From("test2").As("test_2"), "test3") - sql, _, err = ds2.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT * FROM "test2") AS "test_2", "test3"`) - - //should not change original - sql, _, err = ds1.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) -} - -func (me *datasetTest) TestEmptyWhere() { - t := me.T() - ds1 := From("test") - - b := ds1.Where() - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) -} - -func (me *datasetTest) TestWhere() { - t := me.T() - ds1 := From("test") - - b := ds1.Where( - I("a").Eq(true), - I("a").Neq(true), - I("a").Eq(false), - I("a").Neq(false), - ) - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE (("a" IS TRUE) AND ("a" IS NOT TRUE) AND ("a" IS FALSE) AND ("a" IS NOT FALSE))`) - - b = ds1.Where( - I("a").Eq("a"), - I("b").Neq("b"), - I("c").Gt("c"), - I("d").Gte("d"), - I("e").Lt("e"), - I("f").Lte("f"), - ) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE (("a" = 'a') AND ("b" != 'b') AND ("c" > 'c') AND ("d" >= 'd') AND ("e" < 'e') AND ("f" <= 'f'))`) - - b = ds1.Where( - I("a").Eq(From("test2").Select("id")), - ) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" IN (SELECT "id" FROM "test2"))`) - - b = ds1.Where(Ex{ - "a": "a", - "b": Op{"neq": "b"}, - "c": Op{"gt": "c"}, - "d": Op{"gte": "d"}, - "e": Op{"lt": "e"}, - "f": Op{"lte": "f"}, - }) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE (("a" = 'a') AND ("b" != 'b') AND ("c" > 'c') AND ("d" >= 'd') AND ("e" < 'e') AND ("f" <= 'f'))`) - - b = ds1.Where(Ex{ - "a": From("test2").Select("id"), - }) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" IN (SELECT "id" FROM "test2"))`) -} - -func (me *datasetTest) TestWhereChain() { - t := me.T() - ds1 := From("test").Where( - I("x").Eq(0), - I("y").Eq(1), - ) - - ds2 := ds1.Where( - I("z").Eq(2), - ) - - a := ds2.Where( - I("a").Eq("A"), - ) - b := ds2.Where( - I("b").Eq("B"), - ) - sql, _, err := a.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE (("x" = 0) AND ("y" = 1) AND ("z" = 2) AND ("a" = 'A'))`) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE (("x" = 0) AND ("y" = 1) AND ("z" = 2) AND ("b" = 'B'))`) -} - -func (me *datasetTest) TestClearWhere() { - t := me.T() - ds1 := From("test") - - b := ds1.Where( - I("a").Eq(1), - ).ClearWhere() - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) -} - -func (me *datasetTest) TestLimit() { - t := me.T() - ds1 := From("test") - - b := ds1.Where( - I("a").Gt(1), - ).Limit(10) - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) LIMIT 10`) - - b = ds1.Where( - I("a").Gt(1), - ).Limit(0) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1)`) -} - -func (me *datasetTest) TestLimitAll() { - t := me.T() - ds1 := From("test") - - b := ds1.Where( - I("a").Gt(1), - ).LimitAll() - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) LIMIT ALL`) - - b = ds1.Where( - I("a").Gt(1), - ).Limit(0).LimitAll() - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) LIMIT ALL`) -} - -func (me *datasetTest) TestClearLimit() { - t := me.T() - ds1 := From("test") - - b := ds1.Where( - I("a").Gt(1), - ).LimitAll().ClearLimit() - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1)`) - - b = ds1.Where( - I("a").Gt(1), - ).Limit(10).ClearLimit() - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1)`) -} - -func (me *datasetTest) TestOffset() { - t := me.T() - ds1 := From("test") - - b := ds1.Where( - I("a").Gt(1), - ).Offset(10) - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) OFFSET 10`) - - b = ds1.Where( - I("a").Gt(1), - ).Offset(0) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1)`) -} - -func (me *datasetTest) TestClearOffset() { - t := me.T() - ds1 := From("test") - - b := ds1.Where( - I("a").Gt(1), - ).Offset(10).ClearOffset() - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1)`) -} - -func (me *datasetTest) TestForUpdate() { - t := me.T() - ds1 := From("test") - - b := ds1.Where( - I("a").Gt(1), - ).ForUpdate(WAIT) - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) FOR UPDATE `) - - b = ds1.Where( - I("a").Gt(1), - ).ForUpdate(SKIP_LOCKED) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) FOR UPDATE SKIP LOCKED`) -} - -func (me *datasetTest) TestForNoKeyUpdate() { - t := me.T() - ds1 := From("test") - - b := ds1.Where( - I("a").Gt(1), - ).ForNoKeyUpdate(WAIT) - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) FOR NO KEY UPDATE `) - - b = ds1.Where( - I("a").Gt(1), - ).ForNoKeyUpdate(SKIP_LOCKED) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) FOR NO KEY UPDATE SKIP LOCKED`) -} - -func (me *datasetTest) TestForKeyShare() { - t := me.T() - ds1 := From("test") - - b := ds1.Where( - I("a").Gt(1), - ).ForKeyShare(WAIT) - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) FOR KEY SHARE `) - - b = ds1.Where( - I("a").Gt(1), - ).ForKeyShare(SKIP_LOCKED) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) FOR KEY SHARE SKIP LOCKED`) -} - -func (me *datasetTest) TestForShare() { - t := me.T() - ds1 := From("test") - - b := ds1.Where( - I("a").Gt(1), - ).ForShare(WAIT) - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) FOR SHARE `) - - b = ds1.Where( - I("a").Gt(1), - ).ForShare(SKIP_LOCKED) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) FOR SHARE SKIP LOCKED`) -} - -func (me *datasetTest) TestGroupBy() { - t := me.T() - ds1 := From("test") - - b := ds1.Where( - I("a").Gt(1), - ).GroupBy("created") - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) GROUP BY "created"`) - - b = ds1.Where( - I("a").Gt(1), - ).GroupBy(Literal("created::DATE")) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) GROUP BY created::DATE`) - - b = ds1.Where( - I("a").Gt(1), - ).GroupBy("name", Literal("created::DATE")) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > 1) GROUP BY "name", created::DATE`) -} - -func (me *datasetTest) TestHaving() { - t := me.T() - ds1 := From("test") - - b := ds1.Having(Ex{ - "a": Op{"gt": 1}, - }).GroupBy("created") - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" GROUP BY "created" HAVING ("a" > 1)`) - - b = ds1.Where(Ex{"b": true}). - Having(Ex{"a": Op{"gt": 1}}). - GroupBy("created") - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("b" IS TRUE) GROUP BY "created" HAVING ("a" > 1)`) - - b = ds1.Having(Ex{"a": Op{"gt": 1}}) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" HAVING ("a" > 1)`) - - b = ds1.Having(Ex{"a": Op{"gt": 1}}).Having(Ex{"b": 2}) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" HAVING (("a" > 1) AND ("b" = 2))`) -} - -func (me *datasetTest) TestOrder() { - t := me.T() - - ds1 := From("test") - - b := ds1.Order(I("a").Asc(), Literal(`("a" + "b" > 2)`).Asc()) - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" ORDER BY "a" ASC, ("a" + "b" > 2) ASC`) -} - -func (me *datasetTest) TestOrderAppend() { - t := me.T() - b := From("test").Order(I("a").Asc().NullsFirst()).OrderAppend(I("b").Desc().NullsLast()) - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" ORDER BY "a" ASC NULLS FIRST, "b" DESC NULLS LAST`) - - b = From("test").OrderAppend(I("a").Asc().NullsFirst()).OrderAppend(I("b").Desc().NullsLast()) - sql, _, err = b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test" ORDER BY "a" ASC NULLS FIRST, "b" DESC NULLS LAST`) - -} - -func (me *datasetTest) TestClearOrder() { - t := me.T() - b := From("test").Order(I("a").Asc().NullsFirst()).ClearOrder() - sql, _, err := b.ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "test"`) -} - -func (me *datasetTest) TestJoin() { - t := me.T() - ds1 := From("items") - - sql, _, err := ds1.Join(I("players").As("p"), On(Ex{"p.id": I("items.playerId")})).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN "players" AS "p" ON ("p"."id" = "items"."playerId")`) - - sql, _, err = ds1.Join(ds1.From("players").As("p"), On(Ex{"p.id": I("items.playerId")})).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN (SELECT * FROM "players") AS "p" ON ("p"."id" = "items"."playerId")`) - - sql, _, err = ds1.Join(I("v1").Table("test"), On(Ex{"v1.test.id": I("items.playerId")})).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN "v1"."test" ON ("v1"."test"."id" = "items"."playerId")`) - - sql, _, err = ds1.Join(I("test"), Using(I("name"), I("common_id"))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN "test" USING ("name", "common_id")`) - - sql, _, err = ds1.Join(I("test"), Using("name", "common_id")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN "test" USING ("name", "common_id")`) - -} - -func (me *datasetTest) TestLeftOuterJoin() { - t := me.T() - ds1 := From("items") - - sql, _, err := ds1.LeftOuterJoin(I("categories"), On(Ex{ - "categories.categoryId": I("items.id"), - })).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) - - sql, _, err = ds1.LeftOuterJoin(I("categories"), On(I("categories.categoryId").Eq(I("items.id")), I("categories.categoryId").In(1, 2, 3))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" LEFT OUTER JOIN "categories" ON (("categories"."categoryId" = "items"."id") AND ("categories"."categoryId" IN (1, 2, 3)))`) - - sql, _, err = ds1.Where(I("price").Lt(100)).RightOuterJoin(I("categories"), On(Ex{"categories.categoryId": I("items.id")})).ToSql() -} - -func (me *datasetTest) TestFullOuterJoin() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1. - FullOuterJoin(I("categories"), On(Ex{"categories.categoryId": I("items.id")})). - Order(I("stamp").Asc()).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id") ORDER BY "stamp" ASC`) - - sql, _, err = ds1.FullOuterJoin(I("categories"), On(Ex{"categories.categoryId": I("items.id")})).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) -} - -func (me *datasetTest) TestInnerJoin() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1. - InnerJoin(I("b"), On(Ex{"b.itemsId": I("items.id")})). - LeftOuterJoin(I("c"), On(Ex{"c.b_id": I("b.id")})). - ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN "b" ON ("b"."itemsId" = "items"."id") LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")`) - - sql, _, err = ds1. - InnerJoin(I("b"), On(Ex{"b.itemsId": I("items.id")})). - LeftOuterJoin(I("c"), On(Ex{"c.b_id": I("b.id")})). - ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN "b" ON ("b"."itemsId" = "items"."id") LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")`) - - sql, _, err = ds1.InnerJoin(I("categories"), On(Ex{"categories.categoryId": I("items.id")})).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) -} - -func (me *datasetTest) TestRightOuterJoin() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.RightOuterJoin(I("categories"), On(Ex{"categories.categoryId": I("items.id")})).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) -} - -func (me *datasetTest) TestLeftJoin() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.LeftJoin(I("categories"), On(Ex{"categories.categoryId": I("items.id")})).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) -} - -func (me *datasetTest) TestRightJoin() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.RightJoin(I("categories"), On(Ex{"categories.categoryId": I("items.id")})).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) -} - -func (me *datasetTest) TestFullJoin() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.FullJoin(I("categories"), On(Ex{"categories.categoryId": I("items.id")})).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) -} - -func (me *datasetTest) TestNaturalJoin() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.NaturalJoin(I("categories")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" NATURAL JOIN "categories"`) -} - -func (me *datasetTest) TestNaturalLeftJoin() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.NaturalLeftJoin(I("categories")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" NATURAL LEFT JOIN "categories"`) - -} - -func (me *datasetTest) TestNaturalRightJoin() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.NaturalRightJoin(I("categories")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" NATURAL RIGHT JOIN "categories"`) -} - -func (me *datasetTest) TestNaturalFullJoin() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.NaturalFullJoin(I("categories")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" NATURAL FULL JOIN "categories"`) -} - -func (me *datasetTest) TestCrossJoin() { - t := me.T() - sql, _, err := From("items").CrossJoin(I("categories")).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" CROSS JOIN "categories"`) -} - -func (me *datasetTest) TestSqlFunctionExpressionsInHaving() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.GroupBy("name").Having(SUM("amount").Gt(0)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "items" GROUP BY "name" HAVING (SUM("amount") > 0)`) -} - -func (me *datasetTest) TestUnion() { - t := me.T() - a := From("invoice").Select("id", "amount").Where(I("amount").Gt(1000)) - b := From("invoice").Select("id", "amount").Where(I("amount").Lt(10)) - - sql, _, err := a.Union(b).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) - - sql, _, err = a.Limit(1).Union(b).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) - - sql, _, err = a.Order(I("id").Asc()).Union(b).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) ORDER BY "id" ASC) AS "t1" UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) - - sql, _, err = a.Union(b.Limit(1)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) UNION (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) LIMIT 1) AS "t1")`) - - sql, _, err = a.Union(b.Order(I("id").Desc())).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) UNION (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) - - sql, _, err = a.Limit(1).Union(b.Order(I("id").Desc())).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" UNION (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) - - sql, _, err = a.Union(b).Union(b.Where(I("id").Lt(50))).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10)) UNION (SELECT "id", "amount" FROM "invoice" WHERE (("amount" < 10) AND ("id" < 50)))`) - -} - -func (me *datasetTest) TestUnionAll() { - t := me.T() - a := From("invoice").Select("id", "amount").Where(I("amount").Gt(1000)) - b := From("invoice").Select("id", "amount").Where(I("amount").Lt(10)) - - sql, _, err := a.UnionAll(b).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) - - sql, _, err = a.Limit(1).UnionAll(b).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) - - sql, _, err = a.Order(I("id").Asc()).UnionAll(b).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) ORDER BY "id" ASC) AS "t1" UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) - - sql, _, err = a.UnionAll(b.Limit(1)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) UNION ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) LIMIT 1) AS "t1")`) - - sql, _, err = a.UnionAll(b.Order(I("id").Desc())).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) UNION ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) - - sql, _, err = a.Limit(1).UnionAll(b.Order(I("id").Desc())).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" UNION ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) -} - -func (me *datasetTest) TestIntersect() { - t := me.T() - a := From("invoice").Select("id", "amount").Where(I("amount").Gt(1000)) - b := From("invoice").Select("id", "amount").Where(I("amount").Lt(10)) - - sql, _, err := a.Intersect(b).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) INTERSECT (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) - - sql, _, err = a.Limit(1).Intersect(b).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" INTERSECT (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) - - sql, _, err = a.Order(I("id").Asc()).Intersect(b).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) ORDER BY "id" ASC) AS "t1" INTERSECT (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) - - sql, _, err = a.Intersect(b.Limit(1)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) INTERSECT (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) LIMIT 1) AS "t1")`) - - sql, _, err = a.Intersect(b.Order(I("id").Desc())).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) INTERSECT (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) - - sql, _, err = a.Limit(1).Intersect(b.Order(I("id").Desc())).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" INTERSECT (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) -} - -func (me *datasetTest) TestIntersectAll() { - t := me.T() - a := From("invoice").Select("id", "amount").Where(I("amount").Gt(1000)) - b := From("invoice").Select("id", "amount").Where(I("amount").Lt(10)) - - sql, _, err := a.IntersectAll(b).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) INTERSECT ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) - - sql, _, err = a.Limit(1).IntersectAll(b).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" INTERSECT ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) - - sql, _, err = a.Order(I("id").Asc()).IntersectAll(b).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) ORDER BY "id" ASC) AS "t1" INTERSECT ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) - - sql, _, err = a.IntersectAll(b.Limit(1)).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) INTERSECT ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) LIMIT 1) AS "t1")`) - - sql, _, err = a.IntersectAll(b.Order(I("id").Desc())).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) INTERSECT ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) - - sql, _, err = a.Limit(1).IntersectAll(b.Order(I("id").Desc())).ToSql() - assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" INTERSECT ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) -} - -//TO PREPARED - -func (me *datasetTest) TestPreparedWhere() { - t := me.T() - ds1 := From("test") - - b := ds1.Where(Ex{ - "a": true, - "b": Op{"neq": true}, - "c": false, - "d": Op{"neq": false}, - "e": nil, - }) - sql, args, err := b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE (("a" IS TRUE) AND ("b" IS NOT TRUE) AND ("c" IS FALSE) AND ("d" IS NOT FALSE) AND ("e" IS NULL))`) - - b = ds1.Where(Ex{ - "a": "a", - "b": Op{"neq": "b"}, - "c": Op{"gt": "c"}, - "d": Op{"gte": "d"}, - "e": Op{"lt": "e"}, - "f": Op{"lte": "f"}, - "g": Op{"is": nil}, - "h": Op{"isnot": nil}, - }) - sql, args, err = b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"a", "b", "c", "d", "e", "f"}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE (("a" = ?) AND ("b" != ?) AND ("c" > ?) AND ("d" >= ?) AND ("e" < ?) AND ("f" <= ?) AND ("g" IS NULL) AND ("h" IS NOT NULL))`) -} - -func (me *datasetTest) TestPreparedLimit() { - t := me.T() - ds1 := From("test") - - b := ds1.Where(I("a").Gt(1)).Limit(10) - sql, args, err := b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1), int64(10)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > ?) LIMIT ?`) - - b = ds1.Where(I("a").Gt(1)).Limit(0) - sql, args, err = b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > ?)`) -} - -func (me *datasetTest) TestPreparedLimitAll() { - t := me.T() - ds1 := From("test") - - b := ds1.Where(I("a").Gt(1)).LimitAll() - sql, args, err := b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > ?) LIMIT ALL`) - - b = ds1.Where(I("a").Gt(1)).Limit(0).LimitAll() - sql, args, err = b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > ?) LIMIT ALL`) -} - -func (me *datasetTest) TestPreparedClearLimit() { - t := me.T() - ds1 := From("test") - - b := ds1.Where(I("a").Gt(1)).LimitAll().ClearLimit() - sql, args, err := b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > ?)`) - - b = ds1.Where(I("a").Gt(1)).Limit(10).ClearLimit() - sql, args, err = b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > ?)`) -} - -func (me *datasetTest) TestPreparedOffset() { - t := me.T() - ds1 := From("test") - - b := ds1.Where(I("a").Gt(1)).Offset(10) - sql, args, err := b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1), int64(10)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > ?) OFFSET ?`) - - b = ds1.Where(I("a").Gt(1)).Offset(0) - sql, args, err = b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > ?)`) -} - -func (me *datasetTest) TestPreparedClearOffset() { - t := me.T() - ds1 := From("test") - - b := ds1.Where(I("a").Gt(1)).Offset(10).ClearOffset() - sql, args, err := b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > ?)`) -} - -func (me *datasetTest) TestPreparedGroupBy() { - t := me.T() - ds1 := From("test") - - b := ds1.Where(I("a").Gt(1)).GroupBy("created") - sql, args, err := b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > ?) GROUP BY "created"`) - - b = ds1.Where(I("a").Gt(1)).GroupBy(Literal("created::DATE")) - sql, args, err = b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > ?) GROUP BY created::DATE`) - - b = ds1.Where(I("a").Gt(1)).GroupBy("name", Literal("created::DATE")) - sql, args, err = b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("a" > ?) GROUP BY "name", created::DATE`) -} - -func (me *datasetTest) TestPreparedHaving() { - t := me.T() - ds1 := From("test") - - b := ds1.Having(I("a").Gt(1)).GroupBy("created") - sql, args, err := b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" GROUP BY "created" HAVING ("a" > ?)`) - - b = ds1. - Where(I("b").IsTrue()). - Having(I("a").Gt(1)). - GroupBy("created") - sql, args, err = b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" WHERE ("b" IS TRUE) GROUP BY "created" HAVING ("a" > ?)`) - - b = ds1.Having(I("a").Gt(1)) - sql, args, err = b.Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1)}) - assert.Equal(t, sql, `SELECT * FROM "test" HAVING ("a" > ?)`) -} - -func (me *datasetTest) TestPreparedJoin() { - t := me.T() - ds1 := From("items") - - sql, args, err := ds1.Join(I("players").As("p"), On(I("p.id").Eq(I("items.playerId")))).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{}) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN "players" AS "p" ON ("p"."id" = "items"."playerId")`) - - sql, args, err = ds1.Join(ds1.From("players").As("p"), On(I("p.id").Eq(I("items.playerId")))).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{}) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN (SELECT * FROM "players") AS "p" ON ("p"."id" = "items"."playerId")`) - - sql, args, err = ds1.Join(I("v1").Table("test"), On(I("v1.test.id").Eq(I("items.playerId")))).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{}) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN "v1"."test" ON ("v1"."test"."id" = "items"."playerId")`) - - sql, args, err = ds1.Join(I("test"), Using(I("name"), I("common_id"))).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{}) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN "test" USING ("name", "common_id")`) - - sql, args, err = ds1.Join(I("test"), Using("name", "common_id")).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{}) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN "test" USING ("name", "common_id")`) - - sql, args, err = ds1.Join(I("categories"), On(I("categories.categoryId").Eq(I("items.id")), I("categories.categoryId").In(1, 2, 3))).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1), int64(2), int64(3)}) - assert.Equal(t, sql, `SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."categoryId" = "items"."id") AND ("categories"."categoryId" IN (?, ?, ?)))`) - -} - -func (me *datasetTest) TestPreparedFunctionExpressionsInHaving() { - t := me.T() - ds1 := From("items") - sql, args, err := ds1.GroupBy("name").Having(SUM("amount").Gt(0)).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(0)}) - assert.Equal(t, sql, `SELECT * FROM "items" GROUP BY "name" HAVING (SUM("amount") > ?)`) -} - -func (me *datasetTest) TestPreparedUnion() { - t := me.T() - a := From("invoice").Select("id", "amount").Where(I("amount").Gt(1000)) - b := From("invoice").Select("id", "amount").Where(I("amount").Lt(10)) - - sql, args, err := a.Union(b).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(10)}) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?))`) - - sql, args, err = a.Limit(1).Union(b).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(1), int64(10)}) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) LIMIT ?) AS "t1" UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?))`) - - sql, args, err = a.Union(b.Limit(1)).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(10), int64(1)}) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) UNION (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?) LIMIT ?) AS "t1")`) - - sql, args, err = a.Union(b).Union(b.Where(I("id").Lt(50))).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(10), int64(10), int64(50)}) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?)) UNION (SELECT "id", "amount" FROM "invoice" WHERE (("amount" < ?) AND ("id" < ?)))`) - -} - -func (me *datasetTest) TestPreparedUnionAll() { - t := me.T() - a := From("invoice").Select("id", "amount").Where(I("amount").Gt(1000)) - b := From("invoice").Select("id", "amount").Where(I("amount").Lt(10)) - - sql, args, err := a.UnionAll(b).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(10)}) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?))`) - - sql, args, err = a.Limit(1).UnionAll(b).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(1), int64(10)}) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) LIMIT ?) AS "t1" UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?))`) - - sql, args, err = a.UnionAll(b.Limit(1)).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(10), int64(1)}) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) UNION ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?) LIMIT ?) AS "t1")`) - - sql, args, err = a.UnionAll(b).UnionAll(b.Where(I("id").Lt(50))).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(10), int64(10), int64(50)}) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?)) UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE (("amount" < ?) AND ("id" < ?)))`) -} - -func (me *datasetTest) TestPreparedIntersect() { - t := me.T() - a := From("invoice").Select("id", "amount").Where(I("amount").Gt(1000)) - b := From("invoice").Select("id", "amount").Where(I("amount").Lt(10)) - - sql, args, err := a.Intersect(b).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(10)}) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) INTERSECT (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?))`) - - sql, args, err = a.Limit(1).Intersect(b).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(1), int64(10)}) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) LIMIT ?) AS "t1" INTERSECT (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?))`) - - sql, args, err = a.Intersect(b.Limit(1)).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(10), int64(1)}) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) INTERSECT (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?) LIMIT ?) AS "t1")`) - -} - -func (me *datasetTest) TestPreparedIntersectAll() { - t := me.T() - a := From("invoice").Select("id", "amount").Where(I("amount").Gt(1000)) - b := From("invoice").Select("id", "amount").Where(I("amount").Lt(10)) - - sql, args, err := a.IntersectAll(b).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(10)}) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) INTERSECT ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?))`) - - sql, args, err = a.Limit(1).IntersectAll(b).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(1), int64(10)}) - assert.Equal(t, sql, `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) LIMIT ?) AS "t1" INTERSECT ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?))`) - - sql, args, err = a.IntersectAll(b.Limit(1)).Prepared(true).ToSql() - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{int64(1000), int64(10), int64(1)}) - assert.Equal(t, sql, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) INTERSECT ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?) LIMIT ?) AS "t1")`) - -} diff --git a/dataset_sql_example_test.go b/dataset_sql_example_test.go new file mode 100644 index 00000000..bd4bbb33 --- /dev/null +++ b/dataset_sql_example_test.go @@ -0,0 +1,1280 @@ +package goqu_test + +import ( + "fmt" + "regexp" + + "github.com/doug-martin/goqu/v7" +) + +func ExampleDataset() { + ds := goqu.From("test"). + Select(goqu.COUNT("*")). + InnerJoin(goqu.T("test2"), goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.id")))). + LeftJoin(goqu.T("test3"), goqu.On(goqu.I("test2.fkey").Eq(goqu.I("test3.id")))). + Where( + goqu.Ex{ + "test.name": goqu.Op{ + "like": regexp.MustCompile("^(a|b)"), + }, + "test2.amount": goqu.Op{ + "isNot": nil, + }, + }, + goqu.ExOr{ + "test3.id": nil, + "test3.status": []string{"passed", "active", "registered"}, + }). + Order(goqu.I("test.created").Desc().NullsLast()). + GroupBy(goqu.I("test.user_id")). + Having(goqu.AVG("test3.age").Gt(10)) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // nolint:lll + // Output: + // SELECT COUNT(*) FROM "test" INNER JOIN "test2" ON ("test"."fkey" = "test2"."id") LEFT JOIN "test3" ON ("test2"."fkey" = "test3"."id") WHERE ((("test"."name" ~ '^(a|b)') AND ("test2"."amount" IS NOT NULL)) AND (("test3"."id" IS NULL) OR ("test3"."status" IN ('passed', 'active', 'registered')))) GROUP BY "test"."user_id" HAVING (AVG("test3"."age") > 10) ORDER BY "test"."created" DESC NULLS LAST [] + // SELECT COUNT(*) FROM "test" INNER JOIN "test2" ON ("test"."fkey" = "test2"."id") LEFT JOIN "test3" ON ("test2"."fkey" = "test3"."id") WHERE ((("test"."name" ~ ?) AND ("test2"."amount" IS NOT NULL)) AND (("test3"."id" IS NULL) OR ("test3"."status" IN (?, ?, ?)))) GROUP BY "test"."user_id" HAVING (AVG("test3"."age") > ?) ORDER BY "test"."created" DESC NULLS LAST [^(a|b) passed active registered 10] +} + +func ExampleDataset_As() { + ds := goqu.From("test").As("t") + sql, _, _ := goqu.From(ds).ToSQL() + fmt.Println(sql) + // Output: SELECT * FROM (SELECT * FROM "test") AS "t" +} + +func ExampleDataset_Returning() { + sql, _, _ := goqu.From("test"). + Returning("id"). + ToInsertSQL(goqu.Record{"a": "a", "b": "b"}) + fmt.Println(sql) + sql, _, _ = goqu.From("test"). + Returning(goqu.T("test").All()). + ToInsertSQL(goqu.Record{"a": "a", "b": "b"}) + fmt.Println(sql) + sql, _, _ = goqu.From("test"). + Returning("a", "b"). + ToInsertSQL(goqu.Record{"a": "a", "b": "b"}) + fmt.Println(sql) + // Output: + // INSERT INTO "test" ("a", "b") VALUES ('a', 'b') RETURNING "id" + // INSERT INTO "test" ("a", "b") VALUES ('a', 'b') RETURNING "test".* + // INSERT INTO "test" ("a", "b") VALUES ('a', 'b') RETURNING "a", "b" +} + +func ExampleDataset_Union() { + sql, _, _ := goqu.From("test"). + Union(goqu.From("test2")). + ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test"). + Limit(1). + Union(goqu.From("test2")). + ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test"). + Limit(1). + Union(goqu.From("test2"). + Order(goqu.C("id").Desc())). + ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" UNION (SELECT * FROM "test2") + // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" UNION (SELECT * FROM "test2") + // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" UNION (SELECT * FROM (SELECT * FROM "test2" ORDER BY "id" DESC) AS "t1") +} + +func ExampleDataset_UnionAll() { + sql, _, _ := goqu.From("test"). + UnionAll(goqu.From("test2")). + ToSQL() + fmt.Println(sql) + sql, _, _ = goqu.From("test"). + Limit(1). + UnionAll(goqu.From("test2")). + ToSQL() + fmt.Println(sql) + sql, _, _ = goqu.From("test"). + Limit(1). + UnionAll(goqu.From("test2"). + Order(goqu.C("id").Desc())). + ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" UNION ALL (SELECT * FROM "test2") + // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" UNION ALL (SELECT * FROM "test2") + // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" UNION ALL (SELECT * FROM (SELECT * FROM "test2" ORDER BY "id" DESC) AS "t1") +} + +func ExampleDataset_With() { + sql, _, _ := goqu.From("one"). + With("one", goqu.From().Select(goqu.L("1"))). + Select(goqu.Star()). + ToSQL() + fmt.Println(sql) + sql, _, _ = goqu.From("derived"). + With("intermed", goqu.From("test").Select(goqu.Star()).Where(goqu.C("x").Gte(5))). + With("derived", goqu.From("intermed").Select(goqu.Star()).Where(goqu.C("x").Lt(10))). + Select(goqu.Star()). + ToSQL() + fmt.Println(sql) + sql, _, _ = goqu.From("multi"). + With("multi(x,y)", goqu.From().Select(goqu.L("1"), goqu.L("2"))). + Select(goqu.C("x"), goqu.C("y")). + ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test"). + With("moved_rows", goqu.From("other").Where(goqu.C("date").Lt(123))). + ToInsertSQL(goqu.From("moved_rows")) + fmt.Println(sql) + sql, _, _ = goqu.From("test"). + With("check_vals(val)", goqu.From().Select(goqu.L("123"))). + Where(goqu.C("val").Eq(goqu.From("check_vals").Select("val"))). + ToDeleteSQL() + fmt.Println(sql) + sql, _, _ = goqu.From("test"). + With("some_vals(val)", goqu.From().Select(goqu.L("123"))). + Where(goqu.C("val").Eq(goqu.From("some_vals").Select("val"))). + ToUpdateSQL(goqu.Record{"name": "Test"}) + fmt.Println(sql) + + // Output: + // WITH one AS (SELECT 1) SELECT * FROM "one" + // WITH intermed AS (SELECT * FROM "test" WHERE ("x" >= 5)), derived AS (SELECT * FROM "intermed" WHERE ("x" < 10)) SELECT * FROM "derived" + // WITH multi(x,y) AS (SELECT 1, 2) SELECT "x", "y" FROM "multi" + // WITH moved_rows AS (SELECT * FROM "other" WHERE ("date" < 123)) INSERT INTO "test" SELECT * FROM "moved_rows" + // WITH check_vals(val) AS (SELECT 123) DELETE FROM "test" WHERE ("val" IN (SELECT "val" FROM "check_vals")) + // WITH some_vals(val) AS (SELECT 123) UPDATE "test" SET "name"='Test' WHERE ("val" IN (SELECT "val" FROM "some_vals")) +} + +func ExampleDataset_WithRecursive() { + sql, _, _ := goqu.From("nums"). + WithRecursive("nums(x)", + goqu.From().Select(goqu.L("1")). + UnionAll(goqu.From("nums"). + Select(goqu.L("x+1")).Where(goqu.C("x").Lt(5)))). + ToSQL() + fmt.Println(sql) + // Output: + // WITH RECURSIVE nums(x) AS (SELECT 1 UNION ALL (SELECT x+1 FROM "nums" WHERE ("x" < 5))) SELECT * FROM "nums" +} + +func ExampleDataset_Intersect() { + sql, _, _ := goqu.From("test"). + Intersect(goqu.From("test2")). + ToSQL() + fmt.Println(sql) + sql, _, _ = goqu.From("test"). + Limit(1). + Intersect(goqu.From("test2")). + ToSQL() + fmt.Println(sql) + sql, _, _ = goqu.From("test"). + Limit(1). + Intersect(goqu.From("test2"). + Order(goqu.C("id").Desc())). + ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" INTERSECT (SELECT * FROM "test2") + // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" INTERSECT (SELECT * FROM "test2") + // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" INTERSECT (SELECT * FROM (SELECT * FROM "test2" ORDER BY "id" DESC) AS "t1") +} + +func ExampleDataset_IntersectAll() { + sql, _, _ := goqu.From("test"). + IntersectAll(goqu.From("test2")). + ToSQL() + fmt.Println(sql) + sql, _, _ = goqu.From("test"). + Limit(1). + IntersectAll(goqu.From("test2")). + ToSQL() + fmt.Println(sql) + sql, _, _ = goqu.From("test"). + Limit(1). + IntersectAll(goqu.From("test2"). + Order(goqu.C("id").Desc())). + ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" INTERSECT ALL (SELECT * FROM "test2") + // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" INTERSECT ALL (SELECT * FROM "test2") + // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" INTERSECT ALL (SELECT * FROM (SELECT * FROM "test2" ORDER BY "id" DESC) AS "t1") +} + +func ExampleDataset_ClearOffset() { + ds := goqu.From("test"). + Offset(2) + sql, _, _ := ds. + ClearOffset(). + ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" +} + +func ExampleDataset_Offset() { + ds := goqu.From("test"). + Offset(2) + sql, _, _ := ds.ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" OFFSET 2 +} + +func ExampleDataset_Limit() { + ds := goqu.From("test").Limit(10) + sql, _, _ := ds.ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" LIMIT 10 +} + +func ExampleDataset_LimitAll() { + ds := goqu.From("test").LimitAll() + sql, _, _ := ds.ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" LIMIT ALL +} + +func ExampleDataset_ClearLimit() { + ds := goqu.From("test").Limit(10) + sql, _, _ := ds.ClearLimit().ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" +} + +func ExampleDataset_Order() { + ds := goqu.From("test"). + Order(goqu.C("a").Asc()) + sql, _, _ := ds.ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" ORDER BY "a" ASC +} + +func ExampleDataset_OrderAppend() { + ds := goqu.From("test").Order(goqu.C("a").Asc()) + sql, _, _ := ds.OrderAppend(goqu.C("b").Desc().NullsLast()).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" ORDER BY "a" ASC, "b" DESC NULLS LAST +} + +func ExampleDataset_ClearOrder() { + ds := goqu.From("test").Order(goqu.C("a").Asc()) + sql, _, _ := ds.ClearOrder().ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" +} + +func ExampleDataset_Having() { + sql, _, _ := goqu.From("test").Having(goqu.SUM("income").Gt(1000)).ToSQL() + fmt.Println(sql) + sql, _, _ = goqu.From("test").GroupBy("age").Having(goqu.SUM("income").Gt(1000)).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" HAVING (SUM("income") > 1000) + // SELECT * FROM "test" GROUP BY "age" HAVING (SUM("income") > 1000) +} + +func ExampleDataset_Where() { + // By default everything is anded together + sql, _, _ := goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"gt": 10}, + "b": goqu.Op{"lt": 10}, + "c": nil, + "d": []string{"a", "b", "c"}, + }).ToSQL() + fmt.Println(sql) + // You can use ExOr to get ORed expressions together + sql, _, _ = goqu.From("test").Where(goqu.ExOr{ + "a": goqu.Op{"gt": 10}, + "b": goqu.Op{"lt": 10}, + "c": nil, + "d": []string{"a", "b", "c"}, + }).ToSQL() + fmt.Println(sql) + // You can use Or with Ex to Or multiple Ex maps together + sql, _, _ = goqu.From("test").Where( + goqu.Or( + goqu.Ex{ + "a": goqu.Op{"gt": 10}, + "b": goqu.Op{"lt": 10}, + }, + goqu.Ex{ + "c": nil, + "d": []string{"a", "b", "c"}, + }, + ), + ).ToSQL() + fmt.Println(sql) + // By default everything is anded together + sql, _, _ = goqu.From("test").Where( + goqu.C("a").Gt(10), + goqu.C("b").Lt(10), + goqu.C("c").IsNull(), + goqu.C("d").In("a", "b", "c"), + ).ToSQL() + fmt.Println(sql) + // You can use a combination of Ors and Ands + sql, _, _ = goqu.From("test").Where( + goqu.Or( + goqu.C("a").Gt(10), + goqu.And( + goqu.C("b").Lt(10), + goqu.C("c").IsNull(), + ), + ), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" WHERE (("a" > 10) AND ("b" < 10) AND ("c" IS NULL) AND ("d" IN ('a', 'b', 'c'))) + // SELECT * FROM "test" WHERE (("a" > 10) OR ("b" < 10) OR ("c" IS NULL) OR ("d" IN ('a', 'b', 'c'))) + // SELECT * FROM "test" WHERE ((("a" > 10) AND ("b" < 10)) OR (("c" IS NULL) AND ("d" IN ('a', 'b', 'c')))) + // SELECT * FROM "test" WHERE (("a" > 10) AND ("b" < 10) AND ("c" IS NULL) AND ("d" IN ('a', 'b', 'c'))) + // SELECT * FROM "test" WHERE (("a" > 10) OR (("b" < 10) AND ("c" IS NULL))) +} + +func ExampleDataset_Where_prepared() { + // By default everything is anded together + sql, args, _ := goqu.From("test").Prepared(true).Where(goqu.Ex{ + "a": goqu.Op{"gt": 10}, + "b": goqu.Op{"lt": 10}, + "c": nil, + "d": []string{"a", "b", "c"}, + }).ToSQL() + fmt.Println(sql, args) + // You can use ExOr to get ORed expressions together + sql, args, _ = goqu.From("test").Prepared(true).Where(goqu.ExOr{ + "a": goqu.Op{"gt": 10}, + "b": goqu.Op{"lt": 10}, + "c": nil, + "d": []string{"a", "b", "c"}, + }).ToSQL() + fmt.Println(sql, args) + // You can use Or with Ex to Or multiple Ex maps together + sql, args, _ = goqu.From("test").Prepared(true).Where( + goqu.Or( + goqu.Ex{ + "a": goqu.Op{"gt": 10}, + "b": goqu.Op{"lt": 10}, + }, + goqu.Ex{ + "c": nil, + "d": []string{"a", "b", "c"}, + }, + ), + ).ToSQL() + fmt.Println(sql, args) + // By default everything is anded together + sql, args, _ = goqu.From("test").Prepared(true).Where( + goqu.C("a").Gt(10), + goqu.C("b").Lt(10), + goqu.C("c").IsNull(), + goqu.C("d").In("a", "b", "c"), + ).ToSQL() + fmt.Println(sql, args) + // You can use a combination of Ors and Ands + sql, args, _ = goqu.From("test").Prepared(true).Where( + goqu.Or( + goqu.C("a").Gt(10), + goqu.And( + goqu.C("b").Lt(10), + goqu.C("c").IsNull(), + ), + ), + ).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT * FROM "test" WHERE (("a" > ?) AND ("b" < ?) AND ("c" IS NULL) AND ("d" IN (?, ?, ?))) [10 10 a b c] + // SELECT * FROM "test" WHERE (("a" > ?) OR ("b" < ?) OR ("c" IS NULL) OR ("d" IN (?, ?, ?))) [10 10 a b c] + // SELECT * FROM "test" WHERE ((("a" > ?) AND ("b" < ?)) OR (("c" IS NULL) AND ("d" IN (?, ?, ?)))) [10 10 a b c] + // SELECT * FROM "test" WHERE (("a" > ?) AND ("b" < ?) AND ("c" IS NULL) AND ("d" IN (?, ?, ?))) [10 10 a b c] + // SELECT * FROM "test" WHERE (("a" > ?) OR (("b" < ?) AND ("c" IS NULL))) [10 10] +} + +func ExampleDataset_ClearWhere() { + ds := goqu.From("test").Where( + goqu.Or( + goqu.C("a").Gt(10), + goqu.And( + goqu.C("b").Lt(10), + goqu.C("c").IsNull(), + ), + ), + ) + sql, _, _ := ds.ClearWhere().ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" +} + +func ExampleDataset_Join() { + sql, _, _ := goqu.From("test").Join( + goqu.T("test2"), + goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.Id")}), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Join(goqu.T("test2"), goqu.Using("common_column")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Join( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + goqu.On(goqu.I("test.fkey").Eq(goqu.T("test2").Col("Id"))), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Join( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + goqu.On(goqu.T("test").Col("fkey").Eq(goqu.T("t").Col("Id"))), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" INNER JOIN "test2" ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" INNER JOIN "test2" USING ("common_column") + // SELECT * FROM "test" INNER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" INNER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") + +} + +func ExampleDataset_InnerJoin() { + sql, _, _ := goqu.From("test").InnerJoin( + goqu.T("test2"), + goqu.On(goqu.Ex{ + "test.fkey": goqu.I("test2.Id"), + }), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").InnerJoin( + goqu.T("test2"), + goqu.Using("common_column"), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").InnerJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id"))), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").InnerJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id"))), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" INNER JOIN "test2" ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" INNER JOIN "test2" USING ("common_column") + // SELECT * FROM "test" INNER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" INNER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") +} + +func ExampleDataset_FullOuterJoin() { + sql, _, _ := goqu.From("test").FullOuterJoin( + goqu.T("test2"), + goqu.On(goqu.Ex{ + "test.fkey": goqu.I("test2.Id"), + }), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").FullOuterJoin( + goqu.T("test2"), + goqu.Using("common_column"), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").FullOuterJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id"))), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").FullOuterJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id"))), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" FULL OUTER JOIN "test2" ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" FULL OUTER JOIN "test2" USING ("common_column") + // SELECT * FROM "test" FULL OUTER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" FULL OUTER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") +} + +func ExampleDataset_RightOuterJoin() { + sql, _, _ := goqu.From("test").RightOuterJoin( + goqu.T("test2"), + goqu.On(goqu.Ex{ + "test.fkey": goqu.I("test2.Id"), + }), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").RightOuterJoin( + goqu.T("test2"), + goqu.Using("common_column"), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").RightOuterJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id"))), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").RightOuterJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id"))), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" RIGHT OUTER JOIN "test2" ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" RIGHT OUTER JOIN "test2" USING ("common_column") + // SELECT * FROM "test" RIGHT OUTER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" RIGHT OUTER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") +} + +func ExampleDataset_LeftOuterJoin() { + sql, _, _ := goqu.From("test").LeftOuterJoin( + goqu.T("test2"), + goqu.On(goqu.Ex{ + "test.fkey": goqu.I("test2.Id"), + }), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").LeftOuterJoin( + goqu.T("test2"), + goqu.Using("common_column"), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").LeftOuterJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id"))), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").LeftOuterJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id"))), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" LEFT OUTER JOIN "test2" ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" LEFT OUTER JOIN "test2" USING ("common_column") + // SELECT * FROM "test" LEFT OUTER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" LEFT OUTER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") +} + +func ExampleDataset_FullJoin() { + sql, _, _ := goqu.From("test").FullJoin( + goqu.T("test2"), + goqu.On(goqu.Ex{ + "test.fkey": goqu.I("test2.Id"), + }), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").FullJoin( + goqu.T("test2"), + goqu.Using("common_column"), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").FullJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id"))), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").FullJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id"))), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" FULL JOIN "test2" ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" FULL JOIN "test2" USING ("common_column") + // SELECT * FROM "test" FULL JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" FULL JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") +} + +func ExampleDataset_RightJoin() { + sql, _, _ := goqu.From("test").RightJoin( + goqu.T("test2"), + goqu.On(goqu.Ex{ + "test.fkey": goqu.I("test2.Id"), + }), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").RightJoin( + goqu.T("test2"), + goqu.Using("common_column"), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").RightJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id"))), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").RightJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id"))), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" RIGHT JOIN "test2" ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" RIGHT JOIN "test2" USING ("common_column") + // SELECT * FROM "test" RIGHT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" RIGHT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") +} + +func ExampleDataset_LeftJoin() { + sql, _, _ := goqu.From("test").LeftJoin( + goqu.T("test2"), + goqu.On(goqu.Ex{ + "test.fkey": goqu.I("test2.Id"), + }), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").LeftJoin( + goqu.T("test2"), + goqu.Using("common_column"), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").LeftJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id"))), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").LeftJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id"))), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" LEFT JOIN "test2" ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" LEFT JOIN "test2" USING ("common_column") + // SELECT * FROM "test" LEFT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") + // SELECT * FROM "test" LEFT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") +} + +func ExampleDataset_NaturalJoin() { + sql, _, _ := goqu.From("test").NaturalJoin(goqu.T("test2")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").NaturalJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").NaturalJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" NATURAL JOIN "test2" + // SELECT * FROM "test" NATURAL JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) + // SELECT * FROM "test" NATURAL JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" +} + +func ExampleDataset_NaturalLeftJoin() { + sql, _, _ := goqu.From("test").NaturalLeftJoin(goqu.T("test2")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").NaturalLeftJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").NaturalLeftJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" NATURAL LEFT JOIN "test2" + // SELECT * FROM "test" NATURAL LEFT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) + // SELECT * FROM "test" NATURAL LEFT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" +} + +func ExampleDataset_NaturalRightJoin() { + sql, _, _ := goqu.From("test").NaturalRightJoin(goqu.T("test2")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").NaturalRightJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").NaturalRightJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" NATURAL RIGHT JOIN "test2" + // SELECT * FROM "test" NATURAL RIGHT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) + // SELECT * FROM "test" NATURAL RIGHT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" +} + +func ExampleDataset_NaturalFullJoin() { + sql, _, _ := goqu.From("test").NaturalFullJoin(goqu.T("test2")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").NaturalFullJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").NaturalFullJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" NATURAL FULL JOIN "test2" + // SELECT * FROM "test" NATURAL FULL JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) + // SELECT * FROM "test" NATURAL FULL JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" +} + +func ExampleDataset_CrossJoin() { + sql, _, _ := goqu.From("test").CrossJoin(goqu.T("test2")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").CrossJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").CrossJoin( + goqu.From("test2").Where(goqu.C("amount").Gt(0)).As("t"), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" CROSS JOIN "test2" + // SELECT * FROM "test" CROSS JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) + // SELECT * FROM "test" CROSS JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" +} + +func ExampleDataset_FromSelf() { + sql, _, _ := goqu.From("test").FromSelf().ToSQL() + fmt.Println(sql) + sql, _, _ = goqu.From("test").As("my_test_table").FromSelf().ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM (SELECT * FROM "test") AS "t1" + // SELECT * FROM (SELECT * FROM "test") AS "my_test_table" +} + +func ExampleDataset_From() { + ds := goqu.From("test") + sql, _, _ := ds.From("test2").ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test2" +} + +func ExampleDataset_From_withDataset() { + ds := goqu.From("test") + fromDs := ds.Where(goqu.C("age").Gt(10)) + sql, _, _ := ds.From(fromDs).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM (SELECT * FROM "test" WHERE ("age" > 10)) AS "t1" +} + +func ExampleDataset_From_withAliasedDataset() { + ds := goqu.From("test") + fromDs := ds.Where(goqu.C("age").Gt(10)) + sql, _, _ := ds.From(fromDs.As("test2")).ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM (SELECT * FROM "test" WHERE ("age" > 10)) AS "test2" +} + +func ExampleDataset_Select() { + sql, _, _ := goqu.From("test").Select("a", "b", "c").ToSQL() + fmt.Println(sql) + // Output: + // SELECT "a", "b", "c" FROM "test" +} + +func ExampleDataset_Select_withDataset() { + ds := goqu.From("test") + fromDs := ds.Select("age").Where(goqu.C("age").Gt(10)) + sql, _, _ := ds.From().Select(fromDs).ToSQL() + fmt.Println(sql) + // Output: + // SELECT (SELECT "age" FROM "test" WHERE ("age" > 10)) +} + +func ExampleDataset_Select_withAliasedDataset() { + ds := goqu.From("test") + fromDs := ds.Select("age").Where(goqu.C("age").Gt(10)) + sql, _, _ := ds.From().Select(fromDs.As("ages")).ToSQL() + fmt.Println(sql) + // Output: + // SELECT (SELECT "age" FROM "test" WHERE ("age" > 10)) AS "ages" +} + +func ExampleDataset_Select_withLiteral() { + sql, _, _ := goqu.From("test").Select(goqu.L("a + b").As("sum")).ToSQL() + fmt.Println(sql) + // Output: + // SELECT a + b AS "sum" FROM "test" +} + +func ExampleDataset_Select_withSQLFunctionExpression() { + sql, _, _ := goqu.From("test").Select( + goqu.COUNT("*").As("age_count"), + goqu.MAX("age").As("max_age"), + goqu.AVG("age").As("avg_age"), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT COUNT(*) AS "age_count", MAX("age") AS "max_age", AVG("age") AS "avg_age" FROM "test" +} + +func ExampleDataset_Select_withStruct() { + ds := goqu.From("test") + + type myStruct struct { + Name string + Address string `db:"address"` + EmailAddress string `db:"email_address"` + } + + // Pass with pointer + sql, _, _ := ds.Select(&myStruct{}).ToSQL() + fmt.Println(sql) + + // Pass instance of + sql, _, _ = ds.Select(myStruct{}).ToSQL() + fmt.Println(sql) + + type myStruct2 struct { + myStruct + Zipcode string `db:"zipcode"` + } + + // Pass pointer to struct with embedded struct + sql, _, _ = ds.Select(&myStruct2{}).ToSQL() + fmt.Println(sql) + + // Pass instance of struct with embedded struct + sql, _, _ = ds.Select(myStruct2{}).ToSQL() + fmt.Println(sql) + + var myStructs []myStruct + + // Pass slice of structs, will only select columns from underlying type + sql, _, _ = ds.Select(myStructs).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT "address", "email_address", "name" FROM "test" + // SELECT "address", "email_address", "name" FROM "test" + // SELECT "address", "email_address", "name", "zipcode" FROM "test" + // SELECT "address", "email_address", "name", "zipcode" FROM "test" + // SELECT "address", "email_address", "name" FROM "test" +} + +func ExampleDataset_SelectDistinct() { + sql, _, _ := goqu.From("test").SelectDistinct("a", "b").ToSQL() + fmt.Println(sql) + // Output: + // SELECT DISTINCT "a", "b" FROM "test" +} + +func ExampleDataset_SelectAppend() { + ds := goqu.From("test").Select("a", "b") + sql, _, _ := ds.SelectAppend("c").ToSQL() + fmt.Println(sql) + ds = goqu.From("test").SelectDistinct("a", "b") + sql, _, _ = ds.SelectAppend("c").ToSQL() + fmt.Println(sql) + // Output: + // SELECT "a", "b", "c" FROM "test" + // SELECT DISTINCT "a", "b", "c" FROM "test" +} + +func ExampleDataset_ClearSelect() { + ds := goqu.From("test").Select("a", "b") + sql, _, _ := ds.ClearSelect().ToSQL() + fmt.Println(sql) + ds = goqu.From("test").SelectDistinct("a", "b") + sql, _, _ = ds.ClearSelect().ToSQL() + fmt.Println(sql) + // Output: + // SELECT * FROM "test" + // SELECT * FROM "test" +} + +func ExampleDataset_ToSQL() { + sql, args, _ := goqu.From("items").Where(goqu.Ex{"a": 1}).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT * FROM "items" WHERE ("a" = 1) [] +} + +func ExampleDataset_ToSQL_prepared() { + sql, args, _ := goqu.From("items").Where(goqu.Ex{"a": 1}).Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT * FROM "items" WHERE ("a" = ?) [1] +} + +func ExampleDataset_ToUpdateSQL() { + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + sql, args, _ := goqu.From("items").ToUpdateSQL( + item{Name: "Test", Address: "111 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").ToUpdateSQL( + goqu.Record{"name": "Test", "address": "111 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").ToUpdateSQL( + map[string]interface{}{"name": "Test", "address": "111 Test Addr"}, + ) + fmt.Println(sql, args) + + // Output: + // UPDATE "items" SET "address"='111 Test Addr',"name"='Test' [] + // UPDATE "items" SET "address"='111 Test Addr',"name"='Test' [] + // UPDATE "items" SET "address"='111 Test Addr',"name"='Test' [] +} + +func ExampleDataset_ToUpdateSQL_prepared() { + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + + sql, args, _ := goqu.From("items").Prepared(true).ToUpdateSQL( + item{Name: "Test", Address: "111 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").Prepared(true).ToUpdateSQL( + goqu.Record{"name": "Test", "address": "111 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").Prepared(true).ToUpdateSQL( + map[string]interface{}{"name": "Test", "address": "111 Test Addr"}, + ) + fmt.Println(sql, args) + // Output: + // UPDATE "items" SET "address"=?,"name"=? [111 Test Addr Test] + // UPDATE "items" SET "address"=?,"name"=? [111 Test Addr Test] + // UPDATE "items" SET "address"=?,"name"=? [111 Test Addr Test] +} + +func ExampleDataset_ToInsertSQL() { + type item struct { + ID uint32 `db:"id" goqu:"skipinsert"` + Address string `db:"address"` + Name string `db:"name"` + } + sql, args, _ := goqu.From("items").ToInsertSQL( + item{Name: "Test1", Address: "111 Test Addr"}, + item{Name: "Test2", Address: "112 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").ToInsertSQL( + goqu.Record{"name": "Test1", "address": "111 Test Addr"}, + goqu.Record{"name": "Test2", "address": "112 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").ToInsertSQL( + []item{ + {Name: "Test1", Address: "111 Test Addr"}, + {Name: "Test2", Address: "112 Test Addr"}, + }) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").ToInsertSQL( + []goqu.Record{ + {"name": "Test1", "address": "111 Test Addr"}, + {"name": "Test2", "address": "112 Test Addr"}, + }) + fmt.Println(sql, args) + // Output: + // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') [] + // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') [] + // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') [] + // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') [] +} + +func ExampleDataset_ToInsertSQL_prepared() { + type item struct { + ID uint32 `db:"id" goqu:"skipinsert"` + Address string `db:"address"` + Name string `db:"name"` + } + + sql, args, _ := goqu.From("items").Prepared(true).ToInsertSQL( + item{Name: "Test1", Address: "111 Test Addr"}, + item{Name: "Test2", Address: "112 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").Prepared(true).ToInsertSQL( + goqu.Record{"name": "Test1", "address": "111 Test Addr"}, + goqu.Record{"name": "Test2", "address": "112 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").Prepared(true).ToInsertSQL( + []item{ + {Name: "Test1", Address: "111 Test Addr"}, + {Name: "Test2", Address: "112 Test Addr"}, + }) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").Prepared(true).ToInsertSQL( + []goqu.Record{ + {"name": "Test1", "address": "111 Test Addr"}, + {"name": "Test2", "address": "112 Test Addr"}, + }) + fmt.Println(sql, args) + // Output: + // INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?) [111 Test Addr Test1 112 Test Addr Test2] + // INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?) [111 Test Addr Test1 112 Test Addr Test2] + // INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?) [111 Test Addr Test1 112 Test Addr Test2] + // INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?) [111 Test Addr Test1 112 Test Addr Test2] +} + +func ExampleDataset_ToInsertIgnoreSQL() { + type item struct { + ID uint32 `db:"id" goqu:"skipinsert"` + Address string `db:"address"` + Name string `db:"name"` + } + sql, args, _ := goqu.From("items").ToInsertIgnoreSQL( + item{Name: "Test1", Address: "111 Test Addr"}, + item{Name: "Test2", Address: "112 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").ToInsertIgnoreSQL( + goqu.Record{"name": "Test1", "address": "111 Test Addr"}, + goqu.Record{"name": "Test2", "address": "112 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").ToInsertIgnoreSQL( + []item{ + {Name: "Test1", Address: "111 Test Addr"}, + {Name: "Test2", Address: "112 Test Addr"}, + }) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").ToInsertIgnoreSQL( + []goqu.Record{ + {"name": "Test1", "address": "111 Test Addr"}, + {"name": "Test2", "address": "112 Test Addr"}, + }) + fmt.Println(sql, args) + // Output: + // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT DO NOTHING [] + // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT DO NOTHING [] + // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT DO NOTHING [] + // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT DO NOTHING [] +} + +func ExampleDataset_ToInsertConflictSQL() { + type item struct { + ID uint32 `db:"id" goqu:"skipinsert"` + Address string `db:"address"` + Name string `db:"name"` + } + sql, args, _ := goqu.From("items").ToInsertConflictSQL( + goqu.DoNothing(), + item{Name: "Test1", Address: "111 Test Addr"}, + item{Name: "Test2", Address: "112 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").ToInsertConflictSQL( + goqu.DoUpdate("key", goqu.Record{"updated": goqu.L("NOW()")}), + goqu.Record{"name": "Test1", "address": "111 Test Addr"}, + goqu.Record{"name": "Test2", "address": "112 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").ToInsertConflictSQL( + goqu.DoUpdate("key", goqu.Record{"updated": goqu.L("NOW()")}).Where(goqu.C("allow_update").IsTrue()), + []item{ + {Name: "Test1", Address: "111 Test Addr"}, + {Name: "Test2", Address: "112 Test Addr"}, + }) + fmt.Println(sql, args) + + // nolint:lll + // Output: + // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT DO NOTHING [] + // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT (key) DO UPDATE SET "updated"=NOW() [] + // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT (key) DO UPDATE SET "updated"=NOW() WHERE ("allow_update" IS TRUE) [] +} + +func ExampleDataset_ToDeleteSQL() { + sql, args, _ := goqu.From("items").ToDeleteSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items"). + Where(goqu.Ex{"id": goqu.Op{"gt": 10}}). + ToDeleteSQL() + fmt.Println(sql, args) + + // Output: + // DELETE FROM "items" [] + // DELETE FROM "items" WHERE ("id" > 10) [] +} + +func ExampleDataset_ToDeleteSQL_prepared() { + sql, args, _ := goqu.From("items").Prepared(true).ToDeleteSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items"). + Prepared(true). + Where(goqu.Ex{"id": goqu.Op{"gt": 10}}). + ToDeleteSQL() + fmt.Println(sql, args) + + // Output: + // DELETE FROM "items" [] + // DELETE FROM "items" WHERE ("id" > ?) [10] +} + +func ExampleDataset_ToDeleteSQL_withWhere() { + sql, args, _ := goqu.From("items").Where(goqu.C("id").IsNotNull()).ToDeleteSQL() + fmt.Println(sql, args) + + // Output: + // DELETE FROM "items" WHERE ("id" IS NOT NULL) [] +} + +func ExampleDataset_ToDeleteSQL_withReturning() { + ds := goqu.From("items") + sql, args, _ := ds.Returning("id").ToDeleteSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Returning("id").Where(goqu.C("id").IsNotNull()).ToDeleteSQL() + fmt.Println(sql, args) + + // Output: + // DELETE FROM "items" RETURNING "id" [] + // DELETE FROM "items" WHERE ("id" IS NOT NULL) RETURNING "id" [] +} + +func ExampleDataset_ToTruncateSQL() { + sql, args, _ := goqu.From("items").ToTruncateSQL() + fmt.Println(sql, args) + // Output: + // TRUNCATE "items" [] +} + +func ExampleDataset_ToTruncateWithOptsSQL() { + sql, _, _ := goqu.From("items"). + ToTruncateWithOptsSQL(goqu.TruncateOptions{}) + fmt.Println(sql) + sql, _, _ = goqu.From("items"). + ToTruncateWithOptsSQL(goqu.TruncateOptions{Cascade: true}) + fmt.Println(sql) + sql, _, _ = goqu.From("items"). + ToTruncateWithOptsSQL(goqu.TruncateOptions{Restrict: true}) + fmt.Println(sql) + sql, _, _ = goqu.From("items"). + ToTruncateWithOptsSQL(goqu.TruncateOptions{Identity: "RESTART"}) + fmt.Println(sql) + sql, _, _ = goqu.From("items"). + ToTruncateWithOptsSQL(goqu.TruncateOptions{Identity: "RESTART", Cascade: true}) + fmt.Println(sql) + sql, _, _ = goqu.From("items"). + ToTruncateWithOptsSQL(goqu.TruncateOptions{Identity: "RESTART", Restrict: true}) + fmt.Println(sql) + sql, _, _ = goqu.From("items"). + ToTruncateWithOptsSQL(goqu.TruncateOptions{Identity: "CONTINUE"}) + fmt.Println(sql) + sql, _, _ = goqu.From("items"). + ToTruncateWithOptsSQL(goqu.TruncateOptions{Identity: "CONTINUE", Cascade: true}) + fmt.Println(sql) + sql, _, _ = goqu.From("items"). + ToTruncateWithOptsSQL(goqu.TruncateOptions{Identity: "CONTINUE", Restrict: true}) + fmt.Println(sql) + + // Output: + // TRUNCATE "items" + // TRUNCATE "items" CASCADE + // TRUNCATE "items" RESTRICT + // TRUNCATE "items" RESTART IDENTITY + // TRUNCATE "items" RESTART IDENTITY CASCADE + // TRUNCATE "items" RESTART IDENTITY RESTRICT + // TRUNCATE "items" CONTINUE IDENTITY + // TRUNCATE "items" CONTINUE IDENTITY CASCADE + // TRUNCATE "items" CONTINUE IDENTITY RESTRICT +} + +func ExampleDataset_Prepared() { + sql, args, _ := goqu.From("items").Prepared(true).Where(goqu.Ex{ + "col1": "a", + "col2": 1, + "col3": true, + "col4": false, + "col5": []string{"a", "b", "c"}, + }).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").Prepared(true).ToInsertSQL( + goqu.Record{"name": "Test1", "address": "111 Test Addr"}, + goqu.Record{"name": "Test2", "address": "112 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items").Prepared(true).ToUpdateSQL( + goqu.Record{"name": "Test", "address": "111 Test Addr"}, + ) + fmt.Println(sql, args) + + sql, args, _ = goqu.From("items"). + Prepared(true). + Where(goqu.Ex{"id": goqu.Op{"gt": 10}}). + ToDeleteSQL() + fmt.Println(sql, args) + + // nolint:lll + // Output: + // SELECT * FROM "items" WHERE (("col1" = ?) AND ("col2" = ?) AND ("col3" IS TRUE) AND ("col4" IS FALSE) AND ("col5" IN (?, ?, ?))) [a 1 a b c] + // INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?) [111 Test Addr Test1 112 Test Addr Test2] + // UPDATE "items" SET "address"=?,"name"=? [111 Test Addr Test] + // DELETE FROM "items" WHERE ("id" > ?) [10] +} diff --git a/dataset_sql_test.go b/dataset_sql_test.go new file mode 100644 index 00000000..eec61af4 --- /dev/null +++ b/dataset_sql_test.go @@ -0,0 +1,2844 @@ +package goqu + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "testing" + "time" + + "github.com/doug-martin/goqu/v7/exp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type datasetIntegrationTest struct { + suite.Suite +} + +func (dit *datasetIntegrationTest) SetupSuite() { + noReturn := DefaultDialectOptions() + noReturn.SupportsReturn = false + RegisterDialect("no-return", noReturn) + + limitOnDelete := DefaultDialectOptions() + limitOnDelete.SupportsLimitOnDelete = true + RegisterDialect("limit-on-delete", limitOnDelete) + + orderOnDelete := DefaultDialectOptions() + orderOnDelete.SupportsOrderByOnDelete = true + RegisterDialect("order-on-delete", orderOnDelete) + + limitOnUpdate := DefaultDialectOptions() + limitOnUpdate.SupportsLimitOnUpdate = true + RegisterDialect("limit-on-update", limitOnUpdate) + + orderOnUpdate := DefaultDialectOptions() + orderOnUpdate.SupportsOrderByOnUpdate = true + RegisterDialect("order-on-update", orderOnUpdate) +} + +func (dit *datasetIntegrationTest) TearDownSuite() { + DeregisterDialect("no-return") + DeregisterDialect("limit-on-delete") + DeregisterDialect("order-on-delete") + DeregisterDialect("limit-on-update") + DeregisterDialect("order-on-update") +} + +func (dit *datasetIntegrationTest) TestToDeleteSQLNoReturning() { + t := dit.T() + ds1 := New("no-return", nil).From("items") + _, _, err := ds1.Returning("id").ToDeleteSQL() + assert.EqualError(t, err, "goqu: adapter does not support RETURNING clause") +} + +func (dit *datasetIntegrationTest) TestToDeleteSQLWithLimit() { + t := dit.T() + ds1 := New("limit-on-delete", nil).From("items") + dsql, _, err := ds1.Limit(10).ToDeleteSQL() + assert.Nil(t, err) + assert.Equal(t, dsql, `DELETE FROM "items" LIMIT 10`) +} + +func (dit *datasetIntegrationTest) TestToDeleteSQLWithOrder() { + t := dit.T() + ds1 := New("order-on-delete", nil).From("items") + dsql, _, err := ds1.Order(C("name").Desc()).ToDeleteSQL() + assert.Nil(t, err) + assert.Equal(t, dsql, `DELETE FROM "items" ORDER BY "name" DESC`) +} + +func (dit *datasetIntegrationTest) TestToDeleteSQLNoSources() { + t := dit.T() + ds1 := From("items") + _, _, err := ds1.From().ToDeleteSQL() + assert.EqualError(t, err, "goqu: no source found when generating delete sql") +} + +func (dit *datasetIntegrationTest) TestPreparedToDeleteSQL() { + t := dit.T() + ds1 := From("items") + dsql, args, err := ds1.Prepared(true).ToDeleteSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{}) + assert.Equal(t, dsql, `DELETE FROM "items"`) + + dsql, args, err = ds1.Where(I("id").Eq(1)).Prepared(true).ToDeleteSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, dsql, `DELETE FROM "items" WHERE ("id" = ?)`) + + dsql, args, err = ds1.Returning("id").Where(I("id").Eq(1)).Prepared(true).ToDeleteSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, dsql, `DELETE FROM "items" WHERE ("id" = ?) RETURNING "id"`) +} + +func (dit *datasetIntegrationTest) TestToTruncateSQL() { + t := dit.T() + ds1 := From("items") + tsql, _, err := ds1.ToTruncateSQL() + assert.NoError(t, err) + assert.Equal(t, tsql, `TRUNCATE "items"`) +} + +func (dit *datasetIntegrationTest) TestToTruncateSQLNoSources() { + t := dit.T() + ds1 := From("items") + _, _, err := ds1.From().ToTruncateSQL() + assert.EqualError(t, err, "goqu: no source found when generating truncate sql") +} + +func (dit *datasetIntegrationTest) TestToTruncateSQLWithOpts() { + t := dit.T() + ds1 := From("items") + tsql, _, err := ds1.ToTruncateWithOptsSQL(TruncateOptions{Cascade: true}) + assert.NoError(t, err) + assert.Equal(t, tsql, `TRUNCATE "items" CASCADE`) + + tsql, _, err = ds1.ToTruncateWithOptsSQL(TruncateOptions{Restrict: true}) + assert.NoError(t, err) + assert.Equal(t, tsql, `TRUNCATE "items" RESTRICT`) + + tsql, _, err = ds1.ToTruncateWithOptsSQL(TruncateOptions{Identity: "restart"}) + assert.NoError(t, err) + assert.Equal(t, tsql, `TRUNCATE "items" RESTART IDENTITY`) + + tsql, _, err = ds1.ToTruncateWithOptsSQL(TruncateOptions{Identity: "continue"}) + assert.NoError(t, err) + assert.Equal(t, tsql, `TRUNCATE "items" CONTINUE IDENTITY`) +} + +func (dit *datasetIntegrationTest) TestPreparedToTruncateSQL() { + t := dit.T() + ds1 := From("items") + tsql, args, err := ds1.ToTruncateSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{}) + assert.Equal(t, tsql, `TRUNCATE "items"`) +} + +func (dit *datasetIntegrationTest) TestInsertNullTime() { + t := dit.T() + ds1 := From("items") + type item struct { + CreatedAt *time.Time `db:"created_at"` + } + insertSQL, _, err := ds1.ToInsertSQL(item{CreatedAt: nil}) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("created_at") VALUES (NULL)`) +} + +func (dit *datasetIntegrationTest) TestToInsertSQLNoReturning() { + t := dit.T() + ds1 := New("no-return", nil).From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + _, _, err := ds1.Returning("id").ToInsertSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.EqualError(t, err, "goqu: adapter does not support RETURNING clause") + + _, _, err = ds1.Returning("id").ToInsertSQL(From("test2")) + assert.EqualError(t, err, "goqu: adapter does not support RETURNING clause") +} + +func (dit *datasetIntegrationTest) TestInsert_InvalidValue() { + t := dit.T() + ds1 := From("no-return").From("items") + _, _, err := ds1.ToInsertSQL(true) + assert.EqualError(t, err, "goqu: unsupported insert must be map, goqu.Record, or struct type got: bool") +} + +func (dit *datasetIntegrationTest) TestToInsertSQLWithStructs() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + Created time.Time `db:"created"` + } + created, _ := time.Parse("2006-01-02", "2015-01-01") + insertSQL, _, err := ds1.ToInsertSQL(item{Name: "Test", Address: "111 Test Addr", Created: created}) + assert.NoError(t, err) + assert.Equal(t, insertSQL, + `INSERT INTO "items" ("address", "name", "created") VALUES ('111 Test Addr', 'Test', '`+created.Format(time.RFC3339Nano)+`')`, + ) // #nosec + + insertSQL, _, err = ds1.ToInsertSQL( + item{Address: "111 Test Addr", Name: "Test1", Created: created}, + item{Address: "211 Test Addr", Name: "Test2", Created: created}, + item{Address: "311 Test Addr", Name: "Test3", Created: created}, + item{Address: "411 Test Addr", Name: "Test4", Created: created}, + ) + assert.NoError(t, err) + assert.Equal(t, insertSQL, + `INSERT INTO "items" ("address", "name", "created") VALUES `+ + `('111 Test Addr', 'Test1', '`+created.Format(time.RFC3339Nano)+`'), `+ + `('211 Test Addr', 'Test2', '`+created.Format(time.RFC3339Nano)+`'), `+ + `('311 Test Addr', 'Test3', '`+created.Format(time.RFC3339Nano)+`'), `+ + `('411 Test Addr', 'Test4', '`+created.Format(time.RFC3339Nano)+`')`, + ) +} + +func (dit *datasetIntegrationTest) TestToInsertSQLWithEmbeddedStruct() { + t := dit.T() + ds1 := From("items") + type Phone struct { + Primary string `db:"primary_phone"` + Home string `db:"home_phone"` + } + type item struct { + Phone + Address string `db:"address"` + Name string `db:"name"` + } + insertSQL, _, err := ds1.ToInsertSQL(item{ + Name: "Test", + Address: "111 Test Addr", + Phone: Phone{ + Home: "123123", + Primary: "456456", + }, + }) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name") VALUES `+ + `('456456', '123123', '111 Test Addr', 'Test')`) + + insertSQL, _, err = ds1.ToInsertSQL( + item{Address: "111 Test Addr", Name: "Test1", Phone: Phone{Home: "123123", Primary: "456456"}}, + item{Address: "211 Test Addr", Name: "Test2", Phone: Phone{Home: "123123", Primary: "456456"}}, + item{Address: "311 Test Addr", Name: "Test3", Phone: Phone{Home: "123123", Primary: "456456"}}, + item{Address: "411 Test Addr", Name: "Test4", Phone: Phone{Home: "123123", Primary: "456456"}}, + ) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name") VALUES `+ + `('456456', '123123', '111 Test Addr', 'Test1'), `+ + `('456456', '123123', '211 Test Addr', 'Test2'), `+ + `('456456', '123123', '311 Test Addr', 'Test3'), `+ + `('456456', '123123', '411 Test Addr', 'Test4')`) +} + +func (dit *datasetIntegrationTest) TestToInsertSQLWithEmbeddedStructPtr() { + t := dit.T() + ds1 := From("items") + type Phone struct { + Primary string `db:"primary_phone"` + Home string `db:"home_phone"` + } + type item struct { + *Phone + Address string `db:"address"` + Name string `db:"name"` + Valuer sql.NullInt64 `db:"valuer"` + } + insertSQL, _, err := ds1.ToInsertSQL(item{ + Name: "Test", + Address: "111 Test Addr", + Valuer: sql.NullInt64{Int64: 10, Valid: true}, + Phone: &Phone{Home: "123123", Primary: "456456"}, + }) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" `+ + `("primary_phone", "home_phone", "address", "name", "valuer")`+ + ` VALUES ('456456', '123123', '111 Test Addr', 'Test', 10)`) + + insertSQL, _, err = ds1.ToInsertSQL( + item{Address: "111 Test Addr", Name: "Test1", Phone: &Phone{Home: "123123", Primary: "456456"}}, + item{Address: "211 Test Addr", Name: "Test2", Phone: &Phone{Home: "123123", Primary: "456456"}}, + item{Address: "311 Test Addr", Name: "Test3", Phone: &Phone{Home: "123123", Primary: "456456"}}, + item{Address: "411 Test Addr", Name: "Test4", Phone: &Phone{Home: "123123", Primary: "456456"}}, + ) + assert.NoError(t, err) + assert.Equal(t, insertSQL, + `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name", "valuer") VALUES `+ + `('456456', '123123', '111 Test Addr', 'Test1', NULL), `+ + `('456456', '123123', '211 Test Addr', 'Test2', NULL), `+ + `('456456', '123123', '311 Test Addr', 'Test3', NULL), `+ + `('456456', '123123', '411 Test Addr', 'Test4', NULL)`) +} + +func (dit *datasetIntegrationTest) TestToInsertSQLWithValuer() { + t := dit.T() + ds1 := From("items") + + type item struct { + Address string `db:"address"` + Name string `db:"name"` + Valuer sql.NullInt64 `db:"valuer"` + } + sqlString, _, err := ds1.ToInsertSQL(item{Name: "Test", Address: "111 Test Addr", Valuer: sql.NullInt64{Int64: 10, Valid: true}}) + assert.NoError(t, err) + assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES ('111 Test Addr', 'Test', 10)`) + + sqlString, _, err = ds1.ToInsertSQL( + item{Address: "111 Test Addr", Name: "Test1", Valuer: sql.NullInt64{Int64: 10, Valid: true}}, + item{Address: "211 Test Addr", Name: "Test2", Valuer: sql.NullInt64{Int64: 10, Valid: true}}, + item{Address: "311 Test Addr", Name: "Test3", Valuer: sql.NullInt64{Int64: 10, Valid: true}}, + item{Address: "411 Test Addr", Name: "Test4", Valuer: sql.NullInt64{Int64: 10, Valid: true}}, + ) + assert.NoError(t, err) + assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES `+ + `('111 Test Addr', 'Test1', 10), `+ + `('211 Test Addr', 'Test2', 10), `+ + `('311 Test Addr', 'Test3', 10), `+ + `('411 Test Addr', 'Test4', 10)`) +} + +func (dit *datasetIntegrationTest) TestToInsertSQLWithValuerNull() { + t := dit.T() + ds1 := From("items") + + type item struct { + Address string `db:"address"` + Name string `db:"name"` + Valuer sql.NullInt64 `db:"valuer"` + } + sqlString, _, err := ds1.ToInsertSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal( + t, + sqlString, + `INSERT INTO "items" ("address", "name", "valuer") VALUES ('111 Test Addr', 'Test', NULL)`, + ) + + sqlString, _, err = ds1.ToInsertSQL( + item{Address: "111 Test Addr", Name: "Test1"}, + item{Address: "211 Test Addr", Name: "Test2"}, + item{Address: "311 Test Addr", Name: "Test3"}, + item{Address: "411 Test Addr", Name: "Test4"}, + ) + assert.NoError(t, err) + assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES `+ + `('111 Test Addr', 'Test1', NULL), `+ + `('211 Test Addr', 'Test2', NULL), `+ + `('311 Test Addr', 'Test3', NULL), `+ + `('411 Test Addr', 'Test4', NULL)`) +} + +func (dit *datasetIntegrationTest) TestToInsertSQLWithMaps() { + t := dit.T() + ds1 := From("items") + + insertSQL, _, err := ds1.ToInsertSQL(map[string]interface{}{"name": "Test", "address": "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test')`) + + insertSQL, _, err = ds1.ToInsertSQL( + map[string]interface{}{"address": "111 Test Addr", "name": "Test1"}, + map[string]interface{}{"address": "211 Test Addr", "name": "Test2"}, + map[string]interface{}{"address": "311 Test Addr", "name": "Test3"}, + map[string]interface{}{"address": "411 Test Addr", "name": "Test4"}, + ) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES `+ + `('111 Test Addr', 'Test1'), `+ + `('211 Test Addr', 'Test2'), `+ + `('311 Test Addr', 'Test3'), `+ + `('411 Test Addr', 'Test4')`) + + _, _, err = ds1.ToInsertSQL( + map[string]interface{}{"address": "111 Test Addr", "name": "Test1"}, + map[string]interface{}{"address": "211 Test Addr"}, + map[string]interface{}{"address": "311 Test Addr", "name": "Test3"}, + map[string]interface{}{"address": "411 Test Addr", "name": "Test4"}, + ) + assert.EqualError(t, err, "goqu: rows with different value length expected 2 got 1") +} + +func (dit *datasetIntegrationTest) TestToInsertSQLWitSQLBuilder() { + t := dit.T() + ds1 := From("items") + + insertSQL, _, err := ds1.ToInsertSQL(From("other_items")) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" SELECT * FROM "other_items"`) +} + +func (dit *datasetIntegrationTest) TestInsertReturning() { + t := dit.T() + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + ds1 := From("items").Returning("id") + + insertSQL, _, err := ds1.Returning("id").ToInsertSQL(From("other_items")) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" SELECT * FROM "other_items" RETURNING "id"`) + + insertSQL, _, err = ds1.ToInsertSQL(map[string]interface{}{"name": "Test", "address": "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal( + t, + insertSQL, + `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test') RETURNING "id"`, + ) + + insertSQL, _, err = ds1.ToInsertSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal( + t, + insertSQL, + `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test') RETURNING "id"`, + ) +} + +func (dit *datasetIntegrationTest) TestToInsertSQLWithNoFrom() { + t := dit.T() + ds1 := From("test").From() + _, _, err := ds1.ToInsertSQL(map[string]interface{}{"address": "111 Test Addr", "name": "Test1"}) + assert.EqualError(t, err, "goqu: no source found when generating insert sql") +} + +func (dit *datasetIntegrationTest) TestToInsertSQLWithMapsWithDifferentLengths() { + t := dit.T() + ds1 := From("items") + _, _, err := ds1.ToInsertSQL( + map[string]interface{}{"address": "111 Test Addr", "name": "Test1"}, + map[string]interface{}{"address": "211 Test Addr"}, + map[string]interface{}{"address": "311 Test Addr", "name": "Test3"}, + map[string]interface{}{"address": "411 Test Addr", "name": "Test4"}, + ) + assert.EqualError(t, err, "goqu: rows with different value length expected 2 got 1") +} + +func (dit *datasetIntegrationTest) TestToInsertSQLWitDifferentKeys() { + t := dit.T() + ds1 := From("items") + _, _, err := ds1.ToInsertSQL( + map[string]interface{}{"address": "111 Test Addr", "name": "test"}, + map[string]interface{}{"phoneNumber": 10, "address": "111 Test Addr"}, + ) + assert.EqualError( + t, + err, + `goqu: rows with different keys expected ["address","name"] got ["address","phoneNumber"]`, + ) +} + +func (dit *datasetIntegrationTest) TestToInsertSQLDifferentTypes() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + type item2 struct { + Address string `db:"address"` + Name string `db:"name"` + } + _, _, err := ds1.ToInsertSQL( + item{Address: "111 Test Addr", Name: "Test1"}, + item2{Address: "211 Test Addr", Name: "Test2"}, + item{Address: "311 Test Addr", Name: "Test3"}, + item2{Address: "411 Test Addr", Name: "Test4"}, + ) + assert.EqualError(t, err, "goqu: rows must be all the same type expected goqu.item got goqu.item2") + + _, _, err = ds1.ToInsertSQL( + item{Address: "111 Test Addr", Name: "Test1"}, + map[string]interface{}{"address": "211 Test Addr", "name": "Test2"}, + item{Address: "311 Test Addr", Name: "Test3"}, + map[string]interface{}{"address": "411 Test Addr", "name": "Test4"}, + ) + assert.EqualError( + t, + err, + "goqu: rows must be all the same type expected goqu.item got map[string]interface {}", + ) +} + +func (dit *datasetIntegrationTest) TestInsertWithGoquPkTagSQL() { + t := dit.T() + ds1 := From("items") + type item struct { + ID uint32 `db:"id" goqu:"pk,skipinsert"` + Address string `db:"address"` + Name string `db:"name"` + } + insertSQL, _, err := ds1.ToInsertSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test')`) + + insertSQL, _, err = ds1.ToInsertSQL(map[string]interface{}{"name": "Test", "address": "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test')`) + + insertSQL, _, err = ds1.ToInsertSQL( + item{Name: "Test1", Address: "111 Test Addr"}, + item{Name: "Test2", Address: "211 Test Addr"}, + item{Name: "Test3", Address: "311 Test Addr"}, + item{Name: "Test4", Address: "411 Test Addr"}, + ) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES `+ + `('111 Test Addr', 'Test1'), `+ + `('211 Test Addr', 'Test2'), `+ + `('311 Test Addr', 'Test3'), `+ + `('411 Test Addr', 'Test4')`) +} + +func (dit *datasetIntegrationTest) TestInsertWithGoquSkipInsertTagSQL() { + t := dit.T() + ds1 := From("items") + type item struct { + ID uint32 `db:"id" goqu:"skipinsert"` + Address string `db:"address"` + Name string `db:"name"` + } + insertSQL, _, err := ds1.ToInsertSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test')`) + + insertSQL, _, err = ds1.ToInsertSQL( + item{Name: "Test1", Address: "111 Test Addr"}, + item{Name: "Test2", Address: "211 Test Addr"}, + item{Name: "Test3", Address: "311 Test Addr"}, + item{Name: "Test4", Address: "411 Test Addr"}, + ) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES `+ + `('111 Test Addr', 'Test1'), `+ + `('211 Test Addr', 'Test2'), `+ + `('311 Test Addr', 'Test3'), `+ + `('411 Test Addr', 'Test4')`) +} + +func (dit *datasetIntegrationTest) TestInsertDefaultValues() { + t := dit.T() + ds1 := From("items") + + insertSQL, _, err := ds1.ToInsertSQL() + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" DEFAULT VALUES`) + + insertSQL, _, err = ds1.ToInsertSQL(map[string]interface{}{"name": Default(), "address": Default()}) + assert.NoError(t, err) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (DEFAULT, DEFAULT)`) + +} + +func (dit *datasetIntegrationTest) TestPreparedToInsertSQLWithStructs() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + insertSQL, args, err := ds1.Prepared(true).ToInsertSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (?, ?)`) + + insertSQL, args, err = ds1.Prepared(true).ToInsertSQL( + item{Address: "111 Test Addr", Name: "Test1"}, + item{Address: "211 Test Addr", Name: "Test2"}, + item{Address: "311 Test Addr", Name: "Test3"}, + item{Address: "411 Test Addr", Name: "Test4"}, + ) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{ + "111 Test Addr", + "Test1", + "211 Test Addr", + "Test2", + "311 Test Addr", + "Test3", + "411 Test Addr", + "Test4", + }) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?), (?, ?), (?, ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedToInsertSQLWithMaps() { + t := dit.T() + ds1 := From("items") + + insertSQL, args, err := ds1.Prepared(true).ToInsertSQL(map[string]interface{}{ + "name": "Test", + "address": "111 Test Addr", + }) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (?, ?)`) + + insertSQL, args, err = ds1.Prepared(true).ToInsertSQL( + map[string]interface{}{"address": "111 Test Addr", "name": "Test1"}, + map[string]interface{}{"address": "211 Test Addr", "name": "Test2"}, + map[string]interface{}{"address": "311 Test Addr", "name": "Test3"}, + map[string]interface{}{"address": "411 Test Addr", "name": "Test4"}, + ) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{ + "111 Test Addr", + "Test1", + "211 Test Addr", + "Test2", + "311 Test Addr", + "Test3", + "411 Test Addr", + "Test4", + }) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?), (?, ?), (?, ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedToInsertSQLWitSQLBuilder() { + t := dit.T() + ds1 := From("items") + + insertSQL, args, err := ds1. + Prepared(true). + ToInsertSQL( + From("other_items").Where(C("b").Gt(10)), + ) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(10)}) + assert.Equal(t, insertSQL, `INSERT INTO "items" SELECT * FROM "other_items" WHERE ("b" > ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedInsertReturning() { + t := dit.T() + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + ds1 := From("items").Returning("id") + + insertSQL, args, err := ds1. + Returning("id"). + Prepared(true). + ToInsertSQL(From("other_items").Where(C("b").Gt(10))) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(10)}) + assert.Equal(t, insertSQL, `INSERT INTO "items" SELECT * FROM "other_items" WHERE ("b" > ?) RETURNING "id"`) + + insertSQL, args, err = ds1.Prepared(true).ToInsertSQL(map[string]interface{}{ + "name": "Test", + "address": "111 Test Addr", + }) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (?, ?) RETURNING "id"`) + + insertSQL, args, err = ds1.Prepared(true).ToInsertSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (?, ?) RETURNING "id"`) +} + +func (dit *datasetIntegrationTest) TestPreparedInsertWithGoquPkTagSQL() { + t := dit.T() + ds1 := From("items") + type item struct { + ID uint32 `db:"id" goqu:"pk,skipinsert"` + Address string `db:"address"` + Name string `db:"name"` + } + insertSQL, args, err := ds1.Prepared(true).ToInsertSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (?, ?)`) + + insertSQL, args, err = ds1.Prepared(true).ToInsertSQL(map[string]interface{}{ + "name": "Test", + "address": "111 Test Addr", + }) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (?, ?)`) + + insertSQL, args, err = ds1.Prepared(true).ToInsertSQL( + item{Name: "Test1", Address: "111 Test Addr"}, + item{Name: "Test2", Address: "211 Test Addr"}, + item{Name: "Test3", Address: "311 Test Addr"}, + item{Name: "Test4", Address: "411 Test Addr"}, + ) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{ + "111 Test Addr", + "Test1", + "211 Test Addr", + "Test2", + "311 Test Addr", + "Test3", + "411 Test Addr", + "Test4", + }) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?), (?, ?), (?, ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedInsertWithGoquSkipInsertTagSQL() { + t := dit.T() + ds1 := From("items") + type item struct { + ID uint32 `db:"id" goqu:"skipinsert"` + Address string `db:"address"` + Name string `db:"name"` + } + insertSQL, args, err := ds1.Prepared(true).ToInsertSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (?, ?)`) + + insertSQL, args, err = ds1.Prepared(true).ToInsertSQL( + item{Name: "Test1", Address: "111 Test Addr"}, + item{Name: "Test2", Address: "211 Test Addr"}, + item{Name: "Test3", Address: "311 Test Addr"}, + item{Name: "Test4", Address: "411 Test Addr"}, + ) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{ + "111 Test Addr", + "Test1", + "211 Test Addr", + "Test2", + "311 Test Addr", + "Test3", + "411 Test Addr", + "Test4", + }) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?), (?, ?), (?, ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedInsertDefaultValues() { + t := dit.T() + ds1 := From("items") + + insertSQL, args, err := ds1.Prepared(true).ToInsertSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{}) + assert.Equal(t, insertSQL, `INSERT INTO "items" DEFAULT VALUES`) + + insertSQL, args, err = ds1.Prepared(true).ToInsertSQL(map[string]interface{}{ + "name": Default(), + "address": Default(), + }) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{}) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("address", "name") VALUES (DEFAULT, DEFAULT)`) + +} + +func (dit *datasetIntegrationTest) TestPreparedToInsertSQLWithEmbeddedStruct() { + t := dit.T() + ds1 := From("items") + type Phone struct { + Primary string `db:"primary_phone"` + Home string `db:"home_phone"` + } + type item struct { + Phone + Address string `db:"address"` + Name string `db:"name"` + } + insertSQL, args, err := ds1.Prepared(true).ToInsertSQL(item{ + Name: "Test", + Address: "111 Test Addr", + Phone: Phone{ + Home: "123123", + Primary: "456456", + }, + }) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"456456", "123123", "111 Test Addr", "Test"}) + assert.Equal( + t, + insertSQL, + `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name") VALUES (?, ?, ?, ?)`, + ) + + insertSQL, args, err = ds1.Prepared(true).ToInsertSQL( + item{Address: "111 Test Addr", Name: "Test1", Phone: Phone{Home: "123123", Primary: "456456"}}, + item{Address: "211 Test Addr", Name: "Test2", Phone: Phone{Home: "123123", Primary: "456456"}}, + item{Address: "311 Test Addr", Name: "Test3", Phone: Phone{Home: "123123", Primary: "456456"}}, + item{Address: "411 Test Addr", Name: "Test4", Phone: Phone{Home: "123123", Primary: "456456"}}, + ) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{ + "456456", "123123", "111 Test Addr", "Test1", + "456456", "123123", "211 Test Addr", "Test2", + "456456", "123123", "311 Test Addr", "Test3", + "456456", "123123", "411 Test Addr", "Test4", + }) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name") VALUES `+ + `(?, ?, ?, ?), `+ + `(?, ?, ?, ?), `+ + `(?, ?, ?, ?), `+ + `(?, ?, ?, ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedToInsertSQLWithEmbeddedStructPtr() { + t := dit.T() + ds1 := From("items") + type Phone struct { + Primary string `db:"primary_phone"` + Home string `db:"home_phone"` + } + type item struct { + *Phone + Address string `db:"address"` + Name string `db:"name"` + } + insertSQL, args, err := ds1.Prepared(true).ToInsertSQL(item{ + Name: "Test", + Address: "111 Test Addr", + Phone: &Phone{ + Home: "123123", + Primary: "456456", + }, + }) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"456456", "123123", "111 Test Addr", "Test"}) + assert.Equal( + t, + insertSQL, + `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name") VALUES (?, ?, ?, ?)`, + ) + + insertSQL, args, err = ds1.Prepared(true).ToInsertSQL( + item{Address: "111 Test Addr", Name: "Test1", Phone: &Phone{Home: "123123", Primary: "456456"}}, + item{Address: "211 Test Addr", Name: "Test2", Phone: &Phone{Home: "123123", Primary: "456456"}}, + item{Address: "311 Test Addr", Name: "Test3", Phone: &Phone{Home: "123123", Primary: "456456"}}, + item{Address: "411 Test Addr", Name: "Test4", Phone: &Phone{Home: "123123", Primary: "456456"}}, + ) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{ + "456456", "123123", "111 Test Addr", "Test1", + "456456", "123123", "211 Test Addr", "Test2", + "456456", "123123", "311 Test Addr", "Test3", + "456456", "123123", "411 Test Addr", "Test4", + }) + assert.Equal(t, insertSQL, `INSERT INTO "items" ("primary_phone", "home_phone", "address", "name") VALUES `+ + `(?, ?, ?, ?), `+ + `(?, ?, ?, ?), `+ + `(?, ?, ?, ?), `+ + `(?, ?, ?, ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedToInsertSQLWithValuer() { + t := dit.T() + ds1 := From("items") + + type item struct { + Address string `db:"address"` + Name string `db:"name"` + Valuer sql.NullInt64 `db:"valuer"` + } + sqlString, args, err := ds1.Prepared(true).ToInsertSQL(item{ + Name: "Test", + Address: "111 Test Addr", + Valuer: sql.NullInt64{Int64: 10, Valid: true}, + }) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{ + "111 Test Addr", "Test", int64(10), + }) + assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES (?, ?, ?)`) + + sqlString, args, err = ds1.Prepared(true).ToInsertSQL( + item{Address: "111 Test Addr", Name: "Test1", Valuer: sql.NullInt64{Int64: 10, Valid: true}}, + item{Address: "211 Test Addr", Name: "Test2", Valuer: sql.NullInt64{Int64: 20, Valid: true}}, + item{Address: "311 Test Addr", Name: "Test3", Valuer: sql.NullInt64{Int64: 30, Valid: true}}, + item{Address: "411 Test Addr", Name: "Test4", Valuer: sql.NullInt64{Int64: 40, Valid: true}}, + ) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{ + "111 Test Addr", "Test1", int64(10), + "211 Test Addr", "Test2", int64(20), + "311 Test Addr", "Test3", int64(30), + "411 Test Addr", "Test4", int64(40), + }) + assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES `+ + `(?, ?, ?), `+ + `(?, ?, ?), `+ + `(?, ?, ?), `+ + `(?, ?, ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedToInsertSQLWithValuerNull() { + t := dit.T() + ds1 := From("items") + + type item struct { + Address string `db:"address"` + Name string `db:"name"` + Valuer sql.NullInt64 `db:"valuer"` + } + sqlString, args, err := ds1.Prepared(true).ToInsertSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{ + "111 Test Addr", "Test", + }) + assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES (?, ?, NULL)`) + + sqlString, args, err = ds1.Prepared(true).ToInsertSQL( + item{Address: "111 Test Addr", Name: "Test1"}, + item{Address: "211 Test Addr", Name: "Test2"}, + item{Address: "311 Test Addr", Name: "Test3"}, + item{Address: "411 Test Addr", Name: "Test4"}, + ) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{ + "111 Test Addr", "Test1", + "211 Test Addr", "Test2", + "311 Test Addr", "Test3", + "411 Test Addr", "Test4", + }) + assert.Equal(t, sqlString, `INSERT INTO "items" ("address", "name", "valuer") VALUES `+ + `(?, ?, NULL), `+ + `(?, ?, NULL), `+ + `(?, ?, NULL), `+ + `(?, ?, NULL)`) +} + +func (dit *datasetIntegrationTest) TestToInsertConflictSQL__OnConflictIsNil() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + insertSQL, _, err := ds1.ToInsertConflictSQL(nil, item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test')`, insertSQL) +} + +func (dit *datasetIntegrationTest) TestToInsertConflictSQL__OnConflictexpDoUpdate() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + i := item{Name: "Test", Address: "111 Test Addr"} + insertSQL, _, err := ds1.ToInsertConflictSQL( + DoUpdate("name", Record{"address": L("excluded.address")}), + i, + ) + assert.NoError(t, err) + assert.Equal(t, `INSERT INTO "items" ("address", "name") VALUES `+ + `('111 Test Addr', 'Test') `+ + `ON CONFLICT (name) `+ + `DO UPDATE `+ + `SET "address"=excluded.address`, insertSQL) +} + +func (dit *datasetIntegrationTest) TestToInsertConflictSQL__OnConflictDoUpdateWhere() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + i := item{Name: "Test", Address: "111 Test Addr"} + + insertSQL, _, err := ds1.ToInsertConflictSQL( + DoUpdate("name", Record{"address": L("excluded.address")}). + Where(C("name").Eq("Test")), + i, + ) + assert.NoError(t, err) + assert.Equal(t, `INSERT INTO "items" ("address", "name") VALUES `+ + `('111 Test Addr', 'Test') `+ + `ON CONFLICT (name) `+ + `DO UPDATE `+ + `SET "address"=excluded.address WHERE ("name" = 'Test')`, insertSQL) +} + +func (dit *datasetIntegrationTest) TestToInsertConflictSQLWithDataset__OnConflictDoUpdateWhere() { + t := dit.T() + ds1 := From("items") + ds2 := From("ds2") + + insertSQL, _, err := ds1.ToInsertConflictSQL( + DoUpdate("name", Record{"address": L("excluded.address")}). + Where(C("name").Eq("Test")), + ds2, + ) + assert.NoError(t, err) + assert.Equal(t, `INSERT INTO "items" `+ + `SELECT * FROM "ds2" `+ + `ON CONFLICT (name) `+ + `DO UPDATE `+ + `SET "address"=excluded.address WHERE ("name" = 'Test')`, insertSQL) +} + +func (dit *datasetIntegrationTest) TestInsertConflict__ImplementsConflictExpressionInterface() { + t := dit.T() + assert.Implements(t, (*exp.ConflictExpression)(nil), DoNothing()) + assert.Implements(t, (*exp.ConflictExpression)(nil), DoUpdate("", nil)) +} + +func (dit *datasetIntegrationTest) TestToInsertIgnoreSQL() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + insertSQL, _, err := ds1.ToInsertIgnoreSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, `INSERT INTO "items" ("address", "name") VALUES `+ + `('111 Test Addr', 'Test') `+ + `ON CONFLICT DO NOTHING`, insertSQL) +} +func (dit *datasetIntegrationTest) TestToUpdateSQLWithNoSources() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + _, _, err := ds1.From().ToUpdateSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.EqualError(t, err, "goqu: no source found when generating update sql") +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLNoReturning() { + t := dit.T() + ds1 := New("no-return", nil).From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + _, _, err := ds1.Returning("id").ToUpdateSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.EqualError(t, err, "goqu: adapter does not support RETURNING clause") +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithLimit() { + t := dit.T() + ds1 := New("limit-on-update", nil).From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + updateSQL, _, err := ds1.Limit(10).ToUpdateSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.Nil(t, err) + assert.Equal(t, updateSQL, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test' LIMIT 10`) +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithOrder() { + t := dit.T() + ds1 := New("order-on-update", nil).From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + updateSQL, _, err := ds1.Order(C("name").Desc()).ToUpdateSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.Nil(t, err) + assert.Equal(t, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test' ORDER BY "name" DESC`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithStructs() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + updateSQL, _, err := ds1.ToUpdateSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test'`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithMaps() { + t := dit.T() + ds1 := From("items") + updateSQL, _, err := ds1.ToUpdateSQL(Record{"name": "Test", "address": "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test'`, updateSQL) + +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithByteSlice() { + t := dit.T() + ds1 := From("items") + type item struct { + Name string `db:"name"` + Data []byte `db:"data"` + } + updateSQL, _, err := ds1. + Returning(T("items").All()). + ToUpdateSQL(item{Name: "Test", Data: []byte(`{"someJson":"data"}`)}) + assert.NoError(t, err) + assert.Equal(t, `UPDATE "items" SET "name"='Test',"data"='{"someJson":"data"}' RETURNING "items".*`, updateSQL) +} + +type valuerType []byte + +func (j valuerType) Value() (driver.Value, error) { + return []byte(fmt.Sprintf("%s World", string(j))), nil +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithCustomValuer() { + t := dit.T() + ds1 := From("items") + type item struct { + Name string `db:"name"` + Data valuerType `db:"data"` + } + updateSQL, _, err := ds1. + Returning(T("items").All()). + ToUpdateSQL(item{Name: "Test", Data: []byte(`Hello`)}) + assert.NoError(t, err) + assert.Equal(t, `UPDATE "items" SET "name"='Test',"data"='Hello World' RETURNING "items".*`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithValuer() { + t := dit.T() + ds1 := From("items") + type item struct { + Name string `db:"name"` + Data sql.NullString `db:"data"` + } + + updateSQL, _, err := ds1. + Returning(T("items").All()). + ToUpdateSQL(item{Name: "Test", Data: sql.NullString{String: "Hello World", Valid: true}}) + assert.NoError(t, err) + assert.Equal(t, `UPDATE "items" SET "name"='Test',"data"='Hello World' RETURNING "items".*`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithValuerNull() { + t := dit.T() + ds1 := From("items") + type item struct { + Name string `db:"name"` + Data sql.NullString `db:"data"` + } + updateSQL, _, err := ds1.Returning(T("items").All()).ToUpdateSQL(item{Name: "Test"}) + assert.NoError(t, err) + assert.Equal(t, `UPDATE "items" SET "name"='Test',"data"=NULL RETURNING "items".*`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithEmbeddedStruct() { + t := dit.T() + ds1 := From("items") + type Phone struct { + Primary string `db:"primary_phone"` + Home string `db:"home_phone"` + Created time.Time `db:"phone_created"` + } + type item struct { + Phone + Address string `db:"address" goqu:"skipupdate"` + Name string `db:"name"` + Created time.Time `db:"created"` + NilPointer interface{} `db:"nil_pointer"` + } + created, _ := time.Parse("2006-01-02", "2015-01-01") + + updateSQL, args, err := ds1.ToUpdateSQL(item{ + Name: "Test", + Address: "111 Test Addr", + Created: created, + Phone: Phone{ + Home: "123123", + Primary: "456456", + Created: created, + }, + }) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{}) + assert.Equal(t, `UPDATE "items" SET `+ + `"primary_phone"='456456',`+ + `"home_phone"='123123',`+ + `"phone_created"='2015-01-01T00:00:00Z',`+ + `"name"='Test',`+ + `"created"='2015-01-01T00:00:00Z',`+ + `"nil_pointer"=NULL`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithEmbeddedStructPtr() { + t := dit.T() + ds1 := From("items") + type Phone struct { + Primary string `db:"primary_phone"` + Home string `db:"home_phone"` + Created time.Time `db:"phone_created"` + } + type item struct { + *Phone + Address string `db:"address" goqu:"skipupdate"` + Name string `db:"name"` + Created time.Time `db:"created"` + } + created, _ := time.Parse("2006-01-02", "2015-01-01") + + updateSQL, args, err := ds1.ToUpdateSQL(item{ + Name: "Test", + Address: "111 Test Addr", + Created: created, + Phone: &Phone{ + Home: "123123", + Primary: "456456", + Created: created, + }, + }) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{}) + assert.Equal(t, `UPDATE "items" SET `+ + `"primary_phone"='456456',`+ + `"home_phone"='123123',`+ + `"phone_created"='2015-01-01T00:00:00Z',`+ + `"name"='Test',`+ + `"created"='2015-01-01T00:00:00Z'`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithUnsupportedType() { + t := dit.T() + ds1 := From("items") + _, _, err := ds1.ToUpdateSQL([]string{"HELLO"}) + assert.EqualError(t, err, "goqu: unsupported update interface type []string") +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithSkipupdateTag() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address" goqu:"skipupdate"` + Name string `db:"name"` + } + updateSQL, _, err := ds1.ToUpdateSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, `UPDATE "items" SET "name"='Test'`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithWhere() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + updateSQL, _, err := ds1. + Where(C("name").IsNull()). + ToUpdateSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test' WHERE ("name" IS NULL)`, updateSQL) + + updateSQL, _, err = ds1. + Where(C("name").IsNull()). + ToUpdateSQL(Record{"name": "Test", "address": "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test' WHERE ("name" IS NULL)`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestToUpdateSQLWithReturning() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + updateSQL, _, err := ds1. + Returning(T("items").All()). + ToUpdateSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test' RETURNING "items".*`, updateSQL) + + updateSQL, _, err = ds1. + Where(C("name").IsNull()). + Returning(L(`"items".*`)). + ToUpdateSQL(Record{"name": "Test", "address": "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test' WHERE ("name" IS NULL) RETURNING "items".*`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestPreparedToUpdateSQLWithStructs() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + updateSQL, args, err := ds1. + Prepared(true). + ToUpdateSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, `UPDATE "items" SET "address"=?,"name"=?`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestPreparedToUpdateSQLWithMaps() { + t := dit.T() + ds1 := From("items") + updateSQL, args, err := ds1. + Prepared(true). + ToUpdateSQL(Record{"name": "Test", "address": "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, `UPDATE "items" SET "address"=?,"name"=?`, updateSQL) + +} + +func (dit *datasetIntegrationTest) TestPreparedToUpdateSQLWithByteSlice() { + t := dit.T() + ds1 := From("items") + type item struct { + Name string `db:"name"` + Data []byte `db:"data"` + } + updateSQL, args, err := ds1. + Returning(T("items").All()). + Prepared(true). + ToUpdateSQL(item{Name: "Test", Data: []byte(`{"someJson":"data"}`)}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"Test", []byte(`{"someJson":"data"}`)}) + assert.Equal(t, `UPDATE "items" SET "name"=?,"data"=? RETURNING "items".*`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestPreparedToUpdateSQLWithCustomValuer() { + t := dit.T() + ds1 := From("items") + type item struct { + Name string `db:"name"` + Data valuerType `db:"data"` + } + updateSQL, args, err := ds1. + Returning(T("items").All()). + Prepared(true). + ToUpdateSQL(item{Name: "Test", Data: []byte(`Hello`)}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"Test", []byte("Hello World")}) + assert.Equal(t, `UPDATE "items" SET "name"=?,"data"=? RETURNING "items".*`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestPreparedToUpdateSQLWithValuer() { + t := dit.T() + ds1 := From("items") + type item struct { + Name string `db:"name"` + Data sql.NullString `db:"data"` + } + updateSQL, args, err := ds1. + Returning(T("items").All()). + Prepared(true). + ToUpdateSQL(item{Name: "Test", Data: sql.NullString{String: "Hello World", Valid: true}}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"Test", "Hello World"}) + assert.Equal(t, `UPDATE "items" SET "name"=?,"data"=? RETURNING "items".*`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestPreparedToUpdateSQLWithSkipupdateTag() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address" goqu:"skipupdate"` + Name string `db:"name"` + } + updateSQL, args, err := ds1.Prepared(true).ToUpdateSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"Test"}) + assert.Equal(t, `UPDATE "items" SET "name"=?`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestPreparedToUpdateSQLWithEmbeddedStruct() { + t := dit.T() + ds1 := From("items") + type Phone struct { + Primary string `db:"primary_phone"` + Home string `db:"home_phone"` + Created time.Time `db:"phone_created"` + } + type item struct { + Phone + Address string `db:"address" goqu:"skipupdate"` + Name string `db:"name"` + Created time.Time `db:"created"` + NilPointer interface{} `db:"nil_pointer"` + } + created, _ := time.Parse("2006-01-02", "2015-01-01") + + updateSQL, args, err := ds1.Prepared(true).ToUpdateSQL(item{ + Name: "Test", + Address: "111 Test Addr", + Created: created, + Phone: Phone{ + Home: "123123", + Primary: "456456", + Created: created, + }, + }) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"456456", "123123", created, "Test", created}) + assert.Equal(t, `UPDATE "items" `+ + `SET "primary_phone"=?,"home_phone"=?,"phone_created"=?,"name"=?,"created"=?,"nil_pointer"=NULL`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestPreparedToUpdateSQLWithEmbeddedStructPtr() { + t := dit.T() + ds1 := From("items") + type Phone struct { + Primary string `db:"primary_phone"` + Home string `db:"home_phone"` + Created time.Time `db:"phone_created"` + } + type item struct { + *Phone + Address string `db:"address" goqu:"skipupdate"` + Name string `db:"name"` + Created time.Time `db:"created"` + } + created, _ := time.Parse("2006-01-02", "2015-01-01") + + updateSQL, args, err := ds1.Prepared(true).ToUpdateSQL(item{ + Name: "Test", + Address: "111 Test Addr", + Created: created, + Phone: &Phone{ + Home: "123123", + Primary: "456456", + Created: created, + }, + }) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"456456", "123123", created, "Test", created}) + assert.Equal(t, + `UPDATE "items" SET "primary_phone"=?,"home_phone"=?,"phone_created"=?,"name"=?,"created"=?`, + updateSQL, + ) +} + +func (dit *datasetIntegrationTest) TestPreparedToUpdateSQLWithWhere() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + updateSQL, args, err := ds1. + Where(C("name").IsNull()). + Prepared(true). + ToUpdateSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, `UPDATE "items" SET "address"=?,"name"=? WHERE ("name" IS NULL)`, updateSQL) + + updateSQL, args, err = ds1. + Where(C("name").IsNull()). + Prepared(true). + ToUpdateSQL(Record{"name": "Test", "address": "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, `UPDATE "items" SET "address"=?,"name"=? WHERE ("name" IS NULL)`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestPreparedToUpdateSQLWithReturning() { + t := dit.T() + ds1 := From("items") + type item struct { + Address string `db:"address"` + Name string `db:"name"` + } + updateSQL, args, err := ds1. + Returning(T("items").All()). + Prepared(true). + ToUpdateSQL(item{Name: "Test", Address: "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, `UPDATE "items" SET "address"=?,"name"=? RETURNING "items".*`, updateSQL) + + updateSQL, args, err = ds1. + Where(C("name").IsNull()). + Returning(L(`"items".*`)). + Prepared(true). + ToUpdateSQL(Record{"name": "Test", "address": "111 Test Addr"}) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) + assert.Equal(t, `UPDATE "items" SET "address"=?,"name"=? WHERE ("name" IS NULL) RETURNING "items".*`, updateSQL) +} + +func (dit *datasetIntegrationTest) TestSelect() { + t := dit.T() + ds1 := From("test") + + selectSQL, _, err := ds1.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) + + selectSQL, _, err = ds1.Select().ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) + + selectSQL, _, err = ds1.Select("id").ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id" FROM "test"`) + + selectSQL, _, err = ds1.Select("id", "name").ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "name" FROM "test"`) + + selectSQL, _, err = ds1.Select(L("COUNT(*)").As("count")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT COUNT(*) AS "count" FROM "test"`) + + selectSQL, _, err = ds1.Select(C("id").As("other_id"), L("COUNT(*)").As("count")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id" AS "other_id", COUNT(*) AS "count" FROM "test"`) + + selectSQL, _, err = ds1.From().Select(ds1.From("test_1").Select("id")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT (SELECT "id" FROM "test_1")`) + + selectSQL, _, err = ds1.From().Select(ds1.From("test_1").Select("id").As("test_id")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT (SELECT "id" FROM "test_1") AS "test_id"`) + + selectSQL, _, err = ds1.From(). + Select( + DISTINCT("a").As("distinct"), + COUNT("a").As("count"), + L("CASE WHEN ? THEN ? ELSE ? END", MIN("a").Eq(10), true, false), + L("CASE WHEN ? THEN ? ELSE ? END", AVG("a").Neq(10), true, false), + L("CASE WHEN ? THEN ? ELSE ? END", FIRST("a").Gt(10), true, false), + L("CASE WHEN ? THEN ? ELSE ? END", FIRST("a").Gte(10), true, false), + L("CASE WHEN ? THEN ? ELSE ? END", LAST("a").Lt(10), true, false), + L("CASE WHEN ? THEN ? ELSE ? END", LAST("a").Lte(10), true, false), + SUM("a").As("sum"), + COALESCE(C("a"), "a").As("colaseced"), + ).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT `+ + `DISTINCT("a") AS "distinct", `+ + `COUNT("a") AS "count", `+ + `CASE WHEN (MIN("a") = 10) THEN TRUE ELSE FALSE END, `+ + `CASE WHEN (AVG("a") != 10) THEN TRUE ELSE FALSE END, `+ + `CASE WHEN (FIRST("a") > 10) THEN TRUE ELSE FALSE END, `+ + `CASE WHEN (FIRST("a") >= 10) THEN TRUE ELSE FALSE END,`+ + ` CASE WHEN (LAST("a") < 10) THEN TRUE ELSE FALSE END, `+ + `CASE WHEN (LAST("a") <= 10) THEN TRUE ELSE FALSE END, `+ + `SUM("a") AS "sum", `+ + `COALESCE("a", 'a') AS "colaseced"`) + + type MyStruct struct { + Name string + Address string `db:"address"` + EmailAddress string `db:"email_address"` + FakeCol string `db:"-"` + } + selectSQL, _, err = ds1.Select(&MyStruct{}).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "address", "email_address", "name" FROM "test"`) + + selectSQL, _, err = ds1.Select(MyStruct{}).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "address", "email_address", "name" FROM "test"`) + + type myStruct2 struct { + MyStruct + Zipcode string `db:"zipcode"` + } + + selectSQL, _, err = ds1.Select(&myStruct2{}).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "address", "email_address", "name", "zipcode" FROM "test"`) + + selectSQL, _, err = ds1.Select(myStruct2{}).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "address", "email_address", "name", "zipcode" FROM "test"`) + + var myStructs []MyStruct + selectSQL, _, err = ds1.Select(&myStructs).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "address", "email_address", "name" FROM "test"`) + + selectSQL, _, err = ds1.Select(myStructs).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "address", "email_address", "name" FROM "test"`) + // should not change original + selectSQL, _, err = ds1.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) +} + +func (dit *datasetIntegrationTest) TestSelectDistinct() { + t := dit.T() + ds1 := From("test") + + selectSQL, _, err := ds1.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) + + selectSQL, _, err = ds1.SelectDistinct("id").ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT DISTINCT "id" FROM "test"`) + + selectSQL, _, err = ds1.SelectDistinct("id", "name").ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT DISTINCT "id", "name" FROM "test"`) + + selectSQL, _, err = ds1.SelectDistinct(L("COUNT(*)").As("count")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT DISTINCT COUNT(*) AS "count" FROM "test"`) + + selectSQL, _, err = ds1.SelectDistinct(C("id").As("other_id"), L("COUNT(*)").As("count")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT DISTINCT "id" AS "other_id", COUNT(*) AS "count" FROM "test"`) + + type MyStruct struct { + Name string + Address string `db:"address"` + EmailAddress string `db:"email_address"` + FakeCol string `db:"-"` + } + selectSQL, _, err = ds1.SelectDistinct(&MyStruct{}).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT DISTINCT "address", "email_address", "name" FROM "test"`) + + selectSQL, _, err = ds1.SelectDistinct(MyStruct{}).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT DISTINCT "address", "email_address", "name" FROM "test"`) + + type myStruct2 struct { + MyStruct + Zipcode string `db:"zipcode"` + } + + selectSQL, _, err = ds1.SelectDistinct(&myStruct2{}).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT DISTINCT "address", "email_address", "name", "zipcode" FROM "test"`) + + selectSQL, _, err = ds1.SelectDistinct(myStruct2{}).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT DISTINCT "address", "email_address", "name", "zipcode" FROM "test"`) + + var myStructs []MyStruct + selectSQL, _, err = ds1.SelectDistinct(&myStructs).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT DISTINCT "address", "email_address", "name" FROM "test"`) + + selectSQL, _, err = ds1.SelectDistinct(myStructs).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT DISTINCT "address", "email_address", "name" FROM "test"`) + // should not change original + selectSQL, _, err = ds1.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) + // should not change original + selectSQL, _, err = ds1.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) +} + +func (dit *datasetIntegrationTest) TestClearSelect() { + t := dit.T() + ds1 := From("test") + + selectSQL, _, err := ds1.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) + + b := ds1.Select("a").ClearSelect() + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) +} + +func (dit *datasetIntegrationTest) TestSelectAppend() { + t := dit.T() + ds1 := From("test") + + selectSQL, _, err := ds1.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) + + b := ds1.Select("a").SelectAppend("b").SelectAppend("c") + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "a", "b", "c" FROM "test"`) +} + +func (dit *datasetIntegrationTest) TestFrom() { + t := dit.T() + ds1 := From("test") + + selectSQL, _, err := ds1.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) + + ds2 := ds1.From("test2") + selectSQL, _, err = ds2.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test2"`) + + ds2 = ds1.From("test2", "test3") + selectSQL, _, err = ds2.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test2", "test3"`) + + ds2 = ds1.From(T("test2").As("test_2"), "test3") + selectSQL, _, err = ds2.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test2" AS "test_2", "test3"`) + + ds2 = ds1.From(ds1.From("test2"), "test3") + selectSQL, _, err = ds2.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM (SELECT * FROM "test2") AS "t1", "test3"`) + + ds2 = ds1.From(ds1.From("test2").As("test_2"), "test3") + selectSQL, _, err = ds2.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM (SELECT * FROM "test2") AS "test_2", "test3"`) + // should not change original + selectSQL, _, err = ds1.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) +} + +func (dit *datasetIntegrationTest) TestEmptyWhere() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where() + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) +} + +func (dit *datasetIntegrationTest) TestWhere() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where( + C("a").Eq(true), + C("a").Neq(true), + C("a").Eq(false), + C("a").Neq(false), + ) + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" `+ + `WHERE (("a" IS TRUE) AND ("a" IS NOT TRUE) AND ("a" IS FALSE) AND ("a" IS NOT FALSE))`) + + b = ds1.Where( + C("a").Eq("a"), + C("b").Neq("b"), + C("c").Gt("c"), + C("d").Gte("d"), + C("e").Lt("e"), + C("f").Lte("f"), + ) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" `+ + `WHERE (("a" = 'a') AND ("b" != 'b') AND ("c" > 'c') AND ("d" >= 'd') AND ("e" < 'e') AND ("f" <= 'f'))`) + + b = ds1.Where( + C("a").Eq(From("test2").Select("id")), + ) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" IN (SELECT "id" FROM "test2"))`) + + b = ds1.Where(Ex{ + "a": "a", + "b": Op{"neq": "b"}, + "c": Op{"gt": "c"}, + "d": Op{"gte": "d"}, + "e": Op{"lt": "e"}, + "f": Op{"lte": "f"}, + }) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" `+ + `WHERE (("a" = 'a') AND ("b" != 'b') AND ("c" > 'c') AND ("d" >= 'd') AND ("e" < 'e') AND ("f" <= 'f'))`) + + b = ds1.Where(Ex{ + "a": From("test2").Select("id"), + }) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" IN (SELECT "id" FROM "test2"))`) +} + +func (dit *datasetIntegrationTest) TestWhereChain() { + t := dit.T() + ds1 := From("test").Where( + C("x").Eq(0), + C("y").Eq(1), + ) + + ds2 := ds1.Where( + C("z").Eq(2), + ) + + a := ds2.Where( + C("a").Eq("A"), + ) + b := ds2.Where( + C("b").Eq("B"), + ) + selectSQL, _, err := a.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" `+ + `WHERE (("x" = 0) AND ("y" = 1) AND ("z" = 2) AND ("a" = 'A'))`) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" `+ + `WHERE (("x" = 0) AND ("y" = 1) AND ("z" = 2) AND ("b" = 'B'))`) +} + +func (dit *datasetIntegrationTest) TestClearWhere() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where( + C("a").Eq(1), + ).ClearWhere() + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) +} + +func (dit *datasetIntegrationTest) TestLimit() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where( + C("a").Gt(1), + ).Limit(10) + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) LIMIT 10`) + + b = ds1.Where( + C("a").Gt(1), + ).Limit(0) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1)`) +} + +func (dit *datasetIntegrationTest) TestLimitAll() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where( + C("a").Gt(1), + ).LimitAll() + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) LIMIT ALL`) + + b = ds1.Where( + C("a").Gt(1), + ).Limit(0).LimitAll() + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) LIMIT ALL`) +} + +func (dit *datasetIntegrationTest) TestClearLimit() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where( + C("a").Gt(1), + ).LimitAll().ClearLimit() + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1)`) + + b = ds1.Where( + C("a").Gt(1), + ).Limit(10).ClearLimit() + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1)`) +} + +func (dit *datasetIntegrationTest) TestOffset() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where( + C("a").Gt(1), + ).Offset(10) + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) OFFSET 10`) + + b = ds1.Where( + C("a").Gt(1), + ).Offset(0) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1)`) +} + +func (dit *datasetIntegrationTest) TestClearOffset() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where( + C("a").Gt(1), + ).Offset(10).ClearOffset() + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1)`) +} + +func (dit *datasetIntegrationTest) TestForUpdate() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where( + C("a").Gt(1), + ).ForUpdate(Wait) + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) FOR UPDATE `) + + b = ds1.Where( + C("a").Gt(1), + ).ForUpdate(NoWait) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) FOR UPDATE NOWAIT`) + + b = ds1.Where( + C("a").Gt(1), + ).ForUpdate(SkipLocked) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) FOR UPDATE SKIP LOCKED`) +} + +func (dit *datasetIntegrationTest) TestForNoKeyUpdate() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where( + C("a").Gt(1), + ).ForNoKeyUpdate(Wait) + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) FOR NO KEY UPDATE `) + + b = ds1.Where( + C("a").Gt(1), + ).ForNoKeyUpdate(NoWait) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) FOR NO KEY UPDATE NOWAIT`) + + b = ds1.Where( + C("a").Gt(1), + ).ForNoKeyUpdate(SkipLocked) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) FOR NO KEY UPDATE SKIP LOCKED`) +} + +func (dit *datasetIntegrationTest) TestForKeyShare() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where( + C("a").Gt(1), + ).ForKeyShare(Wait) + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) FOR KEY SHARE `) + + b = ds1.Where( + C("a").Gt(1), + ).ForKeyShare(NoWait) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) FOR KEY SHARE NOWAIT`) + + b = ds1.Where( + C("a").Gt(1), + ).ForKeyShare(SkipLocked) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) FOR KEY SHARE SKIP LOCKED`) +} + +func (dit *datasetIntegrationTest) TestForShare() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where( + C("a").Gt(1), + ).ForShare(Wait) + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) FOR SHARE `) + + b = ds1.Where( + C("a").Gt(1), + ).ForShare(NoWait) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) FOR SHARE NOWAIT`) + + b = ds1.Where( + C("a").Gt(1), + ).ForShare(SkipLocked) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) FOR SHARE SKIP LOCKED`) +} + +func (dit *datasetIntegrationTest) TestGroupBy() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where( + C("a").Gt(1), + ).GroupBy("created") + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) GROUP BY "created"`) + + b = ds1.Where( + C("a").Gt(1), + ).GroupBy(L("created::DATE")) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) GROUP BY created::DATE`) + + b = ds1.Where( + C("a").Gt(1), + ).GroupBy("name", L("created::DATE")) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > 1) GROUP BY "name", created::DATE`) +} + +func (dit *datasetIntegrationTest) TestHaving() { + t := dit.T() + ds1 := From("test") + + b := ds1.Having(Ex{ + "a": Op{"gt": 1}, + }).GroupBy("created") + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" GROUP BY "created" HAVING ("a" > 1)`) + + b = ds1.Where(Ex{"b": true}). + Having(Ex{"a": Op{"gt": 1}}). + GroupBy("created") + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("b" IS TRUE) GROUP BY "created" HAVING ("a" > 1)`) + + b = ds1.Having(Ex{"a": Op{"gt": 1}}) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" HAVING ("a" > 1)`) + + b = ds1.Having(Ex{"a": Op{"gt": 1}}).Having(Ex{"b": 2}) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" HAVING (("a" > 1) AND ("b" = 2))`) +} + +func (dit *datasetIntegrationTest) TestOrder() { + t := dit.T() + + ds1 := From("test") + + b := ds1.Order(C("a").Asc(), L(`("a" + "b" > 2)`).Asc()) + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" ORDER BY "a" ASC, ("a" + "b" > 2) ASC`) +} + +func (dit *datasetIntegrationTest) TestOrderAppend() { + t := dit.T() + b := From("test").Order(C("a").Asc().NullsFirst()).OrderAppend(C("b").Desc().NullsLast()) + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" ORDER BY "a" ASC NULLS FIRST, "b" DESC NULLS LAST`) + + b = From("test").OrderAppend(C("a").Asc().NullsFirst()).OrderAppend(C("b").Desc().NullsLast()) + selectSQL, _, err = b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test" ORDER BY "a" ASC NULLS FIRST, "b" DESC NULLS LAST`) + +} + +func (dit *datasetIntegrationTest) TestClearOrder() { + t := dit.T() + b := From("test").Order(C("a").Asc().NullsFirst()).ClearOrder() + selectSQL, _, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "test"`) +} + +func (dit *datasetIntegrationTest) TestJoin() { + t := dit.T() + ds1 := From("items") + + selectSQL, _, err := ds1.Join(T("players").As("p"), On(Ex{"p.id": I("items.playerId")})).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `INNER JOIN "players" AS "p" ON ("p"."id" = "items"."playerId")`) + + selectSQL, _, err = ds1.Join(ds1.From("players").As("p"), On(Ex{"p.id": I("items.playerId")})).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `INNER JOIN (SELECT * FROM "players") AS "p" ON ("p"."id" = "items"."playerId")`) + + selectSQL, _, err = ds1.Join(S("v1").Table("test"), On(Ex{"v1.test.id": I("items.playerId")})).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `INNER JOIN "v1"."test" ON ("v1"."test"."id" = "items"."playerId")`) + + selectSQL, _, err = ds1.Join(T("test"), Using(C("name"), C("common_id"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" INNER JOIN "test" USING ("name", "common_id")`) + + selectSQL, _, err = ds1.Join(T("test"), Using("name", "common_id")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" INNER JOIN "test" USING ("name", "common_id")`) + +} + +func (dit *datasetIntegrationTest) TestLeftOuterJoin() { + t := dit.T() + ds1 := From("items") + + selectSQL, _, err := ds1.LeftOuterJoin(T("categories"), On(Ex{ + "categories.categoryId": I("items.id"), + })).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `LEFT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) + + selectSQL, _, err = ds1. + LeftOuterJoin( + T("categories"), + On( + I("categories.categoryId").Eq(I("items.id")), + I("categories.categoryId").In(1, 2, 3)), + ).ToSQL() + assert.NoError(t, err) + assert.Equal(t, `SELECT * FROM "items" `+ + `LEFT OUTER JOIN "categories" `+ + `ON (("categories"."categoryId" = "items"."id") AND ("categories"."categoryId" IN (1, 2, 3)))`, selectSQL) + +} + +func (dit *datasetIntegrationTest) TestFullOuterJoin() { + t := dit.T() + ds1 := From("items") + selectSQL, _, err := ds1. + FullOuterJoin(T("categories"), On(Ex{"categories.categoryId": I("items.id")})). + Order(C("stamp").Asc()).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `FULL OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id") ORDER BY "stamp" ASC`) + + selectSQL, _, err = ds1.FullOuterJoin( + T("categories"), + On(Ex{"categories.categoryId": I("items.id")}), + ).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `FULL OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) +} + +func (dit *datasetIntegrationTest) TestInnerJoin() { + t := dit.T() + ds1 := From("items") + selectSQL, _, err := ds1. + InnerJoin(T("b"), On(Ex{"b.itemsId": I("items.id")})). + LeftOuterJoin(T("c"), On(Ex{"c.b_id": I("b.id")})). + ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `INNER JOIN "b" ON ("b"."itemsId" = "items"."id") `+ + `LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")`) + + selectSQL, _, err = ds1. + InnerJoin(T("b"), On(Ex{"b.itemsId": I("items.id")})). + LeftOuterJoin(T("c"), On(Ex{"c.b_id": I("b.id")})). + ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `INNER JOIN "b" ON ("b"."itemsId" = "items"."id") `+ + `LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")`) + + selectSQL, _, err = ds1.InnerJoin( + T("categories"), + On(Ex{"categories.categoryId": I("items.id")}), + ).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `INNER JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) +} + +func (dit *datasetIntegrationTest) TestRightOuterJoin() { + t := dit.T() + ds1 := From("items") + selectSQL, _, err := ds1.RightOuterJoin( + T("categories"), + On(Ex{"categories.categoryId": I("items.id")}), + ).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `RIGHT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) +} + +func (dit *datasetIntegrationTest) TestLeftJoin() { + t := dit.T() + ds1 := From("items") + selectSQL, _, err := ds1.LeftJoin( + T("categories"), + On(Ex{"categories.categoryId": I("items.id")}), + ).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `LEFT JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) +} + +func (dit *datasetIntegrationTest) TestRightJoin() { + t := dit.T() + ds1 := From("items") + selectSQL, _, err := ds1.RightJoin( + T("categories"), + On(Ex{"categories.categoryId": I("items.id")}), + ).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `RIGHT JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) +} + +func (dit *datasetIntegrationTest) TestFullJoin() { + t := dit.T() + ds1 := From("items") + selectSQL, _, err := ds1.FullJoin( + T("categories"), + On(Ex{"categories.categoryId": I("items.id")}), + ).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `FULL JOIN "categories" ON ("categories"."categoryId" = "items"."id")`) +} + +func (dit *datasetIntegrationTest) TestNaturalJoin() { + t := dit.T() + ds1 := From("items") + selectSQL, _, err := ds1.NaturalJoin(T("categories")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" NATURAL JOIN "categories"`) +} + +func (dit *datasetIntegrationTest) TestNaturalLeftJoin() { + t := dit.T() + ds1 := From("items") + selectSQL, _, err := ds1.NaturalLeftJoin(T("categories")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" NATURAL LEFT JOIN "categories"`) + +} + +func (dit *datasetIntegrationTest) TestNaturalRightJoin() { + t := dit.T() + ds1 := From("items") + selectSQL, _, err := ds1.NaturalRightJoin(T("categories")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" NATURAL RIGHT JOIN "categories"`) +} + +func (dit *datasetIntegrationTest) TestNaturalFullJoin() { + t := dit.T() + ds1 := From("items") + selectSQL, _, err := ds1.NaturalFullJoin(T("categories")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" NATURAL FULL JOIN "categories"`) +} + +func (dit *datasetIntegrationTest) TestCrossJoin() { + t := dit.T() + selectSQL, _, err := From("items").CrossJoin(T("categories")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" CROSS JOIN "categories"`) +} + +func (dit *datasetIntegrationTest) TestSQLFunctionExpressionsInHaving() { + t := dit.T() + ds1 := From("items") + selectSQL, _, err := ds1.GroupBy("name").Having(SUM("amount").Gt(0)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM "items" GROUP BY "name" HAVING (SUM("amount") > 0)`) +} + +func (dit *datasetIntegrationTest) TestUnion() { + t := dit.T() + a := From("invoice").Select("id", "amount").Where(C("amount").Gt(1000)) + b := From("invoice").Select("id", "amount").Where(C("amount").Lt(10)) + + selectSQL, _, err := a.Union(b).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) + + selectSQL, _, err = a.Limit(1).Union(b).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" `+ + `UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) + + selectSQL, _, err = a.Order(C("id").Asc()).Union(b).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM `+ + `(SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) ORDER BY "id" ASC) AS "t1" `+ + `UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) + + selectSQL, _, err = a.Union(b.Limit(1)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `UNION (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) LIMIT 1) AS "t1")`) + + selectSQL, _, err = a.Union(b.Order(C("id").Desc())).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `UNION (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) + + selectSQL, _, err = a.Limit(1).Union(b.Order(C("id").Desc())).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" `+ + `UNION (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) + + selectSQL, _, err = a.Union(b).Union(b.Where(C("id").Lt(50))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10)) `+ + `UNION (SELECT "id", "amount" FROM "invoice" WHERE (("amount" < 10) AND ("id" < 50)))`) + +} + +func (dit *datasetIntegrationTest) TestUnionAll() { + t := dit.T() + a := From("invoice").Select("id", "amount").Where(C("amount").Gt(1000)) + b := From("invoice").Select("id", "amount").Where(C("amount").Lt(10)) + + selectSQL, _, err := a.UnionAll(b).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) + + selectSQL, _, err = a.Limit(1).UnionAll(b).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM `+ + `(SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" `+ + `UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) + + selectSQL, _, err = a.Order(C("id").Asc()).UnionAll(b).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM `+ + `(SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) ORDER BY "id" ASC) AS "t1" `+ + `UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) + + selectSQL, _, err = a.UnionAll(b.Limit(1)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `UNION ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) LIMIT 1) AS "t1")`) + + selectSQL, _, err = a.UnionAll(b.Order(C("id").Desc())).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `UNION ALL `+ + `(SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) + + selectSQL, _, err = a.Limit(1).UnionAll(b.Order(C("id").Desc())).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" `+ + `UNION ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1"`+ + `)`) +} + +func (dit *datasetIntegrationTest) TestIntersect() { + t := dit.T() + a := From("invoice").Select("id", "amount").Where(C("amount").Gt(1000)) + b := From("invoice").Select("id", "amount").Where(C("amount").Lt(10)) + + selectSQL, _, err := a.Intersect(b).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `INTERSECT (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) + + selectSQL, _, err = a.Limit(1).Intersect(b).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" `+ + `INTERSECT (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10)`+ + `)`) + + selectSQL, _, err = a.Order(C("id").Asc()).Intersect(b).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) ORDER BY "id" ASC) AS "t1" `+ + `INTERSECT (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10)`+ + `)`) + + selectSQL, _, err = a.Intersect(b.Limit(1)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `INTERSECT (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) LIMIT 1) AS "t1")`) + + selectSQL, _, err = a.Intersect(b.Order(C("id").Desc())).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `INTERSECT (`+ + `SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1"`+ + `)`) + + selectSQL, _, err = a.Limit(1).Intersect(b.Order(C("id").Desc())).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" `+ + `INTERSECT (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1"`+ + `)`) +} + +func (dit *datasetIntegrationTest) TestIntersectAll() { + t := dit.T() + a := From("invoice").Select("id", "amount").Where(C("amount").Gt(1000)) + b := From("invoice").Select("id", "amount").Where(C("amount").Lt(10)) + + selectSQL, _, err := a.IntersectAll(b).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `INTERSECT ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10))`) + + selectSQL, _, err = a.Limit(1).IntersectAll(b).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" `+ + `INTERSECT ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10)`+ + `)`) + + selectSQL, _, err = a.Order(C("id").Asc()).IntersectAll(b).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) ORDER BY "id" ASC) AS "t1" `+ + `INTERSECT ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10)`+ + `)`) + + selectSQL, _, err = a.IntersectAll(b.Limit(1)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `INTERSECT ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) LIMIT 1) AS "t1")`) + + selectSQL, _, err = a.IntersectAll(b.Order(C("id").Desc())).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) `+ + `INTERSECT ALL `+ + `(SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) + + selectSQL, _, err = a.Limit(1).IntersectAll(b.Order(C("id").Desc())).ToSQL() + assert.NoError(t, err) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > 1000) LIMIT 1) AS "t1" `+ + `INTERSECT ALL `+ + `(SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < 10) ORDER BY "id" DESC) AS "t1")`) +} + +// TO PREPARED + +func (dit *datasetIntegrationTest) TestPreparedWhere() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where(Ex{ + "a": true, + "b": Op{"neq": true}, + "c": false, + "d": Op{"neq": false}, + "e": nil, + }) + selectSQL, args, err := b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE (`+ + `("a" IS TRUE) `+ + `AND ("b" IS NOT TRUE) `+ + `AND ("c" IS FALSE) `+ + `AND ("d" IS NOT FALSE) `+ + `AND ("e" IS NULL)`+ + `)`) + + b = ds1.Where(Ex{ + "a": "a", + "b": Op{"neq": "b"}, + "c": Op{"gt": "c"}, + "d": Op{"gte": "d"}, + "e": Op{"lt": "e"}, + "f": Op{"lte": "f"}, + "g": Op{"is": nil}, + "h": Op{"isnot": nil}, + }) + selectSQL, args, err = b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{"a", "b", "c", "d", "e", "f"}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE (`+ + `("a" = ?) `+ + `AND ("b" != ?) `+ + `AND ("c" > ?) `+ + `AND ("d" >= ?) `+ + `AND ("e" < ?) `+ + `AND ("f" <= ?) `+ + `AND ("g" IS NULL) `+ + `AND ("h" IS NOT NULL)`+ + `)`) +} + +func (dit *datasetIntegrationTest) TestPreparedLimit() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where(C("a").Gt(1)).Limit(10) + selectSQL, args, err := b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1), int64(10)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > ?) LIMIT ?`) + + b = ds1.Where(C("a").Gt(1)).Limit(0) + selectSQL, args, err = b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedLimitAll() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where(C("a").Gt(1)).LimitAll() + selectSQL, args, err := b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > ?) LIMIT ALL`) + + b = ds1.Where(C("a").Gt(1)).Limit(0).LimitAll() + selectSQL, args, err = b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > ?) LIMIT ALL`) +} + +func (dit *datasetIntegrationTest) TestPreparedClearLimit() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where(C("a").Gt(1)).LimitAll().ClearLimit() + selectSQL, args, err := b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > ?)`) + + b = ds1.Where(C("a").Gt(1)).Limit(10).ClearLimit() + selectSQL, args, err = b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedOffset() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where(C("a").Gt(1)).Offset(10) + selectSQL, args, err := b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1), int64(10)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > ?) OFFSET ?`) + + b = ds1.Where(C("a").Gt(1)).Offset(0) + selectSQL, args, err = b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedClearOffset() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where(C("a").Gt(1)).Offset(10).ClearOffset() + selectSQL, args, err := b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedGroupBy() { + t := dit.T() + ds1 := From("test") + + b := ds1.Where(C("a").Gt(1)).GroupBy("created") + selectSQL, args, err := b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > ?) GROUP BY "created"`) + + b = ds1.Where(C("a").Gt(1)).GroupBy(L("created::DATE")) + selectSQL, args, err = b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > ?) GROUP BY created::DATE`) + + b = ds1.Where(C("a").Gt(1)).GroupBy("name", L("created::DATE")) + selectSQL, args, err = b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("a" > ?) GROUP BY "name", created::DATE`) +} + +func (dit *datasetIntegrationTest) TestPreparedHaving() { + t := dit.T() + ds1 := From("test") + + b := ds1.Having(C("a").Gt(1)).GroupBy("created") + selectSQL, args, err := b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" GROUP BY "created" HAVING ("a" > ?)`) + + b = ds1. + Where(C("b").IsTrue()). + Having(C("a").Gt(1)). + GroupBy("created") + selectSQL, args, err = b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" WHERE ("b" IS TRUE) GROUP BY "created" HAVING ("a" > ?)`) + + b = ds1.Having(C("a").Gt(1)) + selectSQL, args, err = b.Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1)}) + assert.Equal(t, selectSQL, `SELECT * FROM "test" HAVING ("a" > ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedJoin() { + t := dit.T() + ds1 := From("items") + + selectSQL, args, err := ds1.Join( + T("players").As("p"), + On(I("p.id").Eq(I("items.playerId"))), + ).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{}) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `INNER JOIN "players" AS "p" ON ("p"."id" = "items"."playerId")`) + + selectSQL, args, err = ds1.Join( + ds1.From("players").As("p"), + On(I("p.id").Eq(I("items.playerId"))), + ).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{}) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `INNER JOIN (SELECT * FROM "players") AS "p" ON ("p"."id" = "items"."playerId")`) + + selectSQL, args, err = ds1.Join( + S("v1").Table("test"), + On(I("v1.test.id").Eq(I("items.playerId"))), + ).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{}) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `INNER JOIN "v1"."test" ON ("v1"."test"."id" = "items"."playerId")`) + + selectSQL, args, err = ds1.Join( + T("test"), + Using(I("name"), I("common_id")), + ).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{}) + assert.Equal(t, selectSQL, `SELECT * FROM "items" INNER JOIN "test" USING ("name", "common_id")`) + + selectSQL, args, err = ds1.Join( + T("test"), + Using("name", "common_id"), + ).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{}) + assert.Equal(t, selectSQL, `SELECT * FROM "items" INNER JOIN "test" USING ("name", "common_id")`) + + selectSQL, args, err = ds1.Join( + T("categories"), + On( + I("categories.categoryId").Eq(I("items.id")), + I("categories.categoryId").In(1, 2, 3), + ), + ).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1), int64(2), int64(3)}) + assert.Equal(t, selectSQL, `SELECT * FROM "items" `+ + `INNER JOIN "categories" ON (`+ + `("categories"."categoryId" = "items"."id") AND ("categories"."categoryId" IN (?, ?, ?))`+ + `)`) + +} + +func (dit *datasetIntegrationTest) TestPreparedFunctionExpressionsInHaving() { + t := dit.T() + ds1 := From("items") + selectSQL, args, err := ds1. + GroupBy("name"). + Having(SUM("amount").Gt(0)). + Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(0)}) + assert.Equal(t, selectSQL, `SELECT * FROM "items" GROUP BY "name" HAVING (SUM("amount") > ?)`) +} + +func (dit *datasetIntegrationTest) TestPreparedUnion() { + t := dit.T() + a := From("invoice").Select("id", "amount").Where(C("amount").Gt(1000)) + b := From("invoice").Select("id", "amount").Where(C("amount").Lt(10)) + + selectSQL, args, err := a.Union(b).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(10)}) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?)`+ + ` UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?))`) + + selectSQL, args, err = a.Limit(1).Union(b).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(1), int64(10)}) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) LIMIT ?) AS "t1" `+ + `UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?)`+ + `)`) + + selectSQL, args, err = a.Union(b.Limit(1)).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(10), int64(1)}) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) `+ + `UNION (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?) LIMIT ?) AS "t1")`) + + selectSQL, args, err = a.Union(b).Union(b.Where(C("id").Lt(50))).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(10), int64(10), int64(50)}) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) `+ + `UNION (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?)) `+ + `UNION (SELECT "id", "amount" FROM "invoice" WHERE (("amount" < ?) AND ("id" < ?)))`) + +} + +func (dit *datasetIntegrationTest) TestPreparedUnionAll() { + t := dit.T() + a := From("invoice").Select("id", "amount").Where(C("amount").Gt(1000)) + b := From("invoice").Select("id", "amount").Where(C("amount").Lt(10)) + + selectSQL, args, err := a.UnionAll(b).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(10)}) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) `+ + `UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?))`) + + selectSQL, args, err = a.Limit(1).UnionAll(b).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(1), int64(10)}) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) LIMIT ?) AS "t1" `+ + `UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?)`+ + `)`) + + selectSQL, args, err = a.UnionAll(b.Limit(1)).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(10), int64(1)}) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) `+ + `UNION ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?) LIMIT ?) AS "t1")`) + + selectSQL, args, err = a.UnionAll(b).UnionAll(b.Where(C("id").Lt(50))).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(10), int64(10), int64(50)}) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) `+ + `UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?)) `+ + `UNION ALL (SELECT "id", "amount" FROM "invoice" WHERE (("amount" < ?) AND ("id" < ?)))`) +} + +func (dit *datasetIntegrationTest) TestPreparedIntersect() { + t := dit.T() + a := From("invoice").Select("id", "amount").Where(C("amount").Gt(1000)) + b := From("invoice").Select("id", "amount").Where(C("amount").Lt(10)) + + selectSQL, args, err := a.Intersect(b).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(10)}) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) `+ + `INTERSECT (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?))`) + + selectSQL, args, err = a.Limit(1).Intersect(b).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(1), int64(10)}) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) LIMIT ?) AS "t1" `+ + `INTERSECT (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?)`+ + `)`) + + selectSQL, args, err = a.Intersect(b.Limit(1)).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(10), int64(1)}) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) `+ + `INTERSECT (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?) LIMIT ?) AS "t1")`) + +} + +func (dit *datasetIntegrationTest) TestPreparedIntersectAll() { + t := dit.T() + a := From("invoice").Select("id", "amount").Where(C("amount").Gt(1000)) + b := From("invoice").Select("id", "amount").Where(C("amount").Lt(10)) + + selectSQL, args, err := a.IntersectAll(b).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(10)}) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) `+ + `INTERSECT ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?))`) + + selectSQL, args, err = a.Limit(1).IntersectAll(b).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(1), int64(10)}) + assert.Equal(t, selectSQL, `SELECT * FROM (`+ + `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?) LIMIT ?) AS "t1" `+ + `INTERSECT ALL (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?)`+ + `)`) + + selectSQL, args, err = a.IntersectAll(b.Limit(1)).Prepared(true).ToSQL() + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{int64(1000), int64(10), int64(1)}) + assert.Equal(t, selectSQL, `SELECT "id", "amount" FROM "invoice" WHERE ("amount" > ?)`+ + ` INTERSECT ALL (SELECT * FROM (SELECT "id", "amount" FROM "invoice" WHERE ("amount" < ?) LIMIT ?) AS "t1")`) + +} + +func TestDatasetIntegrationSuite(t *testing.T) { + suite.Run(t, new(datasetIntegrationTest)) +} diff --git a/dataset_test.go b/dataset_test.go index e845918b..c969751f 100644 --- a/dataset_test.go +++ b/dataset_test.go @@ -1,13 +1,14 @@ package goqu import ( - "database/sql/driver" - "fmt" - "regexp" "testing" - "time" + "github.com/doug-martin/goqu/v7/exp" + "github.com/doug-martin/goqu/v7/internal/errors" + "github.com/doug-martin/goqu/v7/internal/sb" + "github.com/doug-martin/goqu/v7/mocks" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" ) @@ -15,866 +16,812 @@ type datasetTest struct { suite.Suite } -func (me *datasetTest) Truncate(buf *SqlBuilder) *SqlBuilder { - buf.Truncate(0) - buf.args = make([]interface{}, 0) - return buf -} - -func (me *datasetTest) TestClone() { - t := me.T() +func (dt *datasetTest) TestClone() { + t := dt.T() ds := From("test") assert.Equal(t, ds.Clone(), ds) } -func (me *datasetTest) TestExpression() { - t := me.T() +func (dt *datasetTest) TestExpression() { + t := dt.T() ds := From("test") assert.Equal(t, ds.Expression(), ds) } -func (me *datasetTest) TestAdapter() { - t := me.T() +func (dt *datasetTest) TestDialect() { + t := dt.T() ds := From("test") - assert.Equal(t, ds.Adapter(), ds.adapter) + assert.NotNil(t, ds.Dialect()) } -func (me *datasetTest) TestSetAdapter() { - t := me.T() +func (dt *datasetTest) TestWithDialect() { + t := dt.T() ds := From("test") - adapter := NewAdapter("default", ds) - ds.SetAdapter(adapter) - assert.Equal(t, ds.Adapter(), adapter) + dialect := GetDialect("default") + ds.WithDialect("default") + assert.Equal(t, ds.Dialect(), dialect) } -func (me *datasetTest) TestPrepared() { - t := me.T() +func (dt *datasetTest) TestPrepared() { + t := dt.T() ds := From("test") preparedDs := ds.Prepared(true) - assert.True(t, preparedDs.isPrepared) - assert.False(t, ds.isPrepared) - - //should apply the prepared to any datasets created from the root - assert.True(t, preparedDs.Where(Ex{"a": 1}).isPrepared) -} - -func (me *datasetTest) TestLiteralUnsupportedType() { - t := me.T() - assert.EqualError(t, From("test").Literal(NewSqlBuilder(false), struct{}{}), "goqu: Unable to encode value {}") -} - -type unknowExpression struct { -} - -func (me unknowExpression) Expression() Expression { - return me -} -func (me unknowExpression) Clone() Expression { - return me -} -func (me *datasetTest) TestLiteralUnsupportedExpression() { - t := me.T() - assert.EqualError(t, From("test").Literal(NewSqlBuilder(false), unknowExpression{}), "goqu: Unsupported expression type goqu.unknowExpression") -} - -func (me *datasetTest) TestLiteralFloatTypes() { - t := me.T() - ds := From("test") - var float float64 - buf := NewSqlBuilder(false) - assert.NoError(t, ds.Literal(buf, float32(10.01))) - assert.Equal(t, buf.String(), "10.010000228881836") - assert.NoError(t, ds.Literal(me.Truncate(buf), float64(10.01))) - assert.Equal(t, buf.String(), "10.01") - assert.NoError(t, ds.Literal(me.Truncate(buf), &float)) - assert.Equal(t, buf.String(), "0") - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(buf, float32(10.01))) - assert.Equal(t, buf.args, []interface{}{float64(float32(10.01))}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), float64(10.01))) - assert.Equal(t, buf.args, []interface{}{float64(10.01)}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), &float)) - assert.Equal(t, buf.args, []interface{}{float}) - assert.Equal(t, buf.String(), "?") -} - -func (me *datasetTest) TestLiteralIntTypes() { - t := me.T() - ds := From("test") - var i int64 - buf := NewSqlBuilder(false) - assert.NoError(t, ds.Literal(buf, int(10))) - assert.Equal(t, buf.String(), "10") - assert.NoError(t, ds.Literal(me.Truncate(buf), int8(10))) - assert.Equal(t, buf.String(), "10") - assert.NoError(t, ds.Literal(me.Truncate(buf), int16(10))) - assert.Equal(t, buf.String(), "10") - assert.NoError(t, ds.Literal(me.Truncate(buf), int32(10))) - assert.Equal(t, buf.String(), "10") - assert.NoError(t, ds.Literal(me.Truncate(buf), int64(10))) - assert.Equal(t, buf.String(), "10") - assert.NoError(t, ds.Literal(me.Truncate(buf), uint(10))) - assert.Equal(t, buf.String(), "10") - assert.NoError(t, ds.Literal(me.Truncate(buf), uint8(10))) - assert.Equal(t, buf.String(), "10") - assert.NoError(t, ds.Literal(me.Truncate(buf), uint16(10))) - assert.Equal(t, buf.String(), "10") - assert.NoError(t, ds.Literal(me.Truncate(buf), uint32(10))) - assert.Equal(t, buf.String(), "10") - assert.NoError(t, ds.Literal(me.Truncate(buf), uint64(10))) - assert.Equal(t, buf.String(), "10") - assert.NoError(t, ds.Literal(me.Truncate(buf), &i)) - assert.Equal(t, buf.String(), "0") - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(buf, int(10))) - assert.Equal(t, buf.args, []interface{}{int64(10)}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), int8(10))) - assert.Equal(t, buf.args, []interface{}{int64(10)}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), int16(10))) - assert.Equal(t, buf.args, []interface{}{int64(10)}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), int32(10))) - assert.Equal(t, buf.args, []interface{}{int64(10)}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), int64(10))) - assert.Equal(t, buf.args, []interface{}{int64(10)}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), uint(10))) - assert.Equal(t, buf.args, []interface{}{int64(10)}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), uint8(10))) - assert.Equal(t, buf.args, []interface{}{int64(10)}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), uint16(10))) - assert.Equal(t, buf.args, []interface{}{int64(10)}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), uint32(10))) - assert.Equal(t, buf.args, []interface{}{int64(10)}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), uint64(10))) - assert.Equal(t, buf.args, []interface{}{int64(10)}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), &i)) - assert.Equal(t, buf.args, []interface{}{i}) - assert.Equal(t, buf.String(), "?") -} - -func (me *datasetTest) TestLiteralStringTypes() { - t := me.T() - ds := From("test") - var str string - buf := NewSqlBuilder(false) - assert.NoError(t, ds.Literal(me.Truncate(buf), "Hello")) - assert.Equal(t, buf.String(), "'Hello'") - //should esacpe single quotes - assert.NoError(t, ds.Literal(me.Truncate(buf), "hello'")) - assert.Equal(t, buf.String(), "'hello'''") - assert.NoError(t, ds.Literal(me.Truncate(buf), &str)) - assert.Equal(t, buf.String(), "''") - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), "Hello")) - assert.Equal(t, buf.args, []interface{}{"Hello"}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), "hello'")) - assert.Equal(t, buf.args, []interface{}{"hello'"}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), &str)) - assert.Equal(t, buf.args, []interface{}{str}) - assert.Equal(t, buf.String(), "?") -} - -func (me *datasetTest) TestLiteralBytesTypes() { - t := me.T() - ds := From("test") - var b string - buf := NewSqlBuilder(false) - assert.NoError(t, ds.Literal(me.Truncate(buf), []byte("Hello"))) - assert.Equal(t, buf.Bytes(), []byte("'Hello'")) - //should escape single quotes - assert.NoError(t, ds.Literal(me.Truncate(buf), []byte("hello'"))) - assert.Equal(t, buf.Bytes(), []byte("'hello'''")) - assert.NoError(t, ds.Literal(me.Truncate(buf), (&b))) - assert.Equal(t, buf.Bytes(), []byte("''")) - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), []byte("Hello"))) - assert.Equal(t, buf.args, []interface{}{[]byte("Hello")}) - assert.Equal(t, buf.Bytes(), []byte("?")) - assert.NoError(t, ds.Literal(me.Truncate(buf), []byte("hello'"))) - assert.Equal(t, buf.args, []interface{}{[]byte("hello'")}) - assert.Equal(t, buf.Bytes(), []byte("?")) - assert.NoError(t, ds.Literal(me.Truncate(buf), []byte(*(&b)))) - assert.Equal(t, buf.args, []interface{}{[]byte(b)}) - assert.Equal(t, buf.Bytes(), []byte("?")) -} - -func (me *datasetTest) TestLiteralBoolTypes() { - t := me.T() - var b bool - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), true)) - assert.Equal(t, buf.String(), "TRUE") - assert.NoError(t, ds.Literal(me.Truncate(buf), false)) - assert.Equal(t, buf.String(), "FALSE") - assert.NoError(t, ds.Literal(me.Truncate(buf), &b)) - assert.Equal(t, buf.String(), "FALSE") - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), true)) - assert.Equal(t, buf.args, []interface{}{true}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), false)) - assert.Equal(t, buf.args, []interface{}{false}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), &b)) - assert.Equal(t, buf.args, []interface{}{b}) - assert.Equal(t, buf.String(), "?") -} - -func (me *datasetTest) TestLiteralTimeTypes() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - now := time.Now().UTC() - assert.NoError(t, ds.Literal(me.Truncate(buf), now)) - assert.Equal(t, buf.String(), "'"+now.Format(time.RFC3339Nano)+"'") - assert.NoError(t, ds.Literal(me.Truncate(buf), &now)) - assert.Equal(t, buf.String(), "'"+now.Format(time.RFC3339Nano)+"'") - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), now)) - assert.Equal(t, buf.args, []interface{}{now}) - assert.Equal(t, buf.String(), "?") - assert.NoError(t, ds.Literal(me.Truncate(buf), &now)) - assert.Equal(t, buf.args, []interface{}{now}) - assert.Equal(t, buf.String(), "?") -} - -func (me *datasetTest) TestLiteralNilTypes() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), nil)) - assert.Equal(t, buf.String(), "NULL") - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), nil)) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), "NULL") -} - -type datasetValuerType int64 - -func (j datasetValuerType) Value() (driver.Value, error) { - return []byte(fmt.Sprintf("Hello World %d", j)), nil -} - -func (me *datasetTest) TestLiteralValuer() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - - assert.NoError(t, ds.Literal(me.Truncate(buf), datasetValuerType(10))) - assert.Equal(t, buf.String(), "'Hello World 10'") - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), datasetValuerType(10))) - assert.Equal(t, buf.args, []interface{}{[]byte("Hello World 10")}) - assert.Equal(t, buf.String(), "?") - -} - -func (me *datasetTest) TestLiteraSlice() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), []string{"a", "b", "c"})) - assert.Equal(t, buf.String(), `('a', 'b', 'c')`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), []string{"a", "b", "c"})) - assert.Equal(t, buf.args, []interface{}{"a", "b", "c"}) - assert.Equal(t, buf.String(), `(?, ?, ?)`) + assert.True(t, preparedDs.IsPrepared()) + assert.False(t, ds.IsPrepared()) + // should apply the prepared to any datasets created from the root + assert.True(t, preparedDs.Where(Ex{"a": 1}).IsPrepared()) +} + +func (dt *datasetTest) TestGetClauses() { + t := dt.T() + ds := From("test") + ce := exp.NewClauses().SetFrom(exp.NewColumnListExpression(I("test"))) + assert.Equal(t, ce, ds.GetClauses()) +} + +func (dt *datasetTest) TestWith() { + t := dt.T() + from := From("cte") + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.CommonTablesAppend(exp.NewCommonTableExpression(false, "test-cte", from)) + assert.Equal(t, ec, ds.With("test-cte", from).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) } -func (me *datasetTest) TestLiteralDataset() { - t := me.T() - buf := NewSqlBuilder(false) +func (dt *datasetTest) TestWithRecursive() { + t := dt.T() + from := From("cte") ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), From("a"))) - assert.Equal(t, buf.String(), `(SELECT * FROM "a")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), From("a").As("b"))) - assert.Equal(t, buf.String(), `(SELECT * FROM "a") AS "b"`) + dsc := ds.GetClauses() + ec := dsc.CommonTablesAppend(exp.NewCommonTableExpression(true, "test-cte", from)) + assert.Equal(t, ec, ds.WithRecursive("test-cte", from).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestSelect(selects ...interface{}) { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetSelect(exp.NewColumnListExpression(C("a"))) + assert.Equal(t, ec, ds.Select(C("a")).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestSelectDistinct(selects ...interface{}) { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetSelectDistinct(exp.NewColumnListExpression(C("a"))) + assert.Equal(t, ec, ds.SelectDistinct(C("a")).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), From("a"))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `(SELECT * FROM "a")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), From("a").As("b"))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `(SELECT * FROM "a") AS "b"`) -} - -func (me *datasetTest) TestLiteralColumnList() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), cols("a", Literal("true")))) - assert.Equal(t, buf.String(), `"a", true`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), cols("a", Literal("true")))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `"a", true`) -} - -func (me *datasetTest) TestLiteralExpressionList() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), And(I("a").Eq("b"), I("c").Neq(1)))) - assert.Equal(t, buf.String(), `(("a" = 'b') AND ("c" != 1))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Or(I("a").Eq("b"), I("c").Neq(1)))) - assert.Equal(t, buf.String(), `(("a" = 'b') OR ("c" != 1))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Or(I("a").Eq("b"), And(I("c").Neq(1), I("d").Eq(Literal("NOW()")))))) - assert.Equal(t, buf.String(), `(("a" = 'b') OR (("c" != 1) AND ("d" = NOW())))`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), And(I("a").Eq("b"), I("c").Neq(1)))) - assert.Equal(t, buf.args, []interface{}{"b", int64(1)}) - assert.Equal(t, buf.String(), `(("a" = ?) AND ("c" != ?))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Or(I("a").Eq("b"), I("c").Neq(1)))) - assert.Equal(t, buf.args, []interface{}{"b", int64(1)}) - assert.Equal(t, buf.String(), `(("a" = ?) OR ("c" != ?))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Or(I("a").Eq("b"), And(I("c").Neq(1), I("d").Eq(Literal("NOW()")))))) - assert.Equal(t, buf.args, []interface{}{"b", int64(1)}) - assert.Equal(t, buf.String(), `(("a" = ?) OR (("c" != ?) AND ("d" = NOW())))`) -} - -func (me *datasetTest) TestLiteralLiteralExpression() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - - assert.NoError(t, ds.Literal(me.Truncate(buf), Literal(`"b"::DATE = '2010-09-02'`))) - assert.Equal(t, buf.String(), `"b"::DATE = '2010-09-02'`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Literal(`"b" = ? or "c" = ? or d IN ?`, "a", 1, []int{1, 2, 3, 4}))) - assert.Equal(t, buf.String(), `"b" = 'a' or "c" = 1 or d IN (1, 2, 3, 4)`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), Literal(`"b"::DATE = '2010-09-02'`))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `"b"::DATE = '2010-09-02'`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Literal(`"b" = ? or "c" = ? or d IN ?`, "a", 1, []int{1, 2, 3, 4}))) - assert.Equal(t, buf.args, []interface{}{"a", int64(1), int64(1), int64(2), int64(3), int64(4)}) - assert.Equal(t, buf.String(), `"b" = ? or "c" = ? or d IN (?, ?, ?, ?)`) -} - -func (me *datasetTest) TestLiteralAliasedExpression() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").As("b"))) - assert.Equal(t, buf.String(), `"a" AS "b"`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Literal("count(*)").As("count"))) - assert.Equal(t, buf.String(), `count(*) AS "count"`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").As("b"))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `"a" AS "b"`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Literal("count(*)").As("count"))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `count(*) AS "count"`) - - buf = NewSqlBuilder(false) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").As(I("b")))) - assert.Equal(t, buf.String(), `"a" AS "b"`) -} - -func (me *datasetTest) TestBooleanExpression() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Eq(1))) - assert.Equal(t, buf.String(), `("a" = 1)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Eq(true))) - assert.Equal(t, buf.String(), `("a" IS TRUE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Eq(false))) - assert.Equal(t, buf.String(), `("a" IS FALSE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Eq(nil))) - assert.Equal(t, buf.String(), `("a" IS NULL)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Eq([]int64{1, 2, 3}))) - assert.Equal(t, buf.String(), `("a" IN (1, 2, 3))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Eq(From("test2").Select("id")))) - assert.Equal(t, buf.String(), `("a" IN (SELECT "id" FROM "test2"))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Neq(1))) - assert.Equal(t, buf.String(), `("a" != 1)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Neq(true))) - assert.Equal(t, buf.String(), `("a" IS NOT TRUE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Neq(false))) - assert.Equal(t, buf.String(), `("a" IS NOT FALSE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Neq(nil))) - assert.Equal(t, buf.String(), `("a" IS NOT NULL)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Neq([]int64{1, 2, 3}))) - assert.Equal(t, buf.String(), `("a" NOT IN (1, 2, 3))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Neq(From("test2").Select("id")))) - assert.Equal(t, buf.String(), `("a" NOT IN (SELECT "id" FROM "test2"))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Is(nil))) - assert.Equal(t, buf.String(), `("a" IS NULL)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Is(false))) - assert.Equal(t, buf.String(), `("a" IS FALSE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Is(true))) - assert.Equal(t, buf.String(), `("a" IS TRUE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").IsNot(nil))) - assert.Equal(t, buf.String(), `("a" IS NOT NULL)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").IsNot(false))) - assert.Equal(t, buf.String(), `("a" IS NOT FALSE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").IsNot(true))) - assert.Equal(t, buf.String(), `("a" IS NOT TRUE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Gt(1))) - assert.Equal(t, buf.String(), `("a" > 1)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Gte(1))) - assert.Equal(t, buf.String(), `("a" >= 1)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Lt(1))) - assert.Equal(t, buf.String(), `("a" < 1)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Lte(1))) - assert.Equal(t, buf.String(), `("a" <= 1)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").In([]int{1, 2, 3}))) - assert.Equal(t, buf.String(), `("a" IN (1, 2, 3))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotIn([]int{1, 2, 3}))) - assert.Equal(t, buf.String(), `("a" NOT IN (1, 2, 3))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Like("a%"))) - assert.Equal(t, buf.String(), `("a" LIKE 'a%')`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Like(regexp.MustCompile("(a|b)")))) - assert.Equal(t, buf.String(), `("a" ~ '(a|b)')`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotLike("a%"))) - assert.Equal(t, buf.String(), `("a" NOT LIKE 'a%')`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotLike(regexp.MustCompile("(a|b)")))) - assert.Equal(t, buf.String(), `("a" !~ '(a|b)')`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").ILike("a%"))) - assert.Equal(t, buf.String(), `("a" ILIKE 'a%')`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").ILike(regexp.MustCompile("(a|b)")))) - assert.Equal(t, buf.String(), `("a" ~* '(a|b)')`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotILike("a%"))) - assert.Equal(t, buf.String(), `("a" NOT ILIKE 'a%')`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotILike(regexp.MustCompile("(a|b)")))) - assert.Equal(t, buf.String(), `("a" !~* '(a|b)')`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Eq(1))) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `("a" = ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Eq(true))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS TRUE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Eq(false))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS FALSE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Eq(nil))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS NULL)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Eq([]int64{1, 2, 3}))) - assert.Equal(t, buf.args, []interface{}{int64(1), int64(2), int64(3)}) - assert.Equal(t, buf.String(), `("a" IN (?, ?, ?))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Neq(1))) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `("a" != ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Neq(true))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS NOT TRUE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Neq(false))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS NOT FALSE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Neq(nil))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS NOT NULL)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Neq([]int64{1, 2, 3}))) - assert.Equal(t, buf.args, []interface{}{int64(1), int64(2), int64(3)}) - assert.Equal(t, buf.String(), `("a" NOT IN (?, ?, ?))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Is(nil))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS NULL)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Is(false))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS FALSE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Is(true))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS TRUE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").IsNot(nil))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS NOT NULL)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").IsNot(false))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS NOT FALSE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").IsNot(true))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS NOT TRUE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Gt(1))) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `("a" > ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Gte(1))) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `("a" >= ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Lt(1))) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `("a" < ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Lte(1))) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `("a" <= ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").In([]int{1, 2, 3}))) - assert.Equal(t, buf.args, []interface{}{int64(1), int64(2), int64(3)}) - assert.Equal(t, buf.String(), `("a" IN (?, ?, ?))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotIn([]int{1, 2, 3}))) - assert.Equal(t, buf.args, []interface{}{int64(1), int64(2), int64(3)}) - assert.Equal(t, buf.String(), `("a" NOT IN (?, ?, ?))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Like("a%"))) - assert.Equal(t, buf.args, []interface{}{"a%"}) - assert.Equal(t, buf.String(), `("a" LIKE ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Like(regexp.MustCompile("(a|b)")))) - assert.Equal(t, buf.args, []interface{}{"(a|b)"}) - assert.Equal(t, buf.String(), `("a" ~ ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotLike("a%"))) - assert.Equal(t, buf.args, []interface{}{"a%"}) - assert.Equal(t, buf.String(), `("a" NOT LIKE ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotLike(regexp.MustCompile("(a|b)")))) - assert.Equal(t, buf.args, []interface{}{"(a|b)"}) - assert.Equal(t, buf.String(), `("a" !~ ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").ILike("a%"))) - assert.Equal(t, buf.args, []interface{}{"a%"}) - assert.Equal(t, buf.String(), `("a" ILIKE ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").ILike(regexp.MustCompile("(a|b)")))) - assert.Equal(t, buf.args, []interface{}{"(a|b)"}) - assert.Equal(t, buf.String(), `("a" ~* ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotILike("a%"))) - assert.Equal(t, buf.args, []interface{}{"a%"}) - assert.Equal(t, buf.String(), `("a" NOT ILIKE ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotILike(regexp.MustCompile("(a|b)")))) - assert.Equal(t, buf.args, []interface{}{"(a|b)"}) - assert.Equal(t, buf.String(), `("a" !~* ?)`) -} - -func (me *datasetTest) TestRangeExpression() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Between(RangeVal{Start: 1, End: 2}))) - assert.Equal(t, buf.String(), `("a" BETWEEN 1 AND 2)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotBetween(RangeVal{Start: 1, End: 2}))) - assert.Equal(t, buf.String(), `("a" NOT BETWEEN 1 AND 2)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Between(RangeVal{Start: "aaa", End: "zzz"}))) - assert.Equal(t, buf.String(), `("a" BETWEEN 'aaa' AND 'zzz')`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Between(RangeVal{Start: 1, End: 2}))) - assert.Equal(t, buf.args, []interface{}{int64(1), int64(2)}) - assert.Equal(t, buf.String(), `("a" BETWEEN ? AND ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotBetween(RangeVal{Start: 1, End: 2}))) - assert.Equal(t, buf.args, []interface{}{int64(1), int64(2)}) - assert.Equal(t, buf.String(), `("a" NOT BETWEEN ? AND ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Between(RangeVal{Start: "aaa", End: "zzz"}))) - assert.Equal(t, buf.args, []interface{}{"aaa", "zzz"}) - assert.Equal(t, buf.String(), `("a" BETWEEN ? AND ?)`) -} - -func (me *datasetTest) TestLiteralOrderedExpression() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Asc())) - assert.Equal(t, buf.String(), `"a" ASC`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Desc())) - assert.Equal(t, buf.String(), `"a" DESC`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Asc().NullsLast())) - assert.Equal(t, buf.String(), `"a" ASC NULLS LAST`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Desc().NullsLast())) - assert.Equal(t, buf.String(), `"a" DESC NULLS LAST`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Asc().NullsFirst())) - assert.Equal(t, buf.String(), `"a" ASC NULLS FIRST`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Desc().NullsFirst())) - assert.Equal(t, buf.String(), `"a" DESC NULLS FIRST`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Asc())) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `"a" ASC`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Desc())) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `"a" DESC`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Asc().NullsLast())) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `"a" ASC NULLS LAST`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Desc().NullsLast())) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `"a" DESC NULLS LAST`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Asc().NullsFirst())) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `"a" ASC NULLS FIRST`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Desc().NullsFirst())) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `"a" DESC NULLS FIRST`) -} - -func (me *datasetTest) TestLiteralUpdateExpression() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Set(1))) - assert.Equal(t, buf.String(), `"a"=1`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Set(1))) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `"a"=?`) -} - -func (me *datasetTest) TestLiteralSqlFunctionExpression() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), Func("MIN", I("a")))) - assert.Equal(t, buf.String(), `MIN("a")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), MIN("a"))) - assert.Equal(t, buf.String(), `MIN("a")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), COALESCE(I("a"), "a"))) - assert.Equal(t, buf.String(), `COALESCE("a", 'a')`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), Func("MIN", I("a")))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `MIN("a")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), MIN("a"))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `MIN("a")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), COALESCE(I("a"), "a"))) - assert.Equal(t, buf.args, []interface{}{"a"}) - assert.Equal(t, buf.String(), `COALESCE("a", ?)`) -} - -func (me *datasetTest) TestLiteralCastExpression() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Cast("DATE"))) - assert.Equal(t, buf.String(), `CAST("a" AS DATE)`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Cast("DATE"))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `CAST("a" AS DATE)`) -} - -func (me *datasetTest) TestCommonTableExpression() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), With(false, "a", From("b")))) - assert.Equal(t, buf.String(), `a AS (SELECT * FROM "b")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), With(false, "a(x,y)", From("b")))) - assert.Equal(t, buf.String(), `a(x,y) AS (SELECT * FROM "b")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), With(true, "a", From("b")))) - assert.Equal(t, buf.String(), `a AS (SELECT * FROM "b")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), With(true, "a(x,y)", From("b")))) - assert.Equal(t, buf.String(), `a(x,y) AS (SELECT * FROM "b")`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), With(false, "a", From("b")))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `a AS (SELECT * FROM "b")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), With(false, "a(x,y)", From("b")))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `a(x,y) AS (SELECT * FROM "b")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), With(true, "a", From("b")))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `a AS (SELECT * FROM "b")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), With(true, "a(x,y)", From("b")))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `a(x,y) AS (SELECT * FROM "b")`) -} - -func (me *datasetTest) TestCompoundExpression() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), Union(From("b")))) - assert.Equal(t, buf.String(), ` UNION (SELECT * FROM "b")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), UnionAll(From("b")))) - assert.Equal(t, buf.String(), ` UNION ALL (SELECT * FROM "b")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Intersect(From("b")))) - assert.Equal(t, buf.String(), ` INTERSECT (SELECT * FROM "b")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), IntersectAll(From("b")))) - assert.Equal(t, buf.String(), ` INTERSECT ALL (SELECT * FROM "b")`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), Union(From("b")))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), ` UNION (SELECT * FROM "b")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), UnionAll(From("b")))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), ` UNION ALL (SELECT * FROM "b")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Intersect(From("b")))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), ` INTERSECT (SELECT * FROM "b")`) - assert.NoError(t, ds.Literal(me.Truncate(buf), IntersectAll(From("b")))) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), ` INTERSECT ALL (SELECT * FROM "b")`) -} - -func (me *datasetTest) TestLiteralIdentifierExpression() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a"))) - assert.Equal(t, buf.String(), `"a"`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a.b"))) - assert.Equal(t, buf.String(), `"a"."b"`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a.b.c"))) - assert.Equal(t, buf.String(), `"a"."b"."c"`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a.b.*"))) - assert.Equal(t, buf.String(), `"a"."b".*`) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a.*"))) - assert.Equal(t, buf.String(), `"a".*`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a"))) - assert.Equal(t, buf.String(), `"a"`) - assert.Equal(t, buf.args, []interface{}{}) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a.b"))) - assert.Equal(t, buf.String(), `"a"."b"`) - assert.Equal(t, buf.args, []interface{}{}) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a.b.c"))) - assert.Equal(t, buf.String(), `"a"."b"."c"`) - assert.Equal(t, buf.args, []interface{}{}) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a.b.*"))) - assert.Equal(t, buf.String(), `"a"."b".*`) - assert.Equal(t, buf.args, []interface{}{}) - assert.NoError(t, ds.Literal(me.Truncate(buf), I("a.*"))) - assert.Equal(t, buf.String(), `"a".*`) - assert.Equal(t, buf.args, []interface{}{}) -} - -func (me *datasetTest) TestLiteralExpressionMap() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": 1})) - assert.Equal(t, buf.String(), `("a" = 1)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": true})) - assert.Equal(t, buf.String(), `("a" IS TRUE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": false})) - assert.Equal(t, buf.String(), `("a" IS FALSE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": nil})) - assert.Equal(t, buf.String(), `("a" IS NULL)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": []string{"a", "b", "c"}})) - assert.Equal(t, buf.String(), `("a" IN ('a', 'b', 'c'))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"neq": 1}})) - assert.Equal(t, buf.String(), `("a" != 1)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"isnot": true}})) - assert.Equal(t, buf.String(), `("a" IS NOT TRUE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"gt": 1}})) - assert.Equal(t, buf.String(), `("a" > 1)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"gte": 1}})) - assert.Equal(t, buf.String(), `("a" >= 1)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"lt": 1}})) - assert.Equal(t, buf.String(), `("a" < 1)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"lte": 1}})) - assert.Equal(t, buf.String(), `("a" <= 1)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"like": "a%"}})) - assert.Equal(t, buf.String(), `("a" LIKE 'a%')`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"notLike": "a%"}})) - assert.Equal(t, buf.String(), `("a" NOT LIKE 'a%')`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"notLike": "a%"}})) - assert.Equal(t, buf.String(), `("a" NOT LIKE 'a%')`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"in": []string{"a", "b", "c"}}})) - assert.Equal(t, buf.String(), `("a" IN ('a', 'b', 'c'))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"notIn": []string{"a", "b", "c"}}})) - assert.Equal(t, buf.String(), `("a" NOT IN ('a', 'b', 'c'))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"is": nil, "eq": 10}})) - assert.Equal(t, buf.String(), `(("a" = 10) OR ("a" IS NULL))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"between": RangeVal{Start: 1, End: 10}}})) - assert.Equal(t, buf.String(), `("a" BETWEEN 1 AND 10)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"notbetween": RangeVal{Start: 1, End: 10}}})) - assert.Equal(t, buf.String(), `("a" NOT BETWEEN 1 AND 10)`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": 1})) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `("a" = ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": true})) - assert.Equal(t, buf.String(), `("a" IS TRUE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": false})) - assert.Equal(t, buf.String(), `("a" IS FALSE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": nil})) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS NULL)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": []string{"a", "b", "c"}})) - assert.Equal(t, buf.args, []interface{}{"a", "b", "c"}) - assert.Equal(t, buf.String(), `("a" IN (?, ?, ?))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"neq": 1}})) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `("a" != ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"isnot": true}})) - assert.Equal(t, buf.args, []interface{}{}) - assert.Equal(t, buf.String(), `("a" IS NOT TRUE)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"gt": 1}})) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `("a" > ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"gte": 1}})) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `("a" >= ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"lt": 1}})) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `("a" < ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"lte": 1}})) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `("a" <= ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"like": "a%"}})) - assert.Equal(t, buf.args, []interface{}{"a%"}) - assert.Equal(t, buf.String(), `("a" LIKE ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"notLike": "a%"}})) - assert.Equal(t, buf.args, []interface{}{"a%"}) - assert.Equal(t, buf.String(), `("a" NOT LIKE ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"in": []string{"a", "b", "c"}}})) - assert.Equal(t, buf.args, []interface{}{"a", "b", "c"}) - assert.Equal(t, buf.String(), `("a" IN (?, ?, ?))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"notIn": []string{"a", "b", "c"}}})) - assert.Equal(t, buf.args, []interface{}{"a", "b", "c"}) - assert.Equal(t, buf.String(), `("a" NOT IN (?, ?, ?))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"is": nil, "eq": 10}})) - assert.Equal(t, buf.args, []interface{}{int64(10)}) - assert.Equal(t, buf.String(), `(("a" = ?) OR ("a" IS NULL))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"between": RangeVal{Start: 1, End: 10}}})) - assert.Equal(t, buf.args, []interface{}{int64(1), int64(10)}) - assert.Equal(t, buf.String(), `("a" BETWEEN ? AND ?)`) - assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"notbetween": RangeVal{Start: 1, End: 10}}})) - assert.Equal(t, buf.args, []interface{}{int64(1), int64(10)}) - assert.Equal(t, buf.String(), `("a" NOT BETWEEN ? AND ?)`) -} - -func (me *datasetTest) TestLiteralExpressionOrMap() { - t := me.T() - buf := NewSqlBuilder(false) - ds := From("test") - assert.NoError(t, ds.Literal(me.Truncate(buf), ExOr{"a": 1, "b": true})) - assert.Equal(t, buf.String(), `(("a" = 1) OR ("b" IS TRUE))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), ExOr{"a": 1, "b": []string{"a", "b", "c"}})) - assert.Equal(t, buf.String(), `(("a" = 1) OR ("b" IN ('a', 'b', 'c')))`) - - buf = NewSqlBuilder(true) - assert.NoError(t, ds.Literal(me.Truncate(buf), ExOr{"a": 1, "b": true})) - assert.Equal(t, buf.args, []interface{}{int64(1)}) - assert.Equal(t, buf.String(), `(("a" = ?) OR ("b" IS TRUE))`) - assert.NoError(t, ds.Literal(me.Truncate(buf), ExOr{"a": 1, "b": []string{"a", "b", "c"}})) - assert.Equal(t, buf.args, []interface{}{int64(1), "a", "b", "c"}) - assert.Equal(t, buf.String(), `(("a" = ?) OR ("b" IN (?, ?, ?)))`) +func (dt *datasetTest) TestClearSelect() { + t := dt.T() + ds := From("test").Select(C("a")) + dsc := ds.GetClauses() + ec := dsc.SetSelect(exp.NewColumnListExpression(Star())) + assert.Equal(t, ec, ds.ClearSelect().GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestSelectAppend(selects ...interface{}) { + t := dt.T() + ds := From("test").Select(C("a")) + dsc := ds.GetClauses() + ec := dsc.SelectAppend(exp.NewColumnListExpression(C("b"))) + assert.Equal(t, ec, ds.SelectAppend(C("b")).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestFrom(from ...interface{}) { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetFrom(exp.NewColumnListExpression(T("t"))) + assert.Equal(t, ec, ds.From(T("t")).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestFromSelf() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetFrom(exp.NewColumnListExpression(ds.As("t1"))) + assert.Equal(t, ec, ds.FromSelf().GetClauses()) + + ec2 := dsc.SetFrom(exp.NewColumnListExpression(ds.As("test"))) + assert.Equal(t, ec2, ds.As("test").FromSelf().GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestCompoundFromSelf() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + assert.Equal(t, dsc, ds.CompoundFromSelf().GetClauses()) + + ds2 := ds.Limit(1) + dsc2 := exp.NewClauses().SetFrom(exp.NewColumnListExpression(ds2.As("t1"))) + assert.Equal(t, dsc2, ds2.CompoundFromSelf().GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} +func (dt *datasetTest) TestJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewConditionedJoinExpression(exp.InnerJoinType, T("foo"), On(C("a").IsNull())), + ) + assert.Equal(t, ec, ds.Join(T("foo"), On(C("a").IsNull())).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestInnerJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewConditionedJoinExpression(exp.InnerJoinType, T("foo"), On(C("a").IsNull())), + ) + assert.Equal(t, ec, ds.InnerJoin(T("foo"), On(C("a").IsNull())).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestFullOuterJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewConditionedJoinExpression(exp.FullOuterJoinType, T("foo"), On(C("a").IsNull())), + ) + assert.Equal(t, ec, ds.FullOuterJoin(T("foo"), On(C("a").IsNull())).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestRightOuterJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewConditionedJoinExpression(exp.RightOuterJoinType, T("foo"), On(C("a").IsNull())), + ) + assert.Equal(t, ec, ds.RightOuterJoin(T("foo"), On(C("a").IsNull())).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestLeftOuterJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewConditionedJoinExpression(exp.LeftOuterJoinType, T("foo"), On(C("a").IsNull())), + ) + assert.Equal(t, ec, ds.LeftOuterJoin(T("foo"), On(C("a").IsNull())).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestFullJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewConditionedJoinExpression(exp.FullJoinType, T("foo"), On(C("a").IsNull())), + ) + assert.Equal(t, ec, ds.FullJoin(T("foo"), On(C("a").IsNull())).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestRightJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewConditionedJoinExpression(exp.RightJoinType, T("foo"), On(C("a").IsNull())), + ) + assert.Equal(t, ec, ds.RightJoin(T("foo"), On(C("a").IsNull())).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestLeftJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewConditionedJoinExpression(exp.LeftJoinType, T("foo"), On(C("a").IsNull())), + ) + assert.Equal(t, ec, ds.LeftJoin(T("foo"), On(C("a").IsNull())).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestNaturalJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewUnConditionedJoinExpression(exp.NaturalJoinType, T("foo")), + ) + assert.Equal(t, ec, ds.NaturalJoin(T("foo")).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestNaturalLeftJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewUnConditionedJoinExpression(exp.NaturalLeftJoinType, T("foo")), + ) + assert.Equal(t, ec, ds.NaturalLeftJoin(T("foo")).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestNaturalRightJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewUnConditionedJoinExpression(exp.NaturalRightJoinType, T("foo")), + ) + assert.Equal(t, ec, ds.NaturalRightJoin(T("foo")).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} +func (dt *datasetTest) TestNaturalFullJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewUnConditionedJoinExpression(exp.NaturalFullJoinType, T("foo")), + ) + assert.Equal(t, ec, ds.NaturalFullJoin(T("foo")).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestCrossJoin() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.JoinsAppend( + exp.NewUnConditionedJoinExpression(exp.CrossJoinType, T("foo")), + ) + assert.Equal(t, ec, ds.CrossJoin(T("foo")).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestWhere() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + w := Ex{ + "a": 1, + } + ec := dsc.WhereAppend(w) + assert.Equal(t, ec, ds.Where(w).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestClearWhere() { + t := dt.T() + w := Ex{ + "a": 1, + } + ds := From("test").Where(w) + dsc := ds.GetClauses() + ec := dsc.ClearWhere() + assert.Equal(t, ec, ds.ClearWhere().GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestForUpdate() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetLock(exp.NewLock(exp.ForUpdate, NoWait)) + assert.Equal(t, ec, ds.ForUpdate(NoWait).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestForNoKeyUpdate() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetLock(exp.NewLock(exp.ForNoKeyUpdate, NoWait)) + assert.Equal(t, ec, ds.ForNoKeyUpdate(NoWait).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestForKeyShare() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetLock(exp.NewLock(exp.ForKeyShare, NoWait)) + assert.Equal(t, ec, ds.ForKeyShare(NoWait).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestForShare() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetLock(exp.NewLock(exp.ForShare, NoWait)) + assert.Equal(t, ec, ds.ForShare(NoWait).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestGroupBy() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetGroupBy(exp.NewColumnListExpression(C("a"))) + assert.Equal(t, ec, ds.GroupBy("a").GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestHaving() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + h := C("a").Gt(1) + ec := dsc.HavingAppend(h) + assert.Equal(t, ec, ds.Having(h).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestOrder() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + o := C("a").Desc() + ec := dsc.SetOrder(o) + assert.Equal(t, ec, ds.Order(o).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestOrderAppend() { + t := dt.T() + ds := From("test").Order(C("a").Desc()) + dsc := ds.GetClauses() + o := C("b").Desc() + ec := dsc.OrderAppend(o) + assert.Equal(t, ec, ds.OrderAppend(o).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestClearOrder() { + t := dt.T() + ds := From("test").Order(C("a").Desc()) + dsc := ds.GetClauses() + ec := dsc.ClearOrder() + assert.Equal(t, ec, ds.ClearOrder().GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestLimit() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetLimit(uint(1)) + assert.Equal(t, ec, ds.Limit(1).GetClauses()) + assert.Equal(t, dsc, ds.Limit(0).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestLimitAll() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetLimit(L("ALL")) + assert.Equal(t, ec, ds.LimitAll().GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestClearLimit() { + t := dt.T() + ds := From("test").Limit(1) + dsc := ds.GetClauses() + ec := dsc.ClearLimit() + assert.Equal(t, ec, ds.ClearLimit().GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestOffset() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetOffset(1) + assert.Equal(t, ec, ds.Offset(1).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestClearOffset() { + t := dt.T() + ds := From("test").Offset(1) + dsc := ds.GetClauses() + ec := dsc.ClearOffset() + assert.Equal(t, ec, ds.ClearOffset().GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestUnion() { + t := dt.T() + uds := From("union_test") + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.CompoundsAppend(exp.NewCompoundExpression(exp.UnionCompoundType, uds)) + assert.Equal(t, ec, ds.Union(uds).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestUnionAll() { + t := dt.T() + uds := From("union_test") + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.CompoundsAppend(exp.NewCompoundExpression(exp.UnionAllCompoundType, uds)) + assert.Equal(t, ec, ds.UnionAll(uds).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestIntersect() { + t := dt.T() + uds := From("union_test") + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.CompoundsAppend(exp.NewCompoundExpression(exp.IntersectCompoundType, uds)) + assert.Equal(t, ec, ds.Intersect(uds).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} +func (dt *datasetTest) TestIntersectAll() { + t := dt.T() + uds := From("union_test") + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.CompoundsAppend(exp.NewCompoundExpression(exp.IntersectAllCompoundType, uds)) + assert.Equal(t, ec, ds.IntersectAll(uds).GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestReturning() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetReturning(exp.NewColumnListExpression(C("a"))) + assert.Equal(t, ec, ds.Returning("a").GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestAs() { + t := dt.T() + ds := From("test") + dsc := ds.GetClauses() + ec := dsc.SetAlias(T("a")) + assert.Equal(t, ec, ds.As("a").GetClauses()) + assert.Equal(t, dsc, ds.GetClauses()) +} + +func (dt *datasetTest) TestToSQL() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + md.On("ToSelectSQL", sqlB, c).Return(nil).Once() + sql, args, err := ds.ToSQL() + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Nil(t, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToSQL_ReturnedError() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + ee := errors.New("expected error") + md.On("ToSelectSQL", sqlB, c).Run(func(args mock.Arguments) { + args.Get(0).(sb.SQLBuilder).SetError(ee) + }).Once() + + sql, args, err := ds.ToSQL() + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Equal(t, ee, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestAppendSQL() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + md.On("ToSelectSQL", sqlB, c).Return(nil).Once() + ds.AppendSQL(sqlB) + assert.NoError(t, sqlB.Error()) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToInsertSQL_WithNoArgs() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + eie, err := exp.NewInsertExpression() + assert.NoError(t, err) + sqlB := sb.NewSQLBuilder(false) + md.On("ToInsertSQL", sqlB, c, eie).Return(nil).Once() + sql, args, err := ds.ToInsertSQL() + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Nil(t, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToInsertSQL_WithReturnedError() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + rows := []interface{}{ + Record{"c": "a"}, + Record{"c": "b"}, + } + eie, err := exp.NewInsertExpression(rows...) + assert.NoError(t, err) + + sqlB := sb.NewSQLBuilder(false) + ee := errors.New("test") + md.On("ToInsertSQL", sqlB, c, eie).Run(func(args mock.Arguments) { + args.Get(0).(sb.SQLBuilder).SetError(ee) + }).Once() + sql, args, err := ds.ToInsertSQL(rows...) + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Equal(t, ee, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToInsertIgnoreSQL() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + rows := []interface{}{ + Record{"c": "a"}, + Record{"c": "b"}, + } + eie, err := exp.NewInsertExpression(rows...) + assert.NoError(t, err) + eie = eie.DoNothing() + sqlB := sb.NewSQLBuilder(false) + md.On("ToInsertSQL", sqlB, c, eie).Return(nil).Once() + sql, args, err := ds.ToInsertIgnoreSQL(rows...) + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Nil(t, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToInsertConflictSQL() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + ce := DoUpdate("a", "b") + rows := []interface{}{ + Record{"c": "a"}, + Record{"c": "b"}, + } + eie, err := exp.NewInsertExpression(rows...) + assert.NoError(t, err) + sqlB := sb.NewSQLBuilder(false) + eie = eie.SetOnConflict(ce) + md.On("ToInsertSQL", sqlB, c, eie).Return(nil).Once() + sql, args, err := ds.ToInsertConflictSQL(ce, rows...) + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Nil(t, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToUpdateSQL() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + r := Record{"c": "a"} + md.On("ToUpdateSQL", sqlB, c, r).Return(nil).Once() + sql, args, err := ds.ToUpdateSQL(Record{"c": "a"}) + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Nil(t, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToUpdateSQL_Prepared() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").Prepared(true).SetDialect(md) + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(true) + r := Record{"c": "a"} + md.On("ToUpdateSQL", sqlB, c, r).Return(nil).Once() + sql, args, err := ds.ToUpdateSQL(Record{"c": "a"}) + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Nil(t, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToUpdateSQL_WithError() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + r := Record{"c": "a"} + ee := errors.New("expected error") + md.On("ToUpdateSQL", sqlB, c, r).Run(func(args mock.Arguments) { + args.Get(0).(sb.SQLBuilder).SetError(ee) + }).Once() + + sql, args, err := ds.ToUpdateSQL(Record{"c": "a"}) + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Equal(t, ee, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToDeleteSQL() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + md.On("ToDeleteSQL", sqlB, c).Return(nil).Once() + + sql, args, err := ds.ToDeleteSQL() + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Nil(t, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToDeleteSQL_Prepared() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").Prepared(true).SetDialect(md) + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(true) + md.On("ToDeleteSQL", sqlB, c).Return(nil).Once() + + sql, args, err := ds.ToDeleteSQL() + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Nil(t, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToDeleteSQL_WithError() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + ee := errors.New("expected error") + sqlB := sb.NewSQLBuilder(false) + md.On("ToDeleteSQL", sqlB, c).Run(func(args mock.Arguments) { + args.Get(0).(sb.SQLBuilder).SetError(ee) + }).Once() + + sql, args, err := ds.ToDeleteSQL() + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Equal(t, ee, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToTruncateSQL() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + md.On("ToTruncateSQL", sqlB, c, TruncateOptions{}).Return(nil).Once() + + sql, args, err := ds.ToTruncateSQL() + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Nil(t, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToTruncateSQL__Prepared() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").Prepared(true).SetDialect(md) + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(true) + md.On("ToTruncateSQL", sqlB, c, TruncateOptions{}).Return(nil).Once() + + sql, args, err := ds.ToTruncateSQL() + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Nil(t, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToTruncateSQL_WithError() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + ee := errors.New("expected error") + sqlB := sb.NewSQLBuilder(false) + md.On("ToTruncateSQL", sqlB, c, TruncateOptions{}).Run(func(args mock.Arguments) { + args.Get(0).(sb.SQLBuilder).SetError(ee) + }).Once() + + sql, args, err := ds.ToTruncateSQL() + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Equal(t, ee, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToTruncateWithOptsSQL() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + to := TruncateOptions{Cascade: true} + md.On("ToTruncateSQL", sqlB, c, to).Return(nil).Once() + + sql, args, err := ds.ToTruncateWithOptsSQL(to) + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Nil(t, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToTruncateWithOptsSQL_Prepared() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").Prepared(true).SetDialect(md) + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(true) + to := TruncateOptions{Cascade: true} + md.On("ToTruncateSQL", sqlB, c, to).Return(nil).Once() + + sql, args, err := ds.ToTruncateWithOptsSQL(to) + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Nil(t, err) + md.AssertExpectations(t) +} + +func (dt *datasetTest) TestToTruncateWithOptsSQL_WithError() { + t := dt.T() + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + c := ds.GetClauses() + ee := errors.New("expected error") + to := TruncateOptions{Cascade: true} + sqlB := sb.NewSQLBuilder(false) + md.On("ToTruncateSQL", sqlB, c, to).Run(func(args mock.Arguments) { + args.Get(0).(sb.SQLBuilder).SetError(ee) + }).Once() + + sql, args, err := ds.ToTruncateWithOptsSQL(to) + assert.Empty(t, sql) + assert.Empty(t, args) + assert.Equal(t, ee, err) + md.AssertExpectations(t) } func TestDatasetSuite(t *testing.T) { diff --git a/dataset_update.go b/dataset_update.go deleted file mode 100644 index d2b52870..00000000 --- a/dataset_update.go +++ /dev/null @@ -1,104 +0,0 @@ -package goqu - -import ( - "reflect" - "sort" -) - -func (me *Dataset) canUpdateField(field reflect.StructField) bool { - goquTag, dbTag := tagOptions(field.Tag.Get("goqu")), field.Tag.Get("db") - return !goquTag.Contains("skipupdate") && dbTag != "" && dbTag != "-" -} - -//Generates an UPDATE statement. If `Prepared` has been called with true then the statement will not be interpolated. -//When using structs you may specify a column to be skipped in the update, (e.g. created) by specifying a goqu tag with `skipupdate` -// type Item struct{ -// Id uint32 `db:"id" -// Created time.Time `db:"created" goqu:"skipupdate"` -// Name string `db:"name"` -// } -// -//update: can either be a a map[string]interface{}, Record or a struct -// -//Errors: -// * The update is not a of type struct, Record, or map[string]interface{} -// * The update statement has no FROM clause -// * There is an error generating the SQL -func (me *Dataset) ToUpdateSql(update interface{}) (string, []interface{}, error) { - if !me.hasSources() { - return "", nil, NewGoquError("No source found when generating update sql") - } - updates, err := me.getUpdateExpressions(update) - if err != nil { - return "", nil, err - } - buf := NewSqlBuilder(me.isPrepared) - if err := me.adapter.CommonTablesSql(buf, me.clauses.CommonTables); err != nil { - return "", nil, err - } - if err := me.adapter.UpdateBeginSql(buf); err != nil { - return "", nil, err - } - if err := me.adapter.SourcesSql(buf, me.clauses.From); err != nil { - return "", nil, err - } - if err := me.adapter.UpdateExpressionsSql(buf, updates...); err != nil { - return "", nil, err - } - if err := me.adapter.WhereSql(buf, me.clauses.Where); err != nil { - return "", nil, err - } - if me.adapter.SupportsOrderByOnUpdate() { - if err := me.adapter.OrderSql(buf, me.clauses.Order); err != nil { - return "", nil, err - } - } - if me.adapter.SupportsLimitOnUpdate() { - if err := me.adapter.LimitSql(buf, me.clauses.Limit); err != nil { - return "", nil, err - } - } - if me.adapter.SupportsReturn() { - if err := me.adapter.ReturningSql(buf, me.clauses.Returning); err != nil { - return "", nil, err - } - } else if me.clauses.Returning != nil { - return "", nil, NewGoquError("Adapter does not support RETURNING clause") - } - sql, args := buf.ToSql() - return sql, args, nil -} - -func (me *Dataset) getUpdateExpressions(update interface{}) ([]UpdateExpression, error) { - updateValue := reflect.Indirect(reflect.ValueOf(update)) - var updates []UpdateExpression - switch updateValue.Kind() { - case reflect.Map: - keys := valueSlice(updateValue.MapKeys()) - sort.Sort(keys) - for _, key := range keys { - updates = append(updates, I(key.String()).Set(updateValue.MapIndex(key).Interface())) - } - case reflect.Struct: - updates = me.getUpdateExpressionsStruct(updateValue) - default: - return nil, NewGoquError("Unsupported update interface type %+v", updateValue.Type()) - } - return updates, nil -} - -func (me *Dataset) getUpdateExpressionsStruct(value reflect.Value) (updates []UpdateExpression) { - for i := 0; i < value.NumField(); i++ { - v := value.Field(i) - t := value.Type().Field(i) - if !t.Anonymous { - if me.canUpdateField(t) { - updates = append(updates, I(t.Tag.Get("db")).Set(v.Interface())) - } - } else { - updates = append(updates, me.getUpdateExpressionsStruct(reflect.Indirect(reflect.ValueOf(v.Interface())))...) - } - } - - return updates -} diff --git a/dataset_update_test.go b/dataset_update_test.go deleted file mode 100644 index ddd3e367..00000000 --- a/dataset_update_test.go +++ /dev/null @@ -1,404 +0,0 @@ -package goqu - -import ( - "database/sql" - "database/sql/driver" - "fmt" - "time" - - "github.com/DATA-DOG/go-sqlmock" - "github.com/stretchr/testify/assert" -) - -func (me *datasetTest) TestUpdateSqlWithNoSources() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - _, _, err := ds1.From().ToUpdateSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.EqualError(t, err, "goqu: No source found when generating update sql") -} - -func (me *datasetTest) TestUpdateSqlNoReturning() { - t := me.T() - mDb, _, _ := sqlmock.New() - ds1 := New("no-return", mDb).From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - _, _, err := ds1.Returning("id").ToUpdateSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.EqualError(t, err, "goqu: Adapter does not support RETURNING clause") -} - -func (me *datasetTest) TestUpdateSqlWithLimit() { - t := me.T() - mDb, _, _ := sqlmock.New() - ds1 := New("limit", mDb).From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - sql, _, err := ds1.Limit(10).ToUpdateSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.Nil(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test' LIMIT 10`) -} - -func (me *datasetTest) TestUpdateSqlWithOrder() { - t := me.T() - mDb, _, _ := sqlmock.New() - ds1 := New("order", mDb).From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - sql, _, err := ds1.Order(I("name").Desc()).ToUpdateSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.Nil(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test' ORDER BY "name" DESC`) -} - -func (me *datasetTest) TestUpdateSqlWithStructs() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - sql, _, err := ds1.ToUpdateSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test'`) -} - -func (me *datasetTest) TestUpdateSqlWithMaps() { - t := me.T() - ds1 := From("items") - sql, _, err := ds1.ToUpdateSql(Record{"name": "Test", "address": "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test'`) - -} - -func (me *datasetTest) TestUpdateSqlWithByteSlice() { - t := me.T() - ds1 := From("items") - type item struct { - Name string `db:"name"` - Data []byte `db:"data"` - } - sql, _, err := ds1.Returning(I("items").All()).ToUpdateSql(item{Name: "Test", Data: []byte(`{"someJson":"data"}`)}) - assert.NoError(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "name"='Test',"data"='{"someJson":"data"}' RETURNING "items".*`) -} - -type valuerType []byte - -func (j valuerType) Value() (driver.Value, error) { - return []byte(fmt.Sprintf("%s World", string(j))), nil -} - -func (me *datasetTest) TestUpdateSqlWithCustomValuer() { - t := me.T() - ds1 := From("items") - type item struct { - Name string `db:"name"` - Data valuerType `db:"data"` - } - sql, _, err := ds1.Returning(I("items").All()).ToUpdateSql(item{Name: "Test", Data: []byte(`Hello`)}) - assert.NoError(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "name"='Test',"data"='Hello World' RETURNING "items".*`) -} - -func (me *datasetTest) TestUpdateSqlWithValuer() { - t := me.T() - ds1 := From("items") - type item struct { - Name string `db:"name"` - Data sql.NullString `db:"data"` - } - - sql, _, err := ds1.Returning(I("items").All()).ToUpdateSql(item{Name: "Test", Data: sql.NullString{String: "Hello World", Valid: true}}) - assert.NoError(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "name"='Test',"data"='Hello World' RETURNING "items".*`) -} - -func (me *datasetTest) TestUpdateSqlWithValuerNull() { - t := me.T() - ds1 := From("items") - type item struct { - Name string `db:"name"` - Data sql.NullString `db:"data"` - } - sql, _, err := ds1.Returning(I("items").All()).ToUpdateSql(item{Name: "Test"}) - assert.NoError(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "name"='Test',"data"=NULL RETURNING "items".*`) -} - -func (me *datasetTest) TestUpdateSqlWithEmbeddedStruct() { - t := me.T() - ds1 := From("items") - type Phone struct { - Primary string `db:"primary_phone"` - Home string `db:"home_phone"` - Created time.Time `db:"phone_created"` - } - type item struct { - Phone - Address string `db:"address" goqu:"skipupdate"` - Name string `db:"name"` - Created time.Time `db:"created"` - NilPointer interface{} `db:"nil_pointer"` - } - created, _ := time.Parse("2006-01-02", "2015-01-01") - - sql, args, err := ds1.ToUpdateSql(item{Name: "Test", Address: "111 Test Addr", Created: created, Phone: Phone{ - Home: "123123", - Primary: "456456", - Created: created, - }}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{}) - assert.Equal(t, sql, `UPDATE "items" SET "primary_phone"='456456',"home_phone"='123123',"phone_created"='2015-01-01T00:00:00Z',"name"='Test',"created"='2015-01-01T00:00:00Z',"nil_pointer"=NULL`) -} - -func (me *datasetTest) TestUpdateSqlWithEmbeddedStructPtr() { - t := me.T() - ds1 := From("items") - type Phone struct { - Primary string `db:"primary_phone"` - Home string `db:"home_phone"` - Created time.Time `db:"phone_created"` - } - type item struct { - *Phone - Address string `db:"address" goqu:"skipupdate"` - Name string `db:"name"` - Created time.Time `db:"created"` - } - created, _ := time.Parse("2006-01-02", "2015-01-01") - - sql, args, err := ds1.ToUpdateSql(item{Name: "Test", Address: "111 Test Addr", Created: created, Phone: &Phone{ - Home: "123123", - Primary: "456456", - Created: created, - }}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{}) - assert.Equal(t, sql, `UPDATE "items" SET "primary_phone"='456456',"home_phone"='123123',"phone_created"='2015-01-01T00:00:00Z',"name"='Test',"created"='2015-01-01T00:00:00Z'`) -} - -func (me *datasetTest) TestUpdateSqlWithUnsupportedType() { - t := me.T() - ds1 := From("items") - _, _, err := ds1.ToUpdateSql([]string{"HELLO"}) - assert.EqualError(t, err, "goqu: Unsupported update interface type []string") -} - -func (me *datasetTest) TestUpdateSqlWithSkipupdateTag() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address" goqu:"skipupdate"` - Name string `db:"name"` - } - sql, _, err := ds1.ToUpdateSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "name"='Test'`) -} - -func (me *datasetTest) TestUpdateSqlWithWhere() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - sql, _, err := ds1.Where(I("name").IsNull()).ToUpdateSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test' WHERE ("name" IS NULL)`) - - sql, _, err = ds1.Where(I("name").IsNull()).ToUpdateSql(Record{"name": "Test", "address": "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test' WHERE ("name" IS NULL)`) -} - -func (me *datasetTest) TestUpdateSqlWithReturning() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - sql, _, err := ds1.Returning(I("items").All()).ToUpdateSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test' RETURNING "items".*`) - - sql, _, err = ds1.Where(I("name").IsNull()).Returning(Literal(`"items".*`)).ToUpdateSql(Record{"name": "Test", "address": "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, sql, `UPDATE "items" SET "address"='111 Test Addr',"name"='Test' WHERE ("name" IS NULL) RETURNING "items".*`) -} - -func (me *datasetTest) TestPreparedUpdateSqlWithStructs() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - sql, args, err := ds1.Prepared(true).ToUpdateSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `UPDATE "items" SET "address"=?,"name"=?`) -} - -func (me *datasetTest) TestPreparedUpdateSqlWithMaps() { - t := me.T() - ds1 := From("items") - sql, args, err := ds1.Prepared(true).ToUpdateSql(Record{"name": "Test", "address": "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `UPDATE "items" SET "address"=?,"name"=?`) - -} - -func (me *datasetTest) TestPreparedUpdateSqlWithByteSlice() { - t := me.T() - ds1 := From("items") - type item struct { - Name string `db:"name"` - Data []byte `db:"data"` - } - sql, args, err := ds1.Returning(I("items").All()).Prepared(true).ToUpdateSql(item{Name: "Test", Data: []byte(`{"someJson":"data"}`)}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"Test", []byte(`{"someJson":"data"}`)}) - assert.Equal(t, sql, `UPDATE "items" SET "name"=?,"data"=? RETURNING "items".*`) -} - -func (me *datasetTest) TestPreparedUpdateSqlWithCustomValuer() { - t := me.T() - ds1 := From("items") - type item struct { - Name string `db:"name"` - Data valuerType `db:"data"` - } - sql, args, err := ds1.Returning(I("items").All()).Prepared(true).ToUpdateSql(item{Name: "Test", Data: []byte(`Hello`)}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"Test", []byte("Hello World")}) - assert.Equal(t, sql, `UPDATE "items" SET "name"=?,"data"=? RETURNING "items".*`) -} - -func (me *datasetTest) TestPreparedUpdateSqlWithValuer() { - t := me.T() - ds1 := From("items") - type item struct { - Name string `db:"name"` - Data sql.NullString `db:"data"` - } - sql, args, err := ds1.Returning(I("items").All()).Prepared(true).ToUpdateSql(item{Name: "Test", Data: sql.NullString{String: "Hello World", Valid: true}}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"Test", "Hello World"}) - assert.Equal(t, sql, `UPDATE "items" SET "name"=?,"data"=? RETURNING "items".*`) -} - -func (me *datasetTest) TestPreparedUpdateSqlWithSkipupdateTag() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address" goqu:"skipupdate"` - Name string `db:"name"` - } - sql, args, err := ds1.Prepared(true).ToUpdateSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"Test"}) - assert.Equal(t, sql, `UPDATE "items" SET "name"=?`) -} - -func (me *datasetTest) TestPreparedUpdateSqlWithEmbeddedStruct() { - t := me.T() - ds1 := From("items") - type Phone struct { - Primary string `db:"primary_phone"` - Home string `db:"home_phone"` - Created time.Time `db:"phone_created"` - } - type item struct { - Phone - Address string `db:"address" goqu:"skipupdate"` - Name string `db:"name"` - Created time.Time `db:"created"` - NilPointer interface{} `db:"nil_pointer"` - } - created, _ := time.Parse("2006-01-02", "2015-01-01") - - sql, args, err := ds1.Prepared(true).ToUpdateSql(item{Name: "Test", Address: "111 Test Addr", Created: created, Phone: Phone{ - Home: "123123", - Primary: "456456", - Created: created, - }}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"456456", "123123", created, "Test", created}) - assert.Equal(t, sql, `UPDATE "items" SET "primary_phone"=?,"home_phone"=?,"phone_created"=?,"name"=?,"created"=?,"nil_pointer"=NULL`) -} - -func (me *datasetTest) TestPreparedUpdateSqlWithEmbeddedStructPtr() { - t := me.T() - ds1 := From("items") - type Phone struct { - Primary string `db:"primary_phone"` - Home string `db:"home_phone"` - Created time.Time `db:"phone_created"` - } - type item struct { - *Phone - Address string `db:"address" goqu:"skipupdate"` - Name string `db:"name"` - Created time.Time `db:"created"` - } - created, _ := time.Parse("2006-01-02", "2015-01-01") - - sql, args, err := ds1.Prepared(true).ToUpdateSql(item{Name: "Test", Address: "111 Test Addr", Created: created, Phone: &Phone{ - Home: "123123", - Primary: "456456", - Created: created, - }}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"456456", "123123", created, "Test", created}) - assert.Equal(t, sql, `UPDATE "items" SET "primary_phone"=?,"home_phone"=?,"phone_created"=?,"name"=?,"created"=?`) -} - -func (me *datasetTest) TestPreparedUpdateSqlWithWhere() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - sql, args, err := ds1.Where(I("name").IsNull()).Prepared(true).ToUpdateSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `UPDATE "items" SET "address"=?,"name"=? WHERE ("name" IS NULL)`) - - sql, args, err = ds1.Where(I("name").IsNull()).Prepared(true).ToUpdateSql(Record{"name": "Test", "address": "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `UPDATE "items" SET "address"=?,"name"=? WHERE ("name" IS NULL)`) -} - -func (me *datasetTest) TestPreparedUpdateSqlWithReturning() { - t := me.T() - ds1 := From("items") - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - sql, args, err := ds1.Returning(I("items").All()).Prepared(true).ToUpdateSql(item{Name: "Test", Address: "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `UPDATE "items" SET "address"=?,"name"=? RETURNING "items".*`) - - sql, args, err = ds1.Where(I("name").IsNull()).Returning(Literal(`"items".*`)).Prepared(true).ToUpdateSql(Record{"name": "Test", "address": "111 Test Addr"}) - assert.NoError(t, err) - assert.Equal(t, args, []interface{}{"111 Test Addr", "Test"}) - assert.Equal(t, sql, `UPDATE "items" SET "address"=?,"name"=? WHERE ("name" IS NULL) RETURNING "items".*`) -} diff --git a/default_adapter.go b/default_adapter.go deleted file mode 100644 index b69f9254..00000000 --- a/default_adapter.go +++ /dev/null @@ -1,1123 +0,0 @@ -package goqu - -import ( - "fmt" - "reflect" - "strconv" - "strings" - "time" - "unicode/utf8" -) - -var ( - replacement_rune = '?' - comma_rune = ',' - space_rune = ' ' - left_paren_rune = '(' - right_paren_rune = ')' - star_rune = '*' - default_quote = '"' - period_rune = '.' - empty_string = "" - default_null = []byte("NULL") - default_true = []byte("TRUE") - default_false = []byte("FALSE") - default_update_clause = []byte("UPDATE") - default_insert_clause = []byte("INSERT INTO") - default_select_clause = []byte("SELECT") - default_delete_clause = []byte("DELETE") - default_truncate_clause = []byte("TRUNCATE") - default_with_fragment = []byte("WITH ") - default_recursive_fragment = []byte("RECURSIVE ") - default_cascade_fragment = []byte(" CASCADE") - default_retrict_fragment = []byte(" RESTRICT") - default_default_values_fragment = []byte(" DEFAULT VALUES") - default_values_fragment = []byte(" VALUES ") - default_identity_fragment = []byte(" IDENTITY") - default_set_fragment = []byte(" SET ") - default_distinct_fragment = []byte(" DISTINCT ") - default_returning_fragment = []byte(" RETURNING ") - default_from_fragment = []byte(" FROM") - default_where_fragment = []byte(" WHERE ") - default_group_by_fragment = []byte(" GROUP BY ") - default_having_fragment = []byte(" HAVING ") - default_order_by_fragment = []byte(" ORDER BY ") - default_limit_fragment = []byte(" LIMIT ") - default_offset_fragment = []byte(" OFFSET ") - default_for_update_fragment = []byte(" FOR UPDATE ") - default_for_no_key_update_fragment = []byte(" FOR NO KEY UPDATE ") - default_for_share_fragment = []byte(" FOR SHARE ") - default_for_key_share_fragment = []byte(" FOR KEY SHARE ") - default_nowait_fragment = []byte("NOWAIT") - default_skip_locked_fragment = []byte("SKIP LOCKED") - default_as_fragment = []byte(" AS ") - default_asc_fragment = []byte(" ASC") - default_desc_fragment = []byte(" DESC") - default_nulls_first_fragment = []byte(" NULLS FIRST") - default_nulls_last_fragment = []byte(" NULLS LAST") - default_and_fragment = []byte(" AND ") - default_or_fragment = []byte(" OR ") - default_union_fragment = []byte(" UNION ") - default_union_all_fragment = []byte(" UNION ALL ") - default_intersect_fragment = []byte(" INTERSECT ") - default_intersect_all_fragment = []byte(" INTERSECT ALL ") - default_set_operator_rune = '=' - default_string_quote_rune = '\'' - default_place_holder_rune = '?' - default_operator_lookup = map[BooleanOperation][]byte{ - EQ_OP: []byte("="), - NEQ_OP: []byte("!="), - GT_OP: []byte(">"), - GTE_OP: []byte(">="), - LT_OP: []byte("<"), - LTE_OP: []byte("<="), - IN_OP: []byte("IN"), - NOT_IN_OP: []byte("NOT IN"), - IS_OP: []byte("IS"), - IS_NOT_OP: []byte("IS NOT"), - LIKE_OP: []byte("LIKE"), - NOT_LIKE_OP: []byte("NOT LIKE"), - I_LIKE_OP: []byte("ILIKE"), - NOT_I_LIKE_OP: []byte("NOT ILIKE"), - REGEXP_LIKE_OP: []byte("~"), - REGEXP_NOT_LIKE_OP: []byte("!~"), - REGEXP_I_LIKE_OP: []byte("~*"), - REGEXP_NOT_I_LIKE_OP: []byte("!~*"), - } - default_rangeop_lookup = map[RangeOperation][]byte{ - BETWEEN_OP: []byte("BETWEEN"), - NBETWEEN_OP: []byte("NOT BETWEEN"), - } - default_join_lookup = map[JoinType][]byte{ - INNER_JOIN: []byte(" INNER JOIN "), - FULL_OUTER_JOIN: []byte(" FULL OUTER JOIN "), - RIGHT_OUTER_JOIN: []byte(" RIGHT OUTER JOIN "), - LEFT_OUTER_JOIN: []byte(" LEFT OUTER JOIN "), - FULL_JOIN: []byte(" FULL JOIN "), - RIGHT_JOIN: []byte(" RIGHT JOIN "), - LEFT_JOIN: []byte(" LEFT JOIN "), - NATURAL_JOIN: []byte(" NATURAL JOIN "), - NATURAL_LEFT_JOIN: []byte(" NATURAL LEFT JOIN "), - NATURAL_RIGHT_JOIN: []byte(" NATURAL RIGHT JOIN "), - NATURAL_FULL_JOIN: []byte(" NATURAL FULL JOIN "), - CROSS_JOIN: []byte(" CROSS JOIN "), - } - default_escape_runes = map[rune][]byte{ - '\'': []byte("''"), - } - default_conflict_fragment = []byte(" ON CONFLICT") - default_conflict_nothing_fragment = []byte(" DO NOTHING") - default_conflict_update_fragment = []byte(" DO UPDATE SET ") -) - -type ( - //The default adapter. This class should be used when building a new adapter. When creating a new adapter you can either override methods, or more typically update default values. See (github.com/doug-martin/goqu/adapters/postgres) - DefaultAdapter struct { - Adapter - dataset *Dataset - //The UPDATE fragment to use when generating sql. (DEFAULT=[]byte("UPDATE")) - UpdateClause []byte - //The INSERT fragment to use when generating sql. (DEFAULT=[]byte("INSERT INTO")) - InsertClause []byte - //The INSERT IGNORE INTO fragment to use when generating sql. (DEFAULT=[]byte("INSERT INTO")) - InsertIgnoreClause []byte - //The SELECT fragment to use when generating sql. (DEFAULT=[]byte("SELECT")) - SelectClause []byte - //The DELETE fragment to use when generating sql. (DEFAULT=[]byte("DELETE")) - DeleteClause []byte - //The TRUNCATE fragment to use when generating sql. (DEFAULT=[]byte("TRUNCATE")) - TruncateClause []byte - //The WITH fragment to use when generating sql. (DEFAULT=[]byte("WITH ")) - WithFragment []byte - //The RECURSIVE fragment to use when generating sql (after WITH). (DEFAULT=[]byte("RECURSIVE ")) - RecursiveFragment []byte - //The CASCADE fragment to use when generating sql. (DEFAULT=[]byte(" CASCADE")) - CascadeFragment []byte - //The RESTRICT fragment to use when generating sql. (DEFAULT=[]byte(" RESTRICT")) - RestrictFragment []byte - //The SQL fragment to use when generating insert sql and using DEFAULT VALUES (e.g. postgres="DEFAULT VALUES", mysql="", sqlite3=""). (DEFAULT=[]byte(" DEFAULT VALUES")) - DefaultValuesFragment []byte - //The SQL fragment to use when generating insert sql and listing columns using a VALUES clause (DEFAULT=[]byte(" VALUES ")) - ValuesFragment []byte - //The SQL fragment to use when generating truncate sql and using the IDENTITY clause (DEFAULT=[]byte(" IDENTITY")) - IdentityFragment []byte - //The SQL fragment to use when generating update sql and using the SET clause (DEFAULT=[]byte(" SET ")) - SetFragment []byte - //The SQL DISTINCT keyword (DEFAULT=[]byte(" DISTINCT ")) - DistinctFragment []byte - //The SQL RETURNING clause (DEFAULT=[]byte(" RETURNING ")) - ReturningFragment []byte - //The SQL FROM clause fragment (DEFAULT=[]byte(" FROM")) - FromFragment []byte - //The SQL WHERE clause fragment (DEFAULT=[]byte(" WHERE")) - WhereFragment []byte - //The SQL GROUP BY clause fragment(DEFAULT=[]byte(" GROUP BY ")) - GroupByFragment []byte - //The SQL HAVING clause fragment(DELiFAULT=[]byte(" HAVING ")) - HavingFragment []byte - //The SQL ORDER BY clause fragment(DEFAULT=[]byte(" ORDER BY ")) - OrderByFragment []byte - //The SQL LIMIT BY clause fragment(DEFAULT=[]byte(" LIMIT ")) - LimitFragment []byte - //The SQL OFFSET BY clause fragment(DEFAULT=[]byte(" OFFSET ")) - OffsetFragment []byte - // The SQL FOR UPDATE fragment(DEFAULT=[]byte(" FOR UPDATE ")) - ForUpdateFragment []byte - // The SQL FOR NO KEY UPDATE fragment(DEFAULT=[]byte(" FOR NO KEY UPDATE ")) - ForNoKeyUpdateFragment []byte - // The SQL FOR SHARE fragment(DEFAULT=[]byte(" FOR SHARE ")) - ForShareFragment []byte - // The SQL FOR KEY SHARE fragment(DEFAULT=[]byte(" FOR KEY SHARE ")) - ForKeyShareFragment []byte - // The SQL NOWAIT fragment(DEFAULT=[]byte("NOWAIT")) - NowaitFragment []byte - // The SQL SKIP LOCKED fragment(DEFAULT=[]byte("SKIP LOCKED")) - SkipLockedFragment []byte - //The SQL AS fragment when aliasing an Expression(DEFAULT=[]byte(" AS ")) - AsFragment []byte - //The quote rune to use when quoting identifiers(DEFAULT='"') - QuoteRune rune - //The NULL literal to use when interpolating nulls values (DEFAULT=[]byte("NULL")) - Null []byte - //The TRUE literal to use when interpolating bool true values (DEFAULT=[]byte("TRUE")) - True []byte - //The FALSE literal to use when interpolating bool false values (DEFAULT=[]byte("FALSE")) - False []byte - //The ASC fragment when specifying column order (DEFAULT=[]byte(" ASC")) - AscFragment []byte - //The DESC fragment when specifying column order (DEFAULT=[]byte(" DESC")) - DescFragment []byte - //The NULLS FIRST fragment when specifying column order (DEFAULT=[]byte(" NULLS FIRST")) - NullsFirstFragment []byte - //The NULLS LAST fragment when specifying column order (DEFAULT=[]byte(" NULLS LAST")) - NullsLastFragment []byte - //The AND keyword used when joining ExpressionLists (DEFAULT=[]byte(" AND ")) - AndFragment []byte - //The OR keyword used when joining ExpressionLists (DEFAULT=[]byte(" OR ")) - OrFragment []byte - //The UNION keyword used when creating compound statements (DEFAULT=[]byte(" UNION ")) - UnionFragment []byte - //The UNION ALL keyword used when creating compound statements (DEFAULT=[]byte(" UNION ALL ")) - UnionAllFragment []byte - //The INTERSECT keyword used when creating compound statements (DEFAULT=[]byte(" INTERSECT ")) - IntersectFragment []byte - //The INTERSECT ALL keyword used when creating compound statements (DEFAULT=[]byte(" INTERSECT ALL ")) - IntersectAllFragment []byte - //The quote rune to use when quoting string literals (DEFAULT='\'') - StringQuote rune - //The operator to use when setting values in an update statement (DEFAULT='=') - SetOperatorRune rune - //The placeholder rune to use when generating a non interpolated statement (DEFAULT='?') - PlaceHolderRune rune - //Set to true to include positional argument numbers when creating a prepared statement - IncludePlaceholderNum bool - //The time format to use when serializing time.Time (DEFAULT=time.RFC3339Nano) - TimeFormat string - //A map used to look up BooleanOperations and their SQL equivalents - BooleanOperatorLookup map[BooleanOperation][]byte - //A map used to look up RangeOperations and their SQL equivalents - RangeOperatorLookup map[RangeOperation][]byte - //A map used to look up JoinTypes and their SQL equivalents - JoinTypeLookup map[JoinType][]byte - //Whether or not to use literal TRUE or FALSE for IS statements (e.g. IS TRUE or IS 0) - UseLiteralIsBools bool - //EscapedRunes is a map of a rune and the corresponding escape sequence in bytes. Used when escaping text types. - EscapedRunes map[rune][]byte - - ConflictFragment []byte - ConflictDoNothingFragment []byte - ConflictDoUpdateFragment []byte - ConflictTargetSupported bool - ConflictUpdateWhereSupported bool - InsertIgnoreSyntaxSupported bool - WithCTESupported bool - WithCTERecursiveSupported bool - } -) - -func NewDefaultAdapter(ds *Dataset) Adapter { - return &DefaultAdapter{ - dataset: ds, - UpdateClause: default_update_clause, - InsertClause: default_insert_clause, - SelectClause: default_select_clause, - DeleteClause: default_delete_clause, - TruncateClause: default_truncate_clause, - WithFragment: default_with_fragment, - RecursiveFragment: default_recursive_fragment, - CascadeFragment: default_cascade_fragment, - RestrictFragment: default_retrict_fragment, - DefaultValuesFragment: default_default_values_fragment, - ValuesFragment: default_values_fragment, - IdentityFragment: default_identity_fragment, - SetFragment: default_set_fragment, - DistinctFragment: default_distinct_fragment, - ReturningFragment: default_returning_fragment, - FromFragment: default_from_fragment, - WhereFragment: default_where_fragment, - GroupByFragment: default_group_by_fragment, - HavingFragment: default_having_fragment, - OrderByFragment: default_order_by_fragment, - LimitFragment: default_limit_fragment, - OffsetFragment: default_offset_fragment, - ForUpdateFragment: default_for_update_fragment, - ForNoKeyUpdateFragment: default_for_no_key_update_fragment, - ForShareFragment: default_for_share_fragment, - ForKeyShareFragment: default_for_key_share_fragment, - NowaitFragment: default_nowait_fragment, - SkipLockedFragment: default_skip_locked_fragment, - AsFragment: default_as_fragment, - QuoteRune: default_quote, - Null: default_null, - True: default_true, - False: default_false, - StringQuote: default_string_quote_rune, - AscFragment: default_asc_fragment, - DescFragment: default_desc_fragment, - NullsFirstFragment: default_nulls_first_fragment, - NullsLastFragment: default_nulls_last_fragment, - AndFragment: default_and_fragment, - OrFragment: default_or_fragment, - SetOperatorRune: default_set_operator_rune, - UnionFragment: default_union_fragment, - UnionAllFragment: default_union_all_fragment, - IntersectFragment: default_intersect_fragment, - IntersectAllFragment: default_intersect_all_fragment, - PlaceHolderRune: default_place_holder_rune, - BooleanOperatorLookup: default_operator_lookup, - RangeOperatorLookup: default_rangeop_lookup, - JoinTypeLookup: default_join_lookup, - TimeFormat: time.RFC3339Nano, - UseLiteralIsBools: true, - EscapedRunes: default_escape_runes, - ConflictFragment: default_conflict_fragment, - ConflictDoUpdateFragment: default_conflict_update_fragment, - ConflictDoNothingFragment: default_conflict_nothing_fragment, - ConflictUpdateWhereSupported: true, - InsertIgnoreSyntaxSupported: false, - ConflictTargetSupported: true, - WithCTESupported: true, - WithCTERecursiveSupported: true, - } -} - -//Override to prevent return statements from being generated when creating SQL -func (me *DefaultAdapter) SupportsReturn() bool { - return true -} - -//Override to allow LIMIT on DELETE statements -func (me *DefaultAdapter) SupportsLimitOnDelete() bool { - return false -} - -//Override to allow LIMIT on UPDATE statements -func (me *DefaultAdapter) SupportsLimitOnUpdate() bool { - return false -} - -//Override to allow ORDER BY on DELETE statements -func (me *DefaultAdapter) SupportsOrderByOnDelete() bool { - return false -} - -//Override to allow ORDER BY on UPDATE statements -func (me *DefaultAdapter) SupportsConflictUpdateWhere() bool { - return me.ConflictUpdateWhereSupported -} - -//Override to allow ORDER BY on UPDATE statements -func (me *DefaultAdapter) SupportsConflictTarget() bool { - return me.ConflictTargetSupported -} - -//Override to allow ORDER BY on UPDATE statements -func (me *DefaultAdapter) SupportsOrderByOnUpdate() bool { - return false -} - -func (me *DefaultAdapter) SupportsInsertIgnoreSyntax() bool { - return me.InsertIgnoreSyntaxSupported -} - -func (me *DefaultAdapter) SupportConflictUpdateWhere() bool { - return me.ConflictUpdateWhereSupported -} - -func (me *DefaultAdapter) SupportsWithCTE() bool { - return me.WithCTESupported -} - -func (me *DefaultAdapter) SupportsWithRecursiveCTE() bool { - return me.WithCTERecursiveSupported -} - -//This is a proxy to Dataset.Literal. Used internally to ensure the correct method is called on any subclasses and to prevent duplication of code -func (me *DefaultAdapter) Literal(buf *SqlBuilder, val interface{}) error { - return me.dataset.Literal(buf, val) -} - -//Generates a placeholder (e.g. ?, $1) -func (me *DefaultAdapter) PlaceHolderSql(buf *SqlBuilder, i interface{}) error { - buf.WriteRune(me.PlaceHolderRune) - if me.IncludePlaceholderNum { - buf.WriteString(strconv.FormatInt(int64(buf.CurrentArgPosition), 10)) - } - buf.WriteArg(i) - return nil -} - -//Adds the correct fragment to being an UPDATE statement -func (me *DefaultAdapter) UpdateBeginSql(buf *SqlBuilder) error { - buf.Write(me.UpdateClause) - return nil -} - -//Adds the correct fragment to being an INSERT statement -func (me *DefaultAdapter) InsertBeginSql(buf *SqlBuilder, o ConflictExpression) error { - if me.SupportsInsertIgnoreSyntax() && o != nil { - buf.Write(me.InsertIgnoreClause) - } else { - buf.Write(me.InsertClause) - } - return nil -} - -//Adds the correct fragment to being an DELETE statement -func (me *DefaultAdapter) DeleteBeginSql(buf *SqlBuilder) error { - buf.Write(me.DeleteClause) - return nil -} - -//Generates a TRUNCATE statement -func (me *DefaultAdapter) TruncateSql(buf *SqlBuilder, from ColumnList, opts TruncateOptions) error { - buf.Write(me.TruncateClause) - if err := me.SourcesSql(buf, from); err != nil { - return err - } - if opts.Identity != empty_string { - buf.WriteRune(space_rune) - buf.WriteString(strings.ToUpper(opts.Identity)) - buf.Write(me.IdentityFragment) - } - if opts.Cascade { - buf.Write(me.CascadeFragment) - } else if opts.Restrict { - buf.Write(me.RestrictFragment) - } - return nil -} - -//Adds the DefaultValuesFragment to an SQL statement -func (me *DefaultAdapter) DefaultValuesSql(buf *SqlBuilder) error { - buf.Write(me.DefaultValuesFragment) - return nil -} - -//Adds the columns list to an insert statement -func (me *DefaultAdapter) InsertColumnsSql(buf *SqlBuilder, cols ColumnList) error { - buf.WriteRune(space_rune) - buf.WriteRune(left_paren_rune) - if err := me.Literal(buf, cols); err != nil { - return err - } - buf.WriteRune(right_paren_rune) - return nil -} - -//Adds the values clause to an SQL statement -func (me *DefaultAdapter) InsertValuesSql(buf *SqlBuilder, values [][]interface{}) error { - buf.Write(me.ValuesFragment) - rowLen := len(values[0]) - valueLen := len(values) - for i, row := range values { - if len(row) != rowLen { - return fmt.Errorf("Rows with different value length expected %d got %d", rowLen, len(row)) - } - if err := me.Literal(buf, row); err != nil { - return err - } - if i < valueLen-1 { - buf.WriteRune(comma_rune) - buf.WriteRune(space_rune) - } - } - return nil -} - -//Adds the DefaultValuesFragment to an SQL statement -func (me *DefaultAdapter) OnConflictSql(buf *SqlBuilder, o ConflictExpression) error { - if o == nil { - return nil - } - buf.Write(me.ConflictFragment) - if u := o.Updates(); u != nil { - target := u.Target - if me.SupportsConflictTarget() && target != "" { - wrapParens := !strings.HasPrefix(strings.ToLower(target), "on constraint") - - buf.Write([]byte(" ")) - if wrapParens { - buf.Write([]byte("(")) - } - buf.Write([]byte(target)) - if wrapParens { - buf.Write([]byte(")")) - } - } - if err := me.onConflictDoUpdateSql(buf, *u); err != nil { - return err - } - } else { - buf.Write(me.ConflictDoNothingFragment) - } - return nil -} - -func (me *DefaultAdapter) onConflictDoUpdateSql(buf *SqlBuilder, o ConflictUpdate) error { - buf.Write(me.ConflictDoUpdateFragment) - update := o.Update - if update == nil { - return NewGoquError("Values are required") - } - exp, err := me.dataset.getUpdateExpressions(update) - if err != nil { - return err - } - if err := me.updateValuesSql(buf, exp...); err != nil { - return err - } - if o.WhereClause != nil { - if !me.SupportsConflictUpdateWhere() { - return NewGoquError("Adapter does not support upsert with where clause") - } - - if err := me.WhereSql(buf, o.WhereClause); err != nil { - return err - } - } - return nil -} - -//Adds column setters in an update SET clause -func (me *DefaultAdapter) UpdateExpressionsSql(buf *SqlBuilder, updates ...UpdateExpression) error { - buf.Write(me.SetFragment) - return me.updateValuesSql(buf, updates...) - -} - -//Adds column setters in an update SET clause -func (me *DefaultAdapter) updateValuesSql(buf *SqlBuilder, updates ...UpdateExpression) error { - if len(updates) == 0 { - return NewGoquError("No update values provided") - } - updateLen := len(updates) - for i, update := range updates { - if err := me.Literal(buf, update); err != nil { - return err - } - if i < updateLen-1 { - buf.WriteRune(comma_rune) - } - } - return nil - -} - -//Adds the SELECT clause and columns to a sql statement -func (me *DefaultAdapter) SelectSql(buf *SqlBuilder, cols ColumnList) error { - buf.Write(me.SelectClause) - buf.WriteRune(space_rune) - if len(cols.Columns()) == 0 { - buf.WriteRune(star_rune) - } else { - return me.Literal(buf, cols) - } - return nil -} - -//Adds the SELECT DISTINCT clause and columns to a sql statement -func (me *DefaultAdapter) SelectDistinctSql(buf *SqlBuilder, cols ColumnList) error { - buf.Write(me.SelectClause) - buf.Write(me.DistinctFragment) - return me.Literal(buf, cols) -} - -func (me *DefaultAdapter) ReturningSql(buf *SqlBuilder, returns ColumnList) error { - if returns != nil && len(returns.Columns()) > 0 { - buf.Write(me.ReturningFragment) - return me.Literal(buf, returns) - } - return nil -} - -//Adds the FROM clause and tables to an sql statement -func (me *DefaultAdapter) FromSql(buf *SqlBuilder, from ColumnList) error { - if from != nil && len(from.Columns()) > 0 { - buf.Write(me.FromFragment) - return me.SourcesSql(buf, from) - } - return nil -} - -//Adds the generates the SQL for a column list -func (me *DefaultAdapter) SourcesSql(buf *SqlBuilder, from ColumnList) error { - buf.WriteRune(space_rune) - return me.Literal(buf, from) -} - -//Generates the JOIN clauses for an SQL statement -func (me *DefaultAdapter) JoinSql(buf *SqlBuilder, joins JoiningClauses) error { - if len(joins) > 0 { - for _, j := range joins { - joinType := me.JoinTypeLookup[j.JoinType] - buf.Write(joinType) - if err := me.Literal(buf, j.Table); err != nil { - return err - } - if j.IsConditioned { - buf.WriteRune(space_rune) - if j.Condition == nil { - return NewGoquError("Join condition required for conditioned join %s", string(joinType)) - } - condition := j.Condition - if condition.JoinCondition() == USING_COND { - buf.WriteString("USING ") - } else { - buf.WriteString("ON ") - } - switch condition.(type) { - case JoinOnExpression: - if err := me.Literal(buf, condition.(JoinOnExpression).On()); err != nil { - return err - } - case JoinUsingExpression: - buf.WriteRune(left_paren_rune) - if err := me.Literal(buf, condition.(JoinUsingExpression).Using()); err != nil { - return err - } - buf.WriteRune(right_paren_rune) - } - } - } - } - return nil -} - -//Generates the WHERE clause for an SQL statement -func (me *DefaultAdapter) WhereSql(buf *SqlBuilder, where ExpressionList) error { - if where != nil && len(where.Expressions()) > 0 { - buf.Write(me.WhereFragment) - return me.Literal(buf, where) - } - return nil -} - -//Generates the GROUP BY clause for an SQL statement -func (me *DefaultAdapter) GroupBySql(buf *SqlBuilder, groupBy ColumnList) error { - if groupBy != nil && len(groupBy.Columns()) > 0 { - buf.Write(me.GroupByFragment) - return me.Literal(buf, groupBy) - } - return nil -} - -//Generates the HAVING clause for an SQL statement -func (me *DefaultAdapter) HavingSql(buf *SqlBuilder, having ExpressionList) error { - if having != nil && len(having.Expressions()) > 0 { - buf.Write(me.HavingFragment) - return me.Literal(buf, having) - } - return nil -} - -//Generates the sql for the WITH clauses for common table expressions (CTE) -func (me *DefaultAdapter) CommonTablesSql(buf *SqlBuilder, ctes []CommonTableExpression) error { - if l := len(ctes); l > 0 { - if !me.SupportsWithCTE() { - return NewGoquError("Adapter does not support CTE with clause") - } - buf.Write(me.WithFragment) - anyRecursive := false - for _, cte := range ctes { - anyRecursive = anyRecursive || cte.IsRecursive() - } - if anyRecursive { - if !me.SupportsWithRecursiveCTE() { - return NewGoquError("Adapter does not support CTE with recursive clause") - } - buf.Write(me.RecursiveFragment) - } - for i, cte := range ctes { - if err := me.Literal(buf, cte); err != nil { - return err - } - if i < l-1 { - buf.WriteRune(comma_rune) - buf.WriteRune(space_rune) - } - } - buf.WriteRune(space_rune) - } - return nil -} - -//Generates the compound sql clause for an SQL statement (e.g. UNION, INTERSECT) -func (me *DefaultAdapter) CompoundsSql(buf *SqlBuilder, compounds []CompoundExpression) error { - for _, compound := range compounds { - if err := me.Literal(buf, compound); err != nil { - return err - } - } - return nil -} - -//Generates the ORDER BY clause for an SQL statement -func (me *DefaultAdapter) OrderSql(buf *SqlBuilder, order ColumnList) error { - if order != nil && len(order.Columns()) > 0 { - buf.Write(me.OrderByFragment) - return me.Literal(buf, order) - } - return nil -} - -//Generates the LIMIT clause for an SQL statement -func (me *DefaultAdapter) LimitSql(buf *SqlBuilder, limit interface{}) error { - if limit != nil { - buf.Write(me.LimitFragment) - return me.Literal(buf, limit) - } - return nil -} - -//Generates the OFFSET clause for an SQL statement -func (me *DefaultAdapter) OffsetSql(buf *SqlBuilder, offset uint) error { - if offset > 0 { - buf.Write(me.OffsetFragment) - return me.Literal(buf, offset) - } - return nil -} - -//Generates the FOR (aka "locking") clause for an SQL statement -func (me *DefaultAdapter) ForSql(buf *SqlBuilder, lockingClause Lock) error { - switch lockingClause.Strength { - case FOR_NOLOCK: - return nil - case FOR_UPDATE: - buf.Write(me.ForUpdateFragment) - case FOR_NO_KEY_UPDATE: - buf.Write(me.ForNoKeyUpdateFragment) - case FOR_SHARE: - buf.Write(me.ForShareFragment) - case FOR_KEY_SHARE: - buf.Write(me.ForKeyShareFragment) - } - // the WAIT case is the default in Postgres, and is what you get if you don't specify NOWAIT or - // SKIP LOCKED. There's no special syntax for it in PG, so we don't do anything for it here - switch lockingClause.WaitOption { - case NOWAIT: - buf.Write(me.NowaitFragment) - case SKIP_LOCKED: - buf.Write(me.SkipLockedFragment) - } - return nil -} - -//Generates creates the sql for a sub select on a Dataset -func (me *DefaultAdapter) DatasetSql(buf *SqlBuilder, dataset Dataset) error { - buf.WriteRune(left_paren_rune) - if buf.IsPrepared { - if err := dataset.selectSqlWriteTo(buf); err != nil { - return err - } - } else { - sql, _, err := dataset.Prepared(false).ToSql() - if err != nil { - return err - } - buf.WriteString(sql) - } - buf.WriteRune(right_paren_rune) - alias := dataset.GetClauses().Alias - if alias != nil { - buf.Write(me.AsFragment) - return me.Literal(buf, alias) - } - return nil -} - -//Quotes an identifier (e.g. "col", "table"."col" -func (me *DefaultAdapter) QuoteIdentifier(buf *SqlBuilder, ident IdentifierExpression) error { - schema, table, col := ident.GetSchema(), ident.GetTable(), ident.GetCol() - if schema != empty_string { - buf.WriteRune(me.QuoteRune) - buf.WriteString(schema) - buf.WriteRune(me.QuoteRune) - } - if table != empty_string { - if schema != empty_string { - buf.WriteRune(period_rune) - } - buf.WriteRune(me.QuoteRune) - buf.WriteString(table) - buf.WriteRune(me.QuoteRune) - } - switch col.(type) { - case nil: - case string: - if table != empty_string || schema != empty_string { - buf.WriteRune(period_rune) - } - buf.WriteRune(me.QuoteRune) - buf.WriteString(col.(string)) - buf.WriteRune(me.QuoteRune) - case LiteralExpression: - if table != empty_string || schema != empty_string { - buf.WriteRune(period_rune) - } - return me.Literal(buf, col) - default: - return NewGoquError("Unexpected col type must be string or LiteralExpression %+v", col) - } - return nil -} - -//Generates SQL NULL value -func (me *DefaultAdapter) LiteralNil(buf *SqlBuilder) error { - buf.Write(me.Null) - return nil -} - -//Generates SQL bool literal, (e.g. TRUE, FALSE, mysql 1, 0, sqlite3 1, 0) -func (me *DefaultAdapter) LiteralBool(buf *SqlBuilder, b bool) error { - if buf.IsPrepared { - return me.PlaceHolderSql(buf, b) - } - if b { - buf.Write(me.True) - } else { - buf.Write(me.False) - } - return nil -} - -//Generates SQL for a time.Time value -func (me *DefaultAdapter) LiteralTime(buf *SqlBuilder, t time.Time) error { - if buf.IsPrepared { - return me.PlaceHolderSql(buf, t) - } - return me.Literal(buf, t.UTC().Format(me.TimeFormat)) -} - -//Generates SQL for a Float Value -func (me *DefaultAdapter) LiteralFloat(buf *SqlBuilder, f float64) error { - if buf.IsPrepared { - return me.PlaceHolderSql(buf, f) - } - buf.WriteString(strconv.FormatFloat(f, 'f', -1, 64)) - return nil -} - -//Generates SQL for an int value -func (me *DefaultAdapter) LiteralInt(buf *SqlBuilder, i int64) error { - if buf.IsPrepared { - return me.PlaceHolderSql(buf, i) - } - buf.WriteString(strconv.FormatInt(i, 10)) - return nil -} - -//Generates SQL for a string -func (me *DefaultAdapter) LiteralString(buf *SqlBuilder, s string) error { - if buf.IsPrepared { - return me.PlaceHolderSql(buf, s) - } - buf.WriteRune(me.StringQuote) - for _, char := range s { - if e, ok := me.EscapedRunes[char]; ok { - buf.Write(e) - } else { - buf.WriteRune(char) - } - } - - buf.WriteRune(me.StringQuote) - return nil -} - -// Generates SQL for a slice of bytes -func (me *DefaultAdapter) LiteralBytes(buf *SqlBuilder, bs []byte) error { - if buf.IsPrepared { - return me.PlaceHolderSql(buf, bs) - } - buf.WriteRune(me.StringQuote) - i := 0 - for len(bs) > 0 { - char, l := utf8.DecodeRune(bs) - if e, ok := me.EscapedRunes[char]; ok { - buf.Write(e) - } else { - buf.WriteRune(char) - } - i++ - bs = bs[l:] - } - buf.WriteRune(me.StringQuote) - return nil -} - -//Generates SQL for a slice of values (e.g. []int64{1,2,3,4} -> (1,2,3,4) -func (me *DefaultAdapter) SliceValueSql(buf *SqlBuilder, slice reflect.Value) error { - buf.WriteRune(left_paren_rune) - for i, l := 0, slice.Len(); i < l; i++ { - if err := me.Literal(buf, slice.Index(i).Interface()); err != nil { - return err - } - if i < l-1 { - buf.WriteRune(comma_rune) - buf.WriteRune(space_rune) - } - } - buf.WriteRune(right_paren_rune) - return nil -} - -//Generates SQL for an AliasedExpression (e.g. I("a").As("b") -> "a" AS "b") -func (me *DefaultAdapter) AliasedExpressionSql(buf *SqlBuilder, aliased AliasedExpression) error { - if err := me.Literal(buf, aliased.Aliased()); err != nil { - return err - } - buf.Write(me.AsFragment) - return me.Literal(buf, aliased.GetAs()) -} - -//Generates SQL for a BooleanExpresion (e.g. I("a").Eq(2) -> "a" = 2) -func (me *DefaultAdapter) BooleanExpressionSql(buf *SqlBuilder, operator BooleanExpression) error { - buf.WriteRune(left_paren_rune) - if err := me.Literal(buf, operator.Lhs()); err != nil { - return err - } - buf.WriteRune(space_rune) - operatorOp := operator.Op() - if operator.Rhs() == nil { - switch operatorOp { - case EQ_OP: - operatorOp = IS_OP - case NEQ_OP: - operatorOp = IS_NOT_OP - } - } - if val, ok := me.BooleanOperatorLookup[operatorOp]; ok { - buf.Write(val) - } else { - return NewGoquError("Boolean operator %+v not supported", operatorOp) - } - rhs := operator.Rhs() - if (operatorOp == IS_OP || operatorOp == IS_NOT_OP) && me.UseLiteralIsBools { - if rhs == true { - rhs = L("TRUE") - } else if rhs == false { - rhs = L("FALSE") - } - } - buf.WriteRune(space_rune) - if err := me.Literal(buf, rhs); err != nil { - return err - } - buf.WriteRune(right_paren_rune) - return nil -} - -//Generates SQL for a RangeExpresion (e.g. I("a").Between(RangeVal{Start:2,End:5}) -> "a" BETWEEN 2 AND 5) -func (me *DefaultAdapter) RangeExpressionSql(buf *SqlBuilder, operator RangeExpression) error { - buf.WriteRune(left_paren_rune) - if err := me.Literal(buf, operator.Lhs()); err != nil { - return err - } - buf.WriteRune(space_rune) - operatorOp := operator.Op() - if val, ok := me.RangeOperatorLookup[operatorOp]; ok { - buf.Write(val) - } else { - return NewGoquError("Range operator %+v not supported", operatorOp) - } - rhs := operator.Rhs() - buf.WriteRune(space_rune) - if err := me.Literal(buf, rhs.Start); err != nil { - return err - } - buf.Write(default_and_fragment) - if err := me.Literal(buf, rhs.End); err != nil { - return err - } - buf.WriteRune(right_paren_rune) - return nil -} - -//Generates SQL for an OrderedExpression (e.g. I("a").Asc() -> "a" ASC) -func (me *DefaultAdapter) OrderedExpressionSql(buf *SqlBuilder, order OrderedExpression) error { - if err := me.Literal(buf, order.SortExpression()); err != nil { - return err - } - if order.Direction() == SORT_DESC { - buf.Write(me.DescFragment) - } else { - buf.Write(me.AscFragment) - } - switch order.NullSortType() { - case NULLS_FIRST: - buf.Write(me.NullsFirstFragment) - case NULLS_LAST: - buf.Write(me.NullsLastFragment) - } - return nil -} - -//Generates SQL for an ExpressionList (e.g. And(I("a").Eq("a"), I("b").Eq("b")) -> (("a" = 'a') AND ("b" = 'b'))) -func (me *DefaultAdapter) ExpressionListSql(buf *SqlBuilder, expressionList ExpressionList) error { - var op []byte - if expressionList.Type() == AND_TYPE { - op = me.AndFragment - } else { - op = me.OrFragment - } - exps := expressionList.Expressions() - expLen := len(exps) - 1 - needsAppending := expLen > 0 - if needsAppending { - buf.WriteRune(left_paren_rune) - } else { - return me.Literal(buf, exps[0]) - } - for i, exp := range exps { - if err := me.Literal(buf, exp); err != nil { - return err - } - if i < expLen { - buf.Write(op) - } - } - buf.WriteRune(right_paren_rune) - return nil -} - -//Generates SQL for a ColumnList -func (me *DefaultAdapter) ColumnListSql(buf *SqlBuilder, columnList ColumnList) error { - cols := columnList.Columns() - colLen := len(cols) - for i, col := range cols { - if err := me.Literal(buf, col); err != nil { - return err - } - if i < colLen-1 { - buf.WriteRune(comma_rune) - buf.WriteRune(space_rune) - } - } - return nil -} - -//Generates SQL for an UpdateEpxresion -func (me *DefaultAdapter) UpdateExpressionSql(buf *SqlBuilder, update UpdateExpression) error { - if err := me.Literal(buf, update.Col()); err != nil { - return err - } - buf.WriteRune(me.SetOperatorRune) - return me.Literal(buf, update.Val()) -} - -//Generates SQL for a LiteralExpression -// L("a + b") -> a + b -// L("a = ?", 1) -> a = 1 -func (me *DefaultAdapter) LiteralExpressionSql(buf *SqlBuilder, literal LiteralExpression) error { - lit := literal.Literal() - args := literal.Args() - argsLen := len(args) - if argsLen > 0 { - currIndex := 0 - for _, char := range lit { - if char == replacement_rune && currIndex < argsLen { - if err := me.Literal(buf, args[currIndex]); err != nil { - return err - } - currIndex++ - } else { - buf.WriteRune(char) - } - } - } else { - buf.WriteString(lit) - } - return nil -} - -//Generates SQL for a SqlFunctionExpression -// COUNT(I("a")) -> COUNT("a") -func (me *DefaultAdapter) SqlFunctionExpressionSql(buf *SqlBuilder, sqlFunc SqlFunctionExpression) error { - buf.WriteString(sqlFunc.Name()) - return me.Literal(buf, sqlFunc.Args()) -} - -//Generates SQL for a CastExpression -// I("a").Cast("NUMERIC") -> CAST("a" AS NUMERIC) -func (me *DefaultAdapter) CastExpressionSql(buf *SqlBuilder, cast CastExpression) error { - buf.WriteString("CAST") - buf.WriteRune(left_paren_rune) - if err := me.Literal(buf, cast.Casted()); err != nil { - return err - } - buf.Write(me.AsFragment) - if err := me.Literal(buf, cast.Type()); err != nil { - return err - } - buf.WriteRune(right_paren_rune) - return nil -} - -//Generates SQL for a CommonTableExpression -func (me *DefaultAdapter) CommonTableExpressionSql(buf *SqlBuilder, cte CommonTableExpression) error { - if err := me.Literal(buf, cte.Name()); err != nil { - return err - } - buf.Write(me.AsFragment) - if err := me.Literal(buf, cte.SubQuery()); err != nil { - return err - } - return nil -} - -//Generates SQL for a CompoundExpression -func (me *DefaultAdapter) CompoundExpressionSql(buf *SqlBuilder, compound CompoundExpression) error { - switch compound.Type() { - case UNION: - buf.Write(me.UnionFragment) - case UNION_ALL: - buf.Write(me.UnionAllFragment) - case INTERSECT: - buf.Write(me.IntersectFragment) - case INTERSECT_ALL: - buf.Write(me.IntersectAllFragment) - } - return me.Literal(buf, compound.Rhs()) -} - -func (me *DefaultAdapter) ExpressionMapSql(buf *SqlBuilder, ex Ex) error { - expressionList, err := ex.ToExpressions() - if err != nil { - return err - } - return me.Literal(buf, expressionList) -} - -func (me *DefaultAdapter) ExpressionOrMapSql(buf *SqlBuilder, ex ExOr) error { - expressionList, err := ex.ToExpressions() - if err != nil { - return err - } - return me.Literal(buf, expressionList) -} - -func init() { - RegisterAdapter("default", NewDefaultAdapter) -} diff --git a/dialect/mysql/mysql.go b/dialect/mysql/mysql.go new file mode 100644 index 00000000..fd65d037 --- /dev/null +++ b/dialect/mysql/mysql.go @@ -0,0 +1,67 @@ +package mysql + +import ( + "github.com/doug-martin/goqu/v7" + "github.com/doug-martin/goqu/v7/exp" +) + +func DialectOptions() *goqu.SQLDialectOptions { + opts := goqu.DefaultDialectOptions() + + opts.SupportsReturn = false + opts.SupportsOrderByOnUpdate = true + opts.SupportsLimitOnUpdate = true + opts.SupportsLimitOnDelete = true + opts.SupportsOrderByOnDelete = true + opts.SupportsConflictUpdateWhere = false + opts.SupportsInsertIgnoreSyntax = true + opts.SupportsConflictTarget = false + opts.SupportsWithCTE = false + opts.SupportsWithCTERecursive = false + + opts.PlaceHolderRune = '?' + opts.IncludePlaceholderNum = false + opts.QuoteRune = '`' + opts.DefaultValuesFragment = []byte("") + opts.True = []byte("1") + opts.False = []byte("0") + opts.TimeFormat = "2006-01-02 15:04:05" + opts.BooleanOperatorLookup = map[exp.BooleanOperation][]byte{ + exp.EqOp: []byte("="), + exp.NeqOp: []byte("!="), + exp.GtOp: []byte(">"), + exp.GteOp: []byte(">="), + exp.LtOp: []byte("<"), + exp.LteOp: []byte("<="), + exp.InOp: []byte("IN"), + exp.NotInOp: []byte("NOT IN"), + exp.IsOp: []byte("IS"), + exp.IsNotOp: []byte("IS NOT"), + exp.LikeOp: []byte("LIKE BINARY"), + exp.NotLikeOp: []byte("NOT LIKE BINARY"), + exp.ILikeOp: []byte("LIKE"), + exp.NotILikeOp: []byte("NOT LIKE"), + exp.RegexpLikeOp: []byte("REGEXP BINARY"), + exp.RegexpNotLikeOp: []byte("NOT REGEXP BINARY"), + exp.RegexpILikeOp: []byte("REGEXP"), + exp.RegexpNotILikeOp: []byte("NOT REGEXP"), + } + opts.EscapedRunes = map[rune][]byte{ + '\'': []byte("\\'"), + '"': []byte("\\\""), + '\\': []byte("\\\\"), + '\n': []byte("\\n"), + '\r': []byte("\\r"), + 0: []byte("\\x00"), + 0x1a: []byte("\\x1a"), + } + opts.InsertIgnoreClause = []byte("INSERT IGNORE INTO") + opts.ConflictFragment = []byte("") + opts.ConflictDoUpdateFragment = []byte(" ON DUPLICATE KEY UPDATE ") + opts.ConflictDoNothingFragment = []byte("") + return opts +} + +func init() { + goqu.RegisterDialect("mysql", DialectOptions()) +} diff --git a/dialect/mysql/mysql_dialect_test.go b/dialect/mysql/mysql_dialect_test.go new file mode 100644 index 00000000..944d7dc5 --- /dev/null +++ b/dialect/mysql/mysql_dialect_test.go @@ -0,0 +1,180 @@ +package mysql + +import ( + "regexp" + "testing" + + "github.com/doug-martin/goqu/v7" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type mysqlDialectSuite struct { + suite.Suite +} + +func (mds *mysqlDialectSuite) GetDs(table string) *goqu.Dataset { + return goqu.Dialect("mysql").From(table) +} + +func (mds *mysqlDialectSuite) TestIdentifiers() { + t := mds.T() + ds := mds.GetDs("test") + sql, _, err := ds.Select("a", + goqu.I("a.b.c"), + goqu.I("c.d"), + goqu.C("test").As("test"), + ).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT `a`, `a`.`b`.`c`, `c`.`d`, `test` AS `test` FROM `test`") +} + +func (mds *mysqlDialectSuite) TestLiteralString() { + t := mds.T() + ds := mds.GetDs("test") + col := goqu.C("a") + sql, _, err := ds.Where(col.Eq("test")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test')") + + sql, _, err = ds.Where(col.Eq("test'test")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\'test')") + + sql, _, err = ds.Where(col.Eq(`test"test`)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\"test')") + + sql, _, err = ds.Where(col.Eq(`test\test`)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\\test')") + + sql, _, err = ds.Where(col.Eq("test\ntest")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\ntest')") + + sql, _, err = ds.Where(col.Eq("test\rtest")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\rtest')") + + sql, _, err = ds.Where(col.Eq("test\x00test")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x00test')") + + sql, _, err = ds.Where(col.Eq("test\x1atest")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')") +} + +func (mds *mysqlDialectSuite) TestLiteralBytes() { + t := mds.T() + col := goqu.C("a") + ds := mds.GetDs("test") + sql, _, err := ds.Where(col.Eq([]byte("test"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test')") + + sql, _, err = ds.Where(col.Eq([]byte("test'test"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\'test')") + + sql, _, err = ds.Where(col.Eq([]byte(`test"test`))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\"test')") + + sql, _, err = ds.Where(col.Eq([]byte(`test\test`))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\\test')") + + sql, _, err = ds.Where(col.Eq([]byte("test\ntest"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\ntest')") + + sql, _, err = ds.Where(col.Eq([]byte("test\rtest"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\rtest')") + + sql, _, err = ds.Where(col.Eq([]byte("test\x00test"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x00test')") + + sql, _, err = ds.Where(col.Eq([]byte("test\x1atest"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')") +} + +func (mds *mysqlDialectSuite) TestBooleanOperations() { + t := mds.T() + col := goqu.C("a") + ds := mds.GetDs("test") + sql, _, err := ds.Where(col.Eq(true)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS TRUE)") + sql, _, err = ds.Where(col.Eq(false)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS FALSE)") + sql, _, err = ds.Where(col.Is(true)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS TRUE)") + sql, _, err = ds.Where(col.Is(false)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS FALSE)") + sql, _, err = ds.Where(col.IsTrue()).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS TRUE)") + sql, _, err = ds.Where(col.IsFalse()).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS FALSE)") + + sql, _, err = ds.Where(col.Neq(true)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT TRUE)") + sql, _, err = ds.Where(col.Neq(false)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT FALSE)") + sql, _, err = ds.Where(col.IsNot(true)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT TRUE)") + sql, _, err = ds.Where(col.IsNot(false)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT FALSE)") + sql, _, err = ds.Where(col.IsNotTrue()).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT TRUE)") + sql, _, err = ds.Where(col.IsNotFalse()).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT FALSE)") + + sql, _, err = ds.Where(col.Like("a%")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` LIKE BINARY 'a%')") + + sql, _, err = ds.Where(col.NotLike("a%")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT LIKE BINARY 'a%')") + + sql, _, err = ds.Where(col.ILike("a%")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` LIKE 'a%')") + sql, _, err = ds.Where(col.NotILike("a%")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')") + + sql, _, err = ds.Where(col.Like(regexp.MustCompile("(a|b)"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` REGEXP BINARY '(a|b)')") + sql, _, err = ds.Where(col.NotLike(regexp.MustCompile("(a|b)"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT REGEXP BINARY '(a|b)')") + sql, _, err = ds.Where(col.ILike(regexp.MustCompile("(a|b)"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` REGEXP '(a|b)')") + sql, _, err = ds.Where(col.NotILike(regexp.MustCompile("(a|b)"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT REGEXP '(a|b)')") + +} + +func TestDatasetAdapterSuite(t *testing.T) { + suite.Run(t, new(mysqlDialectSuite)) +} diff --git a/adapters/mysql/mysql_test.go b/dialect/mysql/mysql_test.go similarity index 62% rename from adapters/mysql/mysql_test.go rename to dialect/mysql/mysql_test.go index 1c938800..f4565226 100644 --- a/adapters/mysql/mysql_test.go +++ b/dialect/mysql/mysql_test.go @@ -1,4 +1,4 @@ -package mysql +package mysql_test import ( "database/sql" @@ -7,7 +7,8 @@ import ( "testing" "time" - "github.com/doug-martin/goqu/v6" + "github.com/doug-martin/goqu/v7" + _ "github.com/doug-martin/goqu/v7/dialect/mysql" _ "github.com/go-sql-driver/mysql" "github.com/stretchr/testify/assert" @@ -15,8 +16,8 @@ import ( ) const ( - drop_table = "DROP TABLE IF EXISTS `entry`;" - create_table = "CREATE TABLE `entry` (" + + dropTable = "DROP TABLE IF EXISTS `entry`;" + createTable = "CREATE TABLE `entry` (" + "`id` INT NOT NULL AUTO_INCREMENT ," + "`int` INT NOT NULL UNIQUE," + "`float` FLOAT NOT NULL ," + @@ -25,7 +26,7 @@ const ( "`bool` TINYINT NOT NULL ," + "`bytes` BLOB NOT NULL ," + "PRIMARY KEY (`id`) );" - insert_default_reords = "INSERT INTO `entry` (`int`, `float`, `string`, `time`, `bool`, `bytes`) VALUES" + + insertDefaultReords = "INSERT INTO `entry` (`int`, `float`, `string`, `time`, `bool`, `bytes`) VALUES" + "(0, 0.000000, '0.000000', '2015-02-22 18:19:55', TRUE, '0.000000')," + "(1, 0.100000, '0.100000', '2015-02-22 19:19:55', FALSE, '0.100000')," + "(2, 0.200000, '0.200000', '2015-02-22 20:19:55', TRUE, '0.200000')," + @@ -38,25 +39,15 @@ const ( "(9, 0.900000, '0.900000', '2015-02-23 03:19:55', FALSE, '0.900000');" ) -const default_db_uri = "root@/goqumysql?parseTime=true" - -var db_uri string - -func init() { - db_uri = os.Getenv("MYSQL_URI") - if db_uri == "" { - db_uri = default_db_uri - } -} +const defaultDbURI = "root@/goqumysql?parseTime=true" type ( - logger struct{} mysqlTest struct { suite.Suite db *goqu.Database } entry struct { - Id uint32 `db:"id" goqu:"skipinsert,skipupdate"` + ID uint32 `db:"id" goqu:"skipinsert,skipupdate"` Int int `db:"int"` Float float64 `db:"float"` String string `db:"string"` @@ -66,59 +57,62 @@ type ( } ) -func (me logger) Printf(sql string, args ...interface{}) { - fmt.Printf("\n"+sql, args) -} - -func (me *mysqlTest) SetupSuite() { - db, err := sql.Open("mysql", db_uri) +func (mt *mysqlTest) SetupSuite() { + dbURI := os.Getenv("MYSQL_URI") + if dbURI == "" { + dbURI = defaultDbURI + } + db, err := sql.Open("mysql", dbURI) if err != nil { panic(err.Error()) } - me.db = goqu.New("mysql", db) + mt.db = goqu.New("mysql", db) } -func (me *mysqlTest) SetupTest() { - if _, err := me.db.Exec(drop_table); err != nil { +func (mt *mysqlTest) SetupTest() { + if _, err := mt.db.Exec(dropTable); err != nil { panic(err) } - if _, err := me.db.Exec(create_table); err != nil { + if _, err := mt.db.Exec(createTable); err != nil { panic(err) } - if _, err := me.db.Exec(insert_default_reords); err != nil { + if _, err := mt.db.Exec(insertDefaultReords); err != nil { panic(err) } } -func (me *mysqlTest) TestSelectSql() { - t := me.T() - ds := me.db.From("entry") - sql, _, err := ds.Select("id", "float", "string", "time", "bool").ToSql() +func (mt *mysqlTest) TestToSQL() { + t := mt.T() + ds := mt.db.From("entry") + s, _, err := ds.Select("id", "float", "string", "time", "bool").ToSQL() assert.NoError(t, err) - assert.Equal(t, sql, "SELECT `id`, `float`, `string`, `time`, `bool` FROM `entry`") + assert.Equal(t, s, "SELECT `id`, `float`, `string`, `time`, `bool` FROM `entry`") - sql, _, err = ds.Where(goqu.I("int").Eq(10)).ToSql() + s, _, err = ds.Where(goqu.C("int").Eq(10)).ToSQL() assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `entry` WHERE (`int` = 10)") + assert.Equal(t, s, "SELECT * FROM `entry` WHERE (`int` = 10)") - sql, args, err := ds.Prepared(true).Where(goqu.L("? = ?", goqu.I("int"), 10)).ToSql() + s, args, err := ds.Prepared(true).Where(goqu.L("? = ?", goqu.C("int"), 10)).ToSQL() assert.NoError(t, err) assert.Equal(t, args, []interface{}{int64(10)}) - assert.Equal(t, sql, "SELECT * FROM `entry` WHERE `int` = ?") + assert.Equal(t, s, "SELECT * FROM `entry` WHERE `int` = ?") } -func (me *mysqlTest) TestQuery() { - t := me.T() +func (mt *mysqlTest) TestQuery() { + t := mt.T() var entries []entry - ds := me.db.From("entry") - assert.NoError(t, ds.Order(goqu.I("id").Asc()).ScanStructs(&entries)) + ds := mt.db.From("entry") + assert.NoError(t, ds.Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 10) floatVal := float64(0) - baseDate, err := time.Parse(time_format, "2015-02-22 18:19:55") + baseDate, err := time.Parse( + "2006-01-02 15:04:05", + "2015-02-22 18:19:55", + ) assert.NoError(t, err) for i, entry := range entries { f := fmt.Sprintf("%f", floatVal) - assert.Equal(t, entry.Id, uint32(i+1)) + assert.Equal(t, entry.ID, uint32(i+1)) assert.Equal(t, entry.Int, i) assert.Equal(t, fmt.Sprintf("%f", entry.Float), f) assert.Equal(t, entry.String, f) @@ -128,7 +122,7 @@ func (me *mysqlTest) TestQuery() { floatVal += float64(0.1) } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("bool").IsTrue()).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("bool").IsTrue()).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -136,7 +130,7 @@ func (me *mysqlTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Gt(4)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Gt(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -144,7 +138,7 @@ func (me *mysqlTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Gte(5)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Gte(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -152,7 +146,7 @@ func (me *mysqlTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Lt(5)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Lt(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -160,7 +154,7 @@ func (me *mysqlTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Lte(4)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Lte(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -168,7 +162,7 @@ func (me *mysqlTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Between(goqu.RangeVal{Start: 3, End: 6})).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Between(goqu.Range(3, 6))).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 4) assert.NoError(t, err) for _, entry := range entries { @@ -177,7 +171,7 @@ func (me *mysqlTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("string").Eq("0.100000")).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("string").Eq("0.100000")).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 1) assert.NoError(t, err) for _, entry := range entries { @@ -185,7 +179,7 @@ func (me *mysqlTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("string").Like("0.1%")).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("string").Like("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 1) assert.NoError(t, err) for _, entry := range entries { @@ -193,7 +187,7 @@ func (me *mysqlTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("string").NotLike("0.1%")).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("string").NotLike("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 9) assert.NoError(t, err) for _, entry := range entries { @@ -201,43 +195,43 @@ func (me *mysqlTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("string").IsNull()).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("string").IsNull()).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 0) } -func (me *mysqlTest) TestCount() { - t := me.T() - ds := me.db.From("entry") +func (mt *mysqlTest) TestCount() { + t := mt.T() + ds := mt.db.From("entry") count, err := ds.Count() assert.NoError(t, err) assert.Equal(t, count, int64(10)) - count, err = ds.Where(goqu.I("int").Gt(4)).Count() + count, err = ds.Where(goqu.C("int").Gt(4)).Count() assert.NoError(t, err) assert.Equal(t, count, int64(5)) - count, err = ds.Where(goqu.I("int").Gte(4)).Count() + count, err = ds.Where(goqu.C("int").Gte(4)).Count() assert.NoError(t, err) assert.Equal(t, count, int64(6)) - count, err = ds.Where(goqu.I("string").Like("0.1%")).Count() + count, err = ds.Where(goqu.C("string").Like("0.1%")).Count() assert.NoError(t, err) assert.Equal(t, count, int64(1)) - count, err = ds.Where(goqu.I("string").IsNull()).Count() + count, err = ds.Where(goqu.C("string").IsNull()).Count() assert.NoError(t, err) assert.Equal(t, count, int64(0)) } -func (me *mysqlTest) TestInsert() { - t := me.T() - ds := me.db.From("entry") +func (mt *mysqlTest) TestInsert() { + t := mt.T() + ds := mt.db.From("entry") now := time.Now() e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")} _, err := ds.Insert(e).Exec() assert.NoError(t, err) var insertedEntry entry - found, err := ds.Where(goqu.I("int").Eq(10)).ScanStruct(&insertedEntry) + found, err := ds.Where(goqu.C("int").Eq(10)).ScanStruct(&insertedEntry) assert.NoError(t, err) assert.True(t, found) - assert.True(t, insertedEntry.Id > 0) + assert.True(t, insertedEntry.ID > 0) entries := []entry{ {Int: 11, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")}, @@ -249,7 +243,7 @@ func (me *mysqlTest) TestInsert() { assert.NoError(t, err) var newEntries []entry - assert.NoError(t, ds.Where(goqu.I("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries)) + assert.NoError(t, ds.Where(goqu.C("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries)) assert.Len(t, newEntries, 4) _, err = ds.Insert( @@ -261,13 +255,13 @@ func (me *mysqlTest) TestInsert() { assert.NoError(t, err) newEntries = newEntries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").In([]uint32{15, 16, 17, 18})).ScanStructs(&newEntries)) + assert.NoError(t, ds.Where(goqu.C("int").In([]uint32{15, 16, 17, 18})).ScanStructs(&newEntries)) assert.Len(t, newEntries, 4) } -func (me *mysqlTest) TestInsertReturning() { - t := me.T() - ds := me.db.From("entry") +func (mt *mysqlTest) TestInsertReturning() { + t := mt.T() + ds := mt.db.From("entry") now := time.Now() e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")} _, err := ds.Returning(goqu.Star()).Insert(e).ScanStruct(&e) @@ -275,39 +269,39 @@ func (me *mysqlTest) TestInsertReturning() { } -func (me *mysqlTest) TestUpdate() { - t := me.T() - ds := me.db.From("entry") +func (mt *mysqlTest) TestUpdate() { + t := mt.T() + ds := mt.db.From("entry") var e entry - found, err := ds.Where(goqu.I("int").Eq(9)).Select("id").ScanStruct(&e) + found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e) assert.NoError(t, err) assert.True(t, found) e.Int = 11 - _, err = ds.Where(goqu.I("id").Eq(e.Id)).Update(e).Exec() + _, err = ds.Where(goqu.C("id").Eq(e.ID)).Update(e).Exec() assert.NoError(t, err) - count, err := ds.Where(goqu.I("int").Eq(11)).Count() + count, err := ds.Where(goqu.C("int").Eq(11)).Count() assert.NoError(t, err) assert.Equal(t, count, int64(1)) } -func (me *mysqlTest) TestUpdateReturning() { - t := me.T() - ds := me.db.From("entry") +func (mt *mysqlTest) TestUpdateReturning() { + t := mt.T() + ds := mt.db.From("entry") var id uint32 - _, err := ds.Where(goqu.I("int").Eq(11)).Returning("id").Update(map[string]interface{}{"int": 9}).ScanVal(&id) + _, err := ds.Where(goqu.C("int").Eq(11)).Returning("id").Update(goqu.Record{"int": 9}).ScanVal(&id) assert.Error(t, err) - assert.Equal(t, err.Error(), "goqu: Adapter does not support RETURNING clause") + assert.Equal(t, err.Error(), "goqu: adapter does not support RETURNING clause") } -func (me *mysqlTest) TestDelete() { - t := me.T() - ds := me.db.From("entry") +func (mt *mysqlTest) TestDelete() { + t := mt.T() + ds := mt.db.From("entry") var e entry - found, err := ds.Where(goqu.I("int").Eq(9)).Select("id").ScanStruct(&e) + found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e) assert.NoError(t, err) assert.True(t, found) - _, err = ds.Where(goqu.I("id").Eq(e.Id)).Delete().Exec() + _, err = ds.Where(goqu.C("id").Eq(e.ID)).Delete().Exec() assert.NoError(t, err) count, err := ds.Count() @@ -315,27 +309,27 @@ func (me *mysqlTest) TestDelete() { assert.Equal(t, count, int64(9)) var id uint32 - found, err = ds.Where(goqu.I("id").Eq(e.Id)).ScanVal(&id) + found, err = ds.Where(goqu.C("id").Eq(e.ID)).ScanVal(&id) assert.NoError(t, err) assert.False(t, found) e = entry{} - found, err = ds.Where(goqu.I("int").Eq(8)).Select("id").ScanStruct(&e) + found, err = ds.Where(goqu.C("int").Eq(8)).Select("id").ScanStruct(&e) assert.NoError(t, err) assert.True(t, found) - assert.NotEqual(t, e.Id, 0) + assert.NotEqual(t, e.ID, 0) id = 0 - _, err = ds.Where(goqu.I("id").Eq(e.Id)).Returning("id").Delete().ScanVal(&id) - assert.Equal(t, err.Error(), "goqu: Adapter does not support RETURNING clause") + _, err = ds.Where(goqu.C("id").Eq(e.ID)).Returning("id").Delete().ScanVal(&id) + assert.Equal(t, err.Error(), "goqu: adapter does not support RETURNING clause") } -func (me *mysqlTest) TestInsertIgnore() { - t := me.T() - ds := me.db.From("entry") +func (mt *mysqlTest) TestInsertIgnore() { + t := mt.T() + ds := mt.db.From("entry") now := time.Now() - //insert one + // insert one entries := []entry{ {Int: 8, Float: 6.100000, String: "6.100000", Time: now, Bytes: []byte("6.100000")}, {Int: 9, Float: 7.200000, String: "7.200000", Time: now, Bytes: []byte("7.200000")}, @@ -349,37 +343,37 @@ func (me *mysqlTest) TestInsertIgnore() { assert.Equal(t, count, int64(11)) } -func (me *mysqlTest) TestInsertConflict() { - t := me.T() - ds := me.db.From("entry") +func (mt *mysqlTest) TestInsertConflict() { + t := mt.T() + ds := mt.db.From("entry") now := time.Now() - //insert + // insert e := entry{Int: 10, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")} _, err := ds.InsertConflict(goqu.DoNothing(), e).Exec() assert.NoError(t, err) - //duplicate + // duplicate e = entry{Int: 10, Float: 2.100000, String: "2.100000", Time: now.Add(time.Hour * 100), Bool: false, Bytes: []byte("2.100000")} _, err = ds.InsertConflict(goqu.DoNothing(), e).Exec() assert.NoError(t, err) - //update + // update var entryActual entry e2 := entry{Int: 10, String: "2.000000"} _, err = ds.InsertConflict(goqu.DoUpdate("int", goqu.Record{"string": "upsert"}), e2).Exec() assert.NoError(t, err) - _, err = ds.Where(goqu.I("int").Eq(10)).ScanStruct(&entryActual) + _, err = ds.Where(goqu.C("int").Eq(10)).ScanStruct(&entryActual) assert.NoError(t, err) assert.Equal(t, "upsert", entryActual.String) - //update where should error + // update where should error entries := []entry{ {Int: 8, Float: 6.100000, String: "6.100000", Time: now, Bytes: []byte("6.100000")}, {Int: 9, Float: 7.200000, String: "7.200000", Time: now, Bytes: []byte("7.200000")}, } - _, err = ds.InsertConflict(goqu.DoUpdate("int", goqu.Record{"string": "upsert"}).Where(goqu.I("int").Eq(9)), entries).Exec() - assert.Equal(t, err.Error(), "goqu: Adapter does not support upsert with where clause") + _, err = ds.InsertConflict(goqu.DoUpdate("int", goqu.Record{"string": "upsert"}).Where(goqu.C("int").Eq(9)), entries).Exec() + assert.Equal(t, err.Error(), "goqu: adapter does not support upsert with where clause") } func TestMysqlSuite(t *testing.T) { diff --git a/dialect/postgres/postgres.go b/dialect/postgres/postgres.go new file mode 100644 index 00000000..006139dc --- /dev/null +++ b/dialect/postgres/postgres.go @@ -0,0 +1,16 @@ +package postgres + +import ( + "github.com/doug-martin/goqu/v7" +) + +func DialectOptions() *goqu.SQLDialectOptions { + do := goqu.DefaultDialectOptions() + do.PlaceHolderRune = '$' + do.IncludePlaceholderNum = true + return do +} + +func init() { + goqu.RegisterDialect("postgres", DialectOptions()) +} diff --git a/adapters/postgres/postgres_test.go b/dialect/postgres/postgres_test.go similarity index 71% rename from adapters/postgres/postgres_test.go rename to dialect/postgres/postgres_test.go index 0e586978..56c9f0d6 100644 --- a/adapters/postgres/postgres_test.go +++ b/dialect/postgres/postgres_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/doug-martin/goqu/v6" + "github.com/doug-martin/goqu/v7" "github.com/lib/pq" "github.com/stretchr/testify/assert" @@ -37,16 +37,7 @@ const schema = ` (9, 0.900000, '0.900000', '2015-02-23T03:19:55.000000000-00:00', FALSE, '0.900000'); ` -const default_db_uri = "postgres://postgres:@/goqupostgres?sslmode=disable" - -var db_uri string - -func init() { - db_uri = os.Getenv("PG_URI") - if db_uri == "" { - db_uri = default_db_uri - } -} +const defaultDbURI = "postgres://postgres:@localhost:5435/goqupostgres?sslmode=disable" type ( postgresTest struct { @@ -54,7 +45,7 @@ type ( db *goqu.Database } entry struct { - Id uint32 `db:"id" goqu:"skipinsert,skipupdate"` + ID uint32 `db:"id" goqu:"skipinsert,skipupdate"` Int int `db:"int"` Float float64 `db:"float"` String string `db:"string"` @@ -64,8 +55,12 @@ type ( } ) -func (me *postgresTest) SetupSuite() { - uri, err := pq.ParseURL(db_uri) +func (pt *postgresTest) SetupSuite() { + dbURI := os.Getenv("PG_URI") + if dbURI == "" { + dbURI = defaultDbURI + } + uri, err := pq.ParseURL(dbURI) if err != nil { panic(err) } @@ -73,37 +68,37 @@ func (me *postgresTest) SetupSuite() { if err != nil { panic(err) } - me.db = goqu.New("postgres", db) + pt.db = goqu.New("postgres", db) } -func (me *postgresTest) SetupTest() { - if _, err := me.db.Exec(schema); err != nil { +func (pt *postgresTest) SetupTest() { + if _, err := pt.db.Exec(schema); err != nil { panic(err) } } -func (me *postgresTest) TestSelectSql() { - t := me.T() - ds := me.db.From("entry") - sql, _, err := ds.Select("id", "float", "string", "time", "bool").ToSql() +func (pt *postgresTest) TestToSQL() { + t := pt.T() + ds := pt.db.From("entry") + s, _, err := ds.Select("id", "float", "string", "time", "bool").ToSQL() assert.NoError(t, err) - assert.Equal(t, sql, `SELECT "id", "float", "string", "time", "bool" FROM "entry"`) + assert.Equal(t, s, `SELECT "id", "float", "string", "time", "bool" FROM "entry"`) - sql, _, err = ds.Where(goqu.I("int").Eq(10)).ToSql() + s, _, err = ds.Where(goqu.C("int").Eq(10)).ToSQL() assert.NoError(t, err) - assert.Equal(t, sql, `SELECT * FROM "entry" WHERE ("int" = 10)`) + assert.Equal(t, s, `SELECT * FROM "entry" WHERE ("int" = 10)`) - sql, args, err := ds.Prepared(true).Where(goqu.L("? = ?", goqu.I("int"), 10)).ToSql() + s, args, err := ds.Prepared(true).Where(goqu.L("? = ?", goqu.C("int"), 10)).ToSQL() assert.NoError(t, err) assert.Equal(t, args, []interface{}{int64(10)}) - assert.Equal(t, sql, `SELECT * FROM "entry" WHERE "int" = $1`) + assert.Equal(t, s, `SELECT * FROM "entry" WHERE "int" = $1`) } -func (me *postgresTest) TestQuery() { - t := me.T() +func (pt *postgresTest) TestQuery() { + t := pt.T() var entries []entry - ds := me.db.From("entry") - assert.NoError(t, ds.Order(goqu.I("id").Asc()).ScanStructs(&entries)) + ds := pt.db.From("entry") + assert.NoError(t, ds.Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 10) floatVal := float64(0) baseDate, err := time.Parse(time.RFC3339Nano, "2015-02-22T18:19:55.000000000-00:00") @@ -111,7 +106,7 @@ func (me *postgresTest) TestQuery() { baseDate = baseDate.UTC() for i, entry := range entries { f := fmt.Sprintf("%f", floatVal) - assert.Equal(t, entry.Id, uint32(i+1)) + assert.Equal(t, entry.ID, uint32(i+1)) assert.Equal(t, entry.Int, i) assert.Equal(t, fmt.Sprintf("%f", entry.Float), f) assert.Equal(t, entry.String, f) @@ -121,7 +116,7 @@ func (me *postgresTest) TestQuery() { floatVal += float64(0.1) } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("bool").IsTrue()).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("bool").IsTrue()).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -129,7 +124,7 @@ func (me *postgresTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Gt(4)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Gt(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -137,7 +132,7 @@ func (me *postgresTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Gte(5)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Gte(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -145,7 +140,7 @@ func (me *postgresTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Lt(5)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Lt(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -153,7 +148,7 @@ func (me *postgresTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Lte(4)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Lte(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -161,7 +156,7 @@ func (me *postgresTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Between(goqu.RangeVal{Start: 3, End: 6})).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Between(goqu.Range(3, 6))).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 4) assert.NoError(t, err) for _, entry := range entries { @@ -170,7 +165,7 @@ func (me *postgresTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("string").Eq("0.100000")).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("string").Eq("0.100000")).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 1) assert.NoError(t, err) for _, entry := range entries { @@ -178,7 +173,7 @@ func (me *postgresTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("string").Like("0.1%")).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("string").Like("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 1) assert.NoError(t, err) for _, entry := range entries { @@ -186,7 +181,7 @@ func (me *postgresTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("string").NotLike("0.1%")).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("string").NotLike("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 9) assert.NoError(t, err) for _, entry := range entries { @@ -194,43 +189,43 @@ func (me *postgresTest) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("string").IsNull()).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("string").IsNull()).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 0) } -func (me *postgresTest) TestCount() { - t := me.T() - ds := me.db.From("entry") +func (pt *postgresTest) TestCount() { + t := pt.T() + ds := pt.db.From("entry") count, err := ds.Count() assert.NoError(t, err) assert.Equal(t, count, int64(10)) - count, err = ds.Where(goqu.I("int").Gt(4)).Count() + count, err = ds.Where(goqu.C("int").Gt(4)).Count() assert.NoError(t, err) assert.Equal(t, count, int64(5)) - count, err = ds.Where(goqu.I("int").Gte(4)).Count() + count, err = ds.Where(goqu.C("int").Gte(4)).Count() assert.NoError(t, err) assert.Equal(t, count, int64(6)) - count, err = ds.Where(goqu.I("string").Like("0.1%")).Count() + count, err = ds.Where(goqu.C("string").Like("0.1%")).Count() assert.NoError(t, err) assert.Equal(t, count, int64(1)) - count, err = ds.Where(goqu.I("string").IsNull()).Count() + count, err = ds.Where(goqu.C("string").IsNull()).Count() assert.NoError(t, err) assert.Equal(t, count, int64(0)) } -func (me *postgresTest) TestInsert() { - t := me.T() - ds := me.db.From("entry") +func (pt *postgresTest) TestInsert() { + t := pt.T() + ds := pt.db.From("entry") now := time.Now() e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")} _, err := ds.Insert(e).Exec() assert.NoError(t, err) var insertedEntry entry - found, err := ds.Where(goqu.I("int").Eq(10)).ScanStruct(&insertedEntry) + found, err := ds.Where(goqu.C("int").Eq(10)).ScanStruct(&insertedEntry) assert.NoError(t, err) assert.True(t, found) - assert.True(t, insertedEntry.Id > 0) + assert.True(t, insertedEntry.ID > 0) entries := []entry{ {Int: 11, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")}, @@ -242,7 +237,7 @@ func (me *postgresTest) TestInsert() { assert.NoError(t, err) var newEntries []entry - assert.NoError(t, ds.Where(goqu.I("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries)) + assert.NoError(t, ds.Where(goqu.C("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries)) assert.Len(t, newEntries, 4) _, err = ds.Insert( @@ -254,19 +249,19 @@ func (me *postgresTest) TestInsert() { assert.NoError(t, err) newEntries = newEntries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").In([]uint32{15, 16, 17, 18})).ScanStructs(&newEntries)) + assert.NoError(t, ds.Where(goqu.C("int").In([]uint32{15, 16, 17, 18})).ScanStructs(&newEntries)) assert.Len(t, newEntries, 4) } -func (me *postgresTest) TestInsertReturning() { - t := me.T() - ds := me.db.From("entry") +func (pt *postgresTest) TestInsertReturning() { + t := pt.T() + ds := pt.db.From("entry") now := time.Now() e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")} found, err := ds.Returning(goqu.Star()).Insert(e).ScanStruct(&e) assert.NoError(t, err) assert.True(t, found) - assert.True(t, e.Id > 0) + assert.True(t, e.ID > 0) var ids []uint32 assert.NoError(t, ds.Returning("id").Insert([]entry{ @@ -291,36 +286,36 @@ func (me *postgresTest) TestInsertReturning() { assert.Equal(t, ints, []int64{15, 16, 17, 18}) } -func (me *postgresTest) TestUpdate() { - t := me.T() - ds := me.db.From("entry") +func (pt *postgresTest) TestUpdate() { + t := pt.T() + ds := pt.db.From("entry") var e entry - found, err := ds.Where(goqu.I("int").Eq(9)).Select("id").ScanStruct(&e) + found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e) assert.NoError(t, err) assert.True(t, found) e.Int = 11 - _, err = ds.Where(goqu.I("id").Eq(e.Id)).Update(e).Exec() + _, err = ds.Where(goqu.C("id").Eq(e.ID)).Update(e).Exec() assert.NoError(t, err) - count, err := ds.Where(goqu.I("int").Eq(11)).Count() + count, err := ds.Where(goqu.C("int").Eq(11)).Count() assert.NoError(t, err) assert.Equal(t, count, int64(1)) var id uint32 - found, err = ds.Where(goqu.I("int").Eq(11)).Returning("id").Update(map[string]interface{}{"int": 9}).ScanVal(&id) + found, err = ds.Where(goqu.C("int").Eq(11)).Returning("id").Update(goqu.Record{"int": 9}).ScanVal(&id) assert.NoError(t, err) assert.True(t, found) - assert.Equal(t, id, e.Id) + assert.Equal(t, id, e.ID) } -func (me *postgresTest) TestDelete() { - t := me.T() - ds := me.db.From("entry") +func (pt *postgresTest) TestDelete() { + t := pt.T() + ds := pt.db.From("entry") var e entry - found, err := ds.Where(goqu.I("int").Eq(9)).Select("id").ScanStruct(&e) + found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e) assert.NoError(t, err) assert.True(t, found) - _, err = ds.Where(goqu.I("id").Eq(e.Id)).Delete().Exec() + _, err = ds.Where(goqu.C("id").Eq(e.ID)).Delete().Exec() assert.NoError(t, err) count, err := ds.Count() @@ -328,28 +323,28 @@ func (me *postgresTest) TestDelete() { assert.Equal(t, count, int64(9)) var id uint32 - found, err = ds.Where(goqu.I("id").Eq(e.Id)).ScanVal(&id) + found, err = ds.Where(goqu.C("id").Eq(e.ID)).ScanVal(&id) assert.NoError(t, err) assert.False(t, found) e = entry{} - found, err = ds.Where(goqu.I("int").Eq(8)).Select("id").ScanStruct(&e) + found, err = ds.Where(goqu.C("int").Eq(8)).Select("id").ScanStruct(&e) assert.NoError(t, err) assert.True(t, found) - assert.NotEqual(t, e.Id, int64(0)) + assert.NotEqual(t, e.ID, int64(0)) id = 0 - _, err = ds.Where(goqu.I("id").Eq(e.Id)).Returning("id").Delete().ScanVal(&id) + _, err = ds.Where(goqu.C("id").Eq(e.ID)).Returning("id").Delete().ScanVal(&id) assert.NoError(t, err) - assert.Equal(t, id, e.Id) + assert.Equal(t, id, e.ID) } -func (me *postgresTest) TestInsertIgnore() { - t := me.T() - ds := me.db.From("entry") +func (pt *postgresTest) TestInsertIgnore() { + t := pt.T() + ds := pt.db.From("entry") now := time.Now() - //insert one + // insert one entries := []entry{ {Int: 8, Float: 6.100000, String: "6.100000", Time: now, Bytes: []byte("6.100000")}, {Int: 9, Float: 7.200000, String: "7.200000", Time: now, Bytes: []byte("7.200000")}, @@ -363,31 +358,31 @@ func (me *postgresTest) TestInsertIgnore() { assert.Equal(t, count, int64(11)) } -func (me *postgresTest) TestInsertConflict() { - t := me.T() - ds := me.db.From("entry") +func (pt *postgresTest) TestInsertConflict() { + t := pt.T() + ds := pt.db.From("entry") now := time.Now() - //DO NOTHING insert + // DO NOTHING insert e := entry{Int: 10, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")} _, err := ds.InsertConflict(goqu.DoNothing(), e).Exec() assert.NoError(t, err) - //DO NOTHING duplicate + // DO NOTHING duplicate e = entry{Int: 10, Float: 2.100000, String: "2.100000", Time: now.Add(time.Hour * 100), Bool: false, Bytes: []byte("2.100000")} _, err = ds.InsertConflict(goqu.DoNothing(), e).Exec() assert.NoError(t, err) - //DO NOTHING update + // DO NOTHING update var entryActual entry e2 := entry{Int: 0, String: "2.000000"} _, err = ds.InsertConflict(goqu.DoUpdate("int", goqu.Record{"string": "upsert"}), e2).Exec() assert.NoError(t, err) - _, err = ds.Where(goqu.I("int").Eq(0)).ScanStruct(&entryActual) + _, err = ds.Where(goqu.C("int").Eq(0)).ScanStruct(&entryActual) assert.NoError(t, err) assert.Equal(t, "upsert", entryActual.String) - //DO NOTHING update where + // DO NOTHING update where entries := []entry{ {Int: 1, Float: 6.100000, String: "6.100000", Time: now, Bytes: []byte("6.100000")}, {Int: 2, Float: 7.200000, String: "7.200000", Time: now, Bytes: []byte("7.200000")}, diff --git a/dialect/sqlite3/sqlite3.go b/dialect/sqlite3/sqlite3.go new file mode 100644 index 00000000..53203e68 --- /dev/null +++ b/dialect/sqlite3/sqlite3.go @@ -0,0 +1,66 @@ +package sqlite3 + +import ( + "github.com/doug-martin/goqu/v7" + "github.com/doug-martin/goqu/v7/exp" +) + +func DialectOptions() *goqu.SQLDialectOptions { + opts := goqu.DefaultDialectOptions() + + opts.SupportsReturn = false + opts.SupportsOrderByOnUpdate = true + opts.SupportsLimitOnUpdate = true + opts.SupportsOrderByOnDelete = true + opts.SupportsLimitOnDelete = true + opts.SupportsConflictUpdateWhere = false + opts.SupportsInsertIgnoreSyntax = true + opts.SupportsConflictTarget = false + + opts.PlaceHolderRune = '?' + opts.IncludePlaceholderNum = false + opts.QuoteRune = '`' + opts.DefaultValuesFragment = []byte("") + opts.True = []byte("1") + opts.False = []byte("0") + opts.TimeFormat = "2006-01-02 15:04:05" + opts.BooleanOperatorLookup = map[exp.BooleanOperation][]byte{ + exp.EqOp: []byte("="), + exp.NeqOp: []byte("!="), + exp.GtOp: []byte(">"), + exp.GteOp: []byte(">="), + exp.LtOp: []byte("<"), + exp.LteOp: []byte("<="), + exp.InOp: []byte("IN"), + exp.NotInOp: []byte("NOT IN"), + exp.IsOp: []byte("IS"), + exp.IsNotOp: []byte("IS NOT"), + exp.LikeOp: []byte("LIKE"), + exp.NotLikeOp: []byte("NOT LIKE"), + exp.ILikeOp: []byte("LIKE"), + exp.NotILikeOp: []byte("NOT LIKE"), + exp.RegexpLikeOp: []byte("REGEXP"), + exp.RegexpNotLikeOp: []byte("NOT REGEXP"), + exp.RegexpILikeOp: []byte("REGEXP"), + exp.RegexpNotILikeOp: []byte("NOT REGEXP"), + } + opts.UseLiteralIsBools = false + opts.EscapedRunes = map[rune][]byte{ + '\'': []byte("\\'"), + '"': []byte("\\\""), + '\\': []byte("\\\\"), + '\n': []byte("\\n"), + '\r': []byte("\\r"), + 0: []byte("\\x00"), + 0x1a: []byte("\\x1a"), + } + opts.InsertIgnoreClause = []byte("INSERT OR IGNORE") + opts.ConflictFragment = []byte("") + opts.ConflictDoUpdateFragment = []byte("") + opts.ConflictDoNothingFragment = []byte("") + return opts +} + +func init() { + goqu.RegisterDialect("sqlite3", DialectOptions()) +} diff --git a/dialect/sqlite3/sqlite3_dialect_test.go b/dialect/sqlite3/sqlite3_dialect_test.go new file mode 100644 index 00000000..ff7b26da --- /dev/null +++ b/dialect/sqlite3/sqlite3_dialect_test.go @@ -0,0 +1,179 @@ +package sqlite3 + +import ( + "regexp" + "testing" + + "github.com/doug-martin/goqu/v7" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type sqlite3DialectSuite struct { + suite.Suite +} + +func (sds *sqlite3DialectSuite) GetDs(table string) *goqu.Dataset { + return goqu.Dialect("sqlite3").From(table) +} + +func (sds *sqlite3DialectSuite) TestIdentifiers() { + t := sds.T() + ds := sds.GetDs("test") + sql, _, err := ds.Select( + "a", + goqu.I("a.b.c"), + goqu.I("c.d"), + goqu.C("test").As("test"), + ).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT `a`, `a`.`b`.`c`, `c`.`d`, `test` AS `test` FROM `test`") +} + +func (sds *sqlite3DialectSuite) TestLiteralString() { + t := sds.T() + ds := sds.GetDs("test") + sql, _, err := ds.Where(goqu.C("a").Eq("test")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test')") + + sql, _, err = ds.Where(goqu.C("a").Eq("test'test")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\'test')") + + sql, _, err = ds.Where(goqu.C("a").Eq(`test"test`)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\"test')") + + sql, _, err = ds.Where(goqu.C("a").Eq(`test\test`)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\\test')") + + sql, _, err = ds.Where(goqu.C("a").Eq("test\ntest")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\ntest')") + + sql, _, err = ds.Where(goqu.C("a").Eq("test\rtest")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\rtest')") + + sql, _, err = ds.Where(goqu.C("a").Eq("test\x00test")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x00test')") + + sql, _, err = ds.Where(goqu.C("a").Eq("test\x1atest")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')") +} + +func (sds *sqlite3DialectSuite) TestLiteralBytes() { + t := sds.T() + ds := sds.GetDs("test") + sql, _, err := ds.Where(goqu.C("a").Eq([]byte("test"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test')") + + sql, _, err = ds.Where(goqu.C("a").Eq([]byte("test'test"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\'test')") + + sql, _, err = ds.Where(goqu.C("a").Eq([]byte(`test"test`))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\"test')") + + sql, _, err = ds.Where(goqu.C("a").Eq([]byte(`test\test`))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\\test')") + + sql, _, err = ds.Where(goqu.C("a").Eq([]byte("test\ntest"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\ntest')") + + sql, _, err = ds.Where(goqu.C("a").Eq([]byte("test\rtest"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\rtest')") + + sql, _, err = ds.Where(goqu.C("a").Eq([]byte("test\x00test"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x00test')") + + sql, _, err = ds.Where(goqu.C("a").Eq([]byte("test\x1atest"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')") +} + +func (sds *sqlite3DialectSuite) TestBooleanOperations() { + t := sds.T() + ds := sds.GetDs("test") + sql, _, err := ds.Where(goqu.C("a").Eq(true)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS 1)") + sql, _, err = ds.Where(goqu.C("a").Eq(false)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS 0)") + sql, _, err = ds.Where(goqu.C("a").Is(true)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS 1)") + sql, _, err = ds.Where(goqu.C("a").Is(false)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS 0)") + sql, _, err = ds.Where(goqu.C("a").IsTrue()).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS 1)") + sql, _, err = ds.Where(goqu.C("a").IsFalse()).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS 0)") + + sql, _, err = ds.Where(goqu.C("a").Neq(true)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT 1)") + sql, _, err = ds.Where(goqu.C("a").Neq(false)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT 0)") + sql, _, err = ds.Where(goqu.C("a").IsNot(true)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT 1)") + sql, _, err = ds.Where(goqu.C("a").IsNot(false)).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT 0)") + sql, _, err = ds.Where(goqu.C("a").IsNotTrue()).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT 1)") + sql, _, err = ds.Where(goqu.C("a").IsNotFalse()).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT 0)") + + sql, _, err = ds.Where(goqu.C("a").Like("a%")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` LIKE 'a%')") + + sql, _, err = ds.Where(goqu.C("a").NotLike("a%")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')") + + sql, _, err = ds.Where(goqu.C("a").ILike("a%")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` LIKE 'a%')") + sql, _, err = ds.Where(goqu.C("a").NotILike("a%")).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')") + + sql, _, err = ds.Where(goqu.C("a").Like(regexp.MustCompile("(a|b)"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` REGEXP '(a|b)')") + sql, _, err = ds.Where(goqu.C("a").NotLike(regexp.MustCompile("(a|b)"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT REGEXP '(a|b)')") + sql, _, err = ds.Where(goqu.C("a").ILike(regexp.MustCompile("(a|b)"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` REGEXP '(a|b)')") + sql, _, err = ds.Where(goqu.C("a").NotILike(regexp.MustCompile("(a|b)"))).ToSQL() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT REGEXP '(a|b)')") + +} + +func TestDatasetAdapterSuite(t *testing.T) { + suite.Run(t, new(sqlite3DialectSuite)) +} diff --git a/adapters/sqlite3/sqlite3_test.go b/dialect/sqlite3/sqlite3_test.go similarity index 60% rename from adapters/sqlite3/sqlite3_test.go rename to dialect/sqlite3/sqlite3_test.go index 1158f75d..2255161c 100644 --- a/adapters/sqlite3/sqlite3_test.go +++ b/dialect/sqlite3/sqlite3_test.go @@ -6,16 +6,16 @@ import ( "testing" "time" - "github.com/doug-martin/goqu/v6" - + "github.com/doug-martin/goqu/v7" _ "github.com/mattn/go-sqlite3" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) const ( - drop_table = "DROP TABLE IF EXISTS `entry`;" - create_table = "CREATE TABLE `entry` (" + + dropTable = "DROP TABLE IF EXISTS `entry`;" + createTable = "CREATE TABLE `entry` (" + "`id` INTEGER PRIMARY KEY," + "`int` INT NOT NULL ," + "`float` FLOAT NOT NULL ," + @@ -24,7 +24,7 @@ const ( "`bool` TINYINT NOT NULL ," + "`bytes` BLOB NOT NULL" + ");" - insert_default_reords = "INSERT INTO `entry` (`int`, `float`, `string`, `time`, `bool`, `bytes`) VALUES" + + insertDefaultReords = "INSERT INTO `entry` (`int`, `float`, `string`, `time`, `bool`, `bytes`) VALUES" + "(0, 0.000000, '0.000000', '2015-02-22 18:19:55', 1, '0.000000')," + "(1, 0.100000, '0.100000', '2015-02-22 19:19:55', 0, '0.100000')," + "(2, 0.200000, '0.200000', '2015-02-22 20:19:55', 1, '0.200000')," + @@ -37,16 +37,15 @@ const ( "(9, 0.900000, '0.900000', '2015-02-23 03:19:55', 0, '0.900000');" ) -var db_uri = ":memory:" +var dbURI = ":memory:" type ( - logger struct{} - sqlite3Test struct { + sqlite3Suite struct { suite.Suite db *goqu.Database } entry struct { - Id uint32 `db:"id" goqu:"skipinsert,skipupdate"` + ID uint32 `db:"id" goqu:"skipinsert,skipupdate"` Int int `db:"int"` Float float64 `db:"float"` String string `db:"string"` @@ -56,60 +55,56 @@ type ( } ) -func (me logger) Printf(sql string, args ...interface{}) { - fmt.Printf("\n"+sql, args) -} - -func (me *sqlite3Test) SetupSuite() { - fmt.Println(db_uri) - db, err := sql.Open("sqlite3", db_uri) +func (st *sqlite3Suite) SetupSuite() { + fmt.Println(dbURI) + db, err := sql.Open("sqlite3", dbURI) if err != nil { panic(err.Error()) } - me.db = goqu.New("sqlite3", db) + st.db = goqu.New("sqlite3", db) } -func (me *sqlite3Test) SetupTest() { - if _, err := me.db.Exec(drop_table); err != nil { +func (st *sqlite3Suite) SetupTest() { + if _, err := st.db.Exec(dropTable); err != nil { panic(err) } - if _, err := me.db.Exec(create_table); err != nil { + if _, err := st.db.Exec(createTable); err != nil { panic(err) } - if _, err := me.db.Exec(insert_default_reords); err != nil { + if _, err := st.db.Exec(insertDefaultReords); err != nil { panic(err) } } -func (me *sqlite3Test) TestSelectSql() { - t := me.T() - ds := me.db.From("entry") - sql, _, err := ds.Select("id", "float", "string", "time", "bool").ToSql() +func (st *sqlite3Suite) TestSelectSQL() { + t := st.T() + ds := st.db.From("entry") + s, _, err := ds.Select("id", "float", "string", "time", "bool").ToSQL() assert.NoError(t, err) - assert.Equal(t, sql, "SELECT `id`, `float`, `string`, `time`, `bool` FROM `entry`") + assert.Equal(t, s, "SELECT `id`, `float`, `string`, `time`, `bool` FROM `entry`") - sql, _, err = ds.Where(goqu.I("int").Eq(10)).ToSql() + s, _, err = ds.Where(goqu.C("int").Eq(10)).ToSQL() assert.NoError(t, err) - assert.Equal(t, sql, "SELECT * FROM `entry` WHERE (`int` = 10)") + assert.Equal(t, s, "SELECT * FROM `entry` WHERE (`int` = 10)") - sql, args, err := ds.Prepared(true).Where(goqu.L("? = ?", goqu.I("int"), 10)).ToSql() + s, args, err := ds.Prepared(true).Where(goqu.L("? = ?", goqu.C("int"), 10)).ToSQL() assert.NoError(t, err) assert.Equal(t, args, []interface{}{int64(10)}) - assert.Equal(t, sql, "SELECT * FROM `entry` WHERE `int` = ?") + assert.Equal(t, s, "SELECT * FROM `entry` WHERE `int` = ?") } -func (me *sqlite3Test) TestQuery() { - t := me.T() +func (st *sqlite3Suite) TestQuery() { + t := st.T() var entries []entry - ds := me.db.From("entry") - assert.NoError(t, ds.Order(goqu.I("id").Asc()).ScanStructs(&entries)) + ds := st.db.From("entry") + assert.NoError(t, ds.Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 10) floatVal := float64(0) - baseDate, err := time.Parse(time_format, "2015-02-22 18:19:55") + baseDate, err := time.Parse(DialectOptions().TimeFormat, "2015-02-22 18:19:55") assert.NoError(t, err) for i, entry := range entries { f := fmt.Sprintf("%f", floatVal) - assert.Equal(t, entry.Id, uint32(i+1)) + assert.Equal(t, entry.ID, uint32(i+1)) assert.Equal(t, entry.Int, i) assert.Equal(t, fmt.Sprintf("%f", entry.Float), f) assert.Equal(t, entry.String, f) @@ -119,7 +114,7 @@ func (me *sqlite3Test) TestQuery() { floatVal += float64(0.1) } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("bool").IsTrue()).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("bool").IsTrue()).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -127,7 +122,7 @@ func (me *sqlite3Test) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Gt(4)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Gt(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -135,7 +130,7 @@ func (me *sqlite3Test) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Gte(5)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Gte(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -143,7 +138,7 @@ func (me *sqlite3Test) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Lt(5)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Lt(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -151,7 +146,7 @@ func (me *sqlite3Test) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Lte(4)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Lte(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 5) assert.NoError(t, err) for _, entry := range entries { @@ -159,7 +154,7 @@ func (me *sqlite3Test) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").Between(goqu.RangeVal{Start: 3, End: 6})).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("int").Between(goqu.Range(3, 6))).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 4) assert.NoError(t, err) for _, entry := range entries { @@ -168,7 +163,7 @@ func (me *sqlite3Test) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("string").Eq("0.100000")).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("string").Eq("0.100000")).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 1) assert.NoError(t, err) for _, entry := range entries { @@ -176,7 +171,7 @@ func (me *sqlite3Test) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("string").Like("0.1%")).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("string").Like("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 1) assert.NoError(t, err) for _, entry := range entries { @@ -184,7 +179,7 @@ func (me *sqlite3Test) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("string").NotLike("0.1%")).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("string").NotLike("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 9) assert.NoError(t, err) for _, entry := range entries { @@ -192,43 +187,43 @@ func (me *sqlite3Test) TestQuery() { } entries = entries[0:0] - assert.NoError(t, ds.Where(goqu.I("string").IsNull()).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.NoError(t, ds.Where(goqu.C("string").IsNull()).Order(goqu.C("id").Asc()).ScanStructs(&entries)) assert.Len(t, entries, 0) } -func (me *sqlite3Test) TestCount() { - t := me.T() - ds := me.db.From("entry") +func (st *sqlite3Suite) TestCount() { + t := st.T() + ds := st.db.From("entry") count, err := ds.Count() assert.NoError(t, err) assert.Equal(t, count, int64(10)) - count, err = ds.Where(goqu.I("int").Gt(4)).Count() + count, err = ds.Where(goqu.C("int").Gt(4)).Count() assert.NoError(t, err) assert.Equal(t, count, int64(5)) - count, err = ds.Where(goqu.I("int").Gte(4)).Count() + count, err = ds.Where(goqu.C("int").Gte(4)).Count() assert.NoError(t, err) assert.Equal(t, count, int64(6)) - count, err = ds.Where(goqu.I("string").Like("0.1%")).Count() + count, err = ds.Where(goqu.C("string").Like("0.1%")).Count() assert.NoError(t, err) assert.Equal(t, count, int64(1)) - count, err = ds.Where(goqu.I("string").IsNull()).Count() + count, err = ds.Where(goqu.C("string").IsNull()).Count() assert.NoError(t, err) assert.Equal(t, count, int64(0)) } -func (me *sqlite3Test) TestInsert() { - t := me.T() - ds := me.db.From("entry") +func (st *sqlite3Suite) TestInsert() { + t := st.T() + ds := st.db.From("entry") now := time.Now() e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")} _, err := ds.Insert(e).Exec() assert.NoError(t, err) var insertedEntry entry - found, err := ds.Where(goqu.I("int").Eq(10)).ScanStruct(&insertedEntry) + found, err := ds.Where(goqu.C("int").Eq(10)).ScanStruct(&insertedEntry) assert.NoError(t, err) assert.True(t, found) - assert.True(t, insertedEntry.Id > 0) + assert.True(t, insertedEntry.ID > 0) entries := []entry{ {Int: 11, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")}, @@ -240,7 +235,7 @@ func (me *sqlite3Test) TestInsert() { assert.NoError(t, err) var newEntries []entry - assert.NoError(t, ds.Where(goqu.I("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries)) + assert.NoError(t, ds.Where(goqu.C("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries)) assert.Len(t, newEntries, 4) _, err = ds.Insert( @@ -252,13 +247,13 @@ func (me *sqlite3Test) TestInsert() { assert.NoError(t, err) newEntries = newEntries[0:0] - assert.NoError(t, ds.Where(goqu.I("int").In([]uint32{15, 16, 17, 18})).ScanStructs(&newEntries)) + assert.NoError(t, ds.Where(goqu.C("int").In([]uint32{15, 16, 17, 18})).ScanStructs(&newEntries)) assert.Len(t, newEntries, 4) } -func (me *sqlite3Test) TestInsertReturning() { - t := me.T() - ds := me.db.From("entry") +func (st *sqlite3Suite) TestInsertReturning() { + t := st.T() + ds := st.db.From("entry") now := time.Now() e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")} _, err := ds.Returning(goqu.Star()).Insert(e).ScanStruct(&e) @@ -266,39 +261,39 @@ func (me *sqlite3Test) TestInsertReturning() { } -func (me *sqlite3Test) TestUpdate() { - t := me.T() - ds := me.db.From("entry") +func (st *sqlite3Suite) TestUpdate() { + t := st.T() + ds := st.db.From("entry") var e entry - found, err := ds.Where(goqu.I("int").Eq(9)).Select("id").ScanStruct(&e) + found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e) assert.NoError(t, err) assert.True(t, found) e.Int = 11 - _, err = ds.Where(goqu.I("id").Eq(e.Id)).Update(e).Exec() + _, err = ds.Where(goqu.C("id").Eq(e.ID)).Update(e).Exec() assert.NoError(t, err) - count, err := ds.Where(goqu.I("int").Eq(11)).Count() + count, err := ds.Where(goqu.C("int").Eq(11)).Count() assert.NoError(t, err) assert.Equal(t, count, int64(1)) } -func (me *sqlite3Test) TestUpdateReturning() { - t := me.T() - ds := me.db.From("entry") +func (st *sqlite3Suite) TestUpdateReturning() { + t := st.T() + ds := st.db.From("entry") var id uint32 - _, err := ds.Where(goqu.I("int").Eq(11)).Returning("id").Update(map[string]interface{}{"int": 9}).ScanVal(&id) + _, err := ds.Where(goqu.C("int").Eq(11)).Returning("id").Update(map[string]interface{}{"int": 9}).ScanVal(&id) assert.Error(t, err) - assert.Equal(t, err.Error(), "goqu: Adapter does not support RETURNING clause") + assert.Equal(t, err.Error(), "goqu: adapter does not support RETURNING clause") } -func (me *sqlite3Test) TestDelete() { - t := me.T() - ds := me.db.From("entry") +func (st *sqlite3Suite) TestDelete() { + t := st.T() + ds := st.db.From("entry") var e entry - found, err := ds.Where(goqu.I("int").Eq(9)).Select("id").ScanStruct(&e) + found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e) assert.NoError(t, err) assert.True(t, found) - _, err = ds.Where(goqu.I("id").Eq(e.Id)).Delete().Exec() + _, err = ds.Where(goqu.C("id").Eq(e.ID)).Delete().Exec() assert.NoError(t, err) count, err := ds.Count() @@ -306,21 +301,21 @@ func (me *sqlite3Test) TestDelete() { assert.Equal(t, count, int64(9)) var id uint32 - found, err = ds.Where(goqu.I("id").Eq(e.Id)).ScanVal(&id) + found, err = ds.Where(goqu.C("id").Eq(e.ID)).ScanVal(&id) assert.NoError(t, err) assert.False(t, found) e = entry{} - found, err = ds.Where(goqu.I("int").Eq(8)).Select("id").ScanStruct(&e) + found, err = ds.Where(goqu.C("int").Eq(8)).Select("id").ScanStruct(&e) assert.NoError(t, err) assert.True(t, found) - assert.NotEqual(t, e.Id, int64(0)) + assert.NotEqual(t, e.ID, int64(0)) id = 0 - _, err = ds.Where(goqu.I("id").Eq(e.Id)).Returning("id").Delete().ScanVal(&id) - assert.Equal(t, err.Error(), "goqu: Adapter does not support RETURNING clause") + _, err = ds.Where(goqu.C("id").Eq(e.ID)).Returning("id").Delete().ScanVal(&id) + assert.Equal(t, err.Error(), "goqu: adapter does not support RETURNING clause") } func TestSqlite3Suite(t *testing.T) { - suite.Run(t, new(sqlite3Test)) + suite.Run(t, new(sqlite3Suite)) } diff --git a/errors.go b/errors.go deleted file mode 100644 index 285a0848..00000000 --- a/errors.go +++ /dev/null @@ -1,28 +0,0 @@ -package goqu - -import "fmt" - -func newEncodeError(message string, args ...interface{}) error { - return EncodeError{err: "goqu: " + fmt.Sprintf(message, args...)} -} - -func NewGoquError(message string, args ...interface{}) error { - return GoquError{err: "goqu: " + fmt.Sprintf(message, args...)} -} - -type EncodeError struct { - error - err string -} - -func (me EncodeError) Error() string { - return me.err -} - -type GoquError struct { - err string -} - -func (me GoquError) Error() string { - return me.err -} diff --git a/example_test.go b/example_test.go deleted file mode 100644 index dc120368..00000000 --- a/example_test.go +++ /dev/null @@ -1,1888 +0,0 @@ -package goqu_test - -import ( - "database/sql" - "fmt" - "regexp" - "strings" - - "github.com/doug-martin/goqu/v6" - - "github.com/DATA-DOG/go-sqlmock" -) - -var driver *sql.DB - -func init() { - db, _, _ := sqlmock.New() - driver = db -} - -func ExampleOr() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Where(goqu.Ex{ - "a": goqu.Op{"gt": 10, "lt": 5}, - }).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where( - goqu.Or( - goqu.I("a").Gt(10), - goqu.I("a").Lt(5), - ), - ).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" WHERE (("a" > 10) OR ("a" < 5)) - // SELECT * FROM "test" WHERE (("a" > 10) OR ("a" < 5)) -} - -func ExampleOr_withAnd() { - db := goqu.New("default", driver) - sql, _, _ := db.From("items").Where( - goqu.Or( - goqu.I("a").Gt(10), - goqu.Ex{ - "b": 100, - "c": goqu.Op{"neq": "test"}, - }, - ), - ).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("items").Where( - goqu.Or( - goqu.I("a").Gt(10), - goqu.And( - goqu.I("b").Eq(100), - goqu.I("c").Neq("test"), - ), - ), - ).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "items" WHERE (("a" > 10) OR (("b" = 100) AND ("c" != 'test'))) - // SELECT * FROM "items" WHERE (("a" > 10) OR (("b" = 100) AND ("c" != 'test'))) -} - -func ExampleAnd() { - db := goqu.New("default", driver) - //by default Where assumes an And - sql, _, _ := db.From("test").Where(goqu.Ex{ - "a": goqu.Op{"gt": 10}, - "b": goqu.Op{"lt": 5}, - }).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where( - goqu.I("a").Gt(10), - goqu.I("b").Lt(5), - ).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" WHERE (("a" > 10) AND ("b" < 5)) - // SELECT * FROM "test" WHERE (("a" > 10) AND ("b" < 5)) -} - -func ExampleAnd_withOr() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Where( - goqu.I("a").Gt(10), - goqu.Or( - goqu.I("b").Lt(5), - goqu.I("c").In([]string{"hello", "world"}), - ), - ).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" WHERE (("a" > 10) AND (("b" < 5) OR ("c" IN ('hello', 'world')))) -} - -func ExampleI() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Where( - goqu.I("a").Eq(10), - goqu.I("b").Lt(10), - goqu.I("d").IsTrue(), - ).ToSql() - fmt.Println(sql) - - //qualify with schema - sql, _, _ = db.From(goqu.I("test").Schema("my_schema")).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From(goqu.I("mychema.test")).Where( - //qualify with schema, table, and col - goqu.I("my_schema.test.a").Eq(10), - ).ToSql() - fmt.Println(sql) - - //* will be taken literally and no quoted - sql, _, _ = db.From(goqu.I("test")).Select(goqu.I("test.*")).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" WHERE (("a" = 10) AND ("b" < 10) AND ("d" IS TRUE)) - // SELECT * FROM "my_schema"."test" - // SELECT * FROM "mychema"."test" WHERE ("my_schema"."test"."a" = 10) - // SELECT "test".* FROM "test" - -} - -func ExampleAliasMethods() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Select(goqu.I("a").As("as_a")).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Select(goqu.COUNT("*").As("count")).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Select(goqu.L("sum(amount)").As("total_amount")).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Select(goqu.I("a").As(goqu.I("as_a"))).ToSql() - fmt.Println(sql) - // Output: - // SELECT "a" AS "as_a" FROM "test" - // SELECT COUNT(*) AS "count" FROM "test" - // SELECT sum(amount) AS "total_amount" FROM "test" - // SELECT "a" AS "as_a" FROM "test" - -} - -func ExampleComparisonMethods() { - db := goqu.New("default", driver) - //used from an identifier - sql, _, _ := db.From("test").Where(goqu.I("a").Eq(10)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").Neq(10)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").Gt(10)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").Gte(10)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").Lt(10)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").Lte(10)).ToSql() - fmt.Println(sql) - //used from a literal expression - sql, _, _ = db.From("test").Where(goqu.L("(a + b)").Eq(10)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.L("(a + b)").Neq(10)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.L("(a + b)").Gt(10)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.L("(a + b)").Gte(10)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.L("(a + b)").Lt(10)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.L("(a + b)").Lte(10)).ToSql() - fmt.Println(sql) - - //used with Ex expression map - sql, _, _ = db.From("test").Where(goqu.Ex{ - "a": 10, - "b": goqu.Op{"neq": 10}, - "c": goqu.Op{"gte": 10}, - "d": goqu.Op{"lt": 10}, - "e": goqu.Op{"lte": 10}, - }).ToSql() - fmt.Println(sql) - - // Output: - // SELECT * FROM "test" WHERE ("a" = 10) - // SELECT * FROM "test" WHERE ("a" != 10) - // SELECT * FROM "test" WHERE ("a" > 10) - // SELECT * FROM "test" WHERE ("a" >= 10) - // SELECT * FROM "test" WHERE ("a" < 10) - // SELECT * FROM "test" WHERE ("a" <= 10) - // SELECT * FROM "test" WHERE ((a + b) = 10) - // SELECT * FROM "test" WHERE ((a + b) != 10) - // SELECT * FROM "test" WHERE ((a + b) > 10) - // SELECT * FROM "test" WHERE ((a + b) >= 10) - // SELECT * FROM "test" WHERE ((a + b) < 10) - // SELECT * FROM "test" WHERE ((a + b) <= 10) - // SELECT * FROM "test" WHERE (("a" = 10) AND ("b" != 10) AND ("c" >= 10) AND ("d" < 10) AND ("e" <= 10)) -} - -func ExampleInMethods() { - db := goqu.New("default", driver) - //using identifiers - sql, _, _ := db.From("test").Where(goqu.I("a").In("a", "b", "c")).ToSql() - fmt.Println(sql) - - //with a slice - sql, _, _ = db.From("test").Where(goqu.I("a").In([]string{"a", "b", "c"})).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").NotIn("a", "b", "c")).ToSql() - fmt.Println(sql) - - //with a slice - sql, _, _ = db.From("test").Where(goqu.I("a").NotIn([]string{"a", "b", "c"})).ToSql() - fmt.Println(sql) - - //using an Ex expression map - sql, _, _ = db.From("test").Where(goqu.Ex{ - "a": []string{"a", "b", "c"}, - }).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").Where(goqu.Ex{ - "a": goqu.Op{"notIn": []string{"a", "b", "c"}}, - }).ToSql() - fmt.Println(sql) - - // Output: - // SELECT * FROM "test" WHERE ("a" IN ('a', 'b', 'c')) - // SELECT * FROM "test" WHERE ("a" IN ('a', 'b', 'c')) - // SELECT * FROM "test" WHERE ("a" NOT IN ('a', 'b', 'c')) - // SELECT * FROM "test" WHERE ("a" NOT IN ('a', 'b', 'c')) - // SELECT * FROM "test" WHERE ("a" IN ('a', 'b', 'c')) - // SELECT * FROM "test" WHERE ("a" NOT IN ('a', 'b', 'c')) -} - -func ExampleOrderedMethods() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Order(goqu.I("a").Asc()).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Order(goqu.I("a").Desc()).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Order(goqu.I("a").Desc().NullsFirst()).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Order(goqu.I("a").Desc().NullsLast()).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" ORDER BY "a" ASC - // SELECT * FROM "test" ORDER BY "a" DESC - // SELECT * FROM "test" ORDER BY "a" DESC NULLS FIRST - // SELECT * FROM "test" ORDER BY "a" DESC NULLS LAST -} - -func ExampleRangeMethods() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Where(goqu.I("name").Between(goqu.RangeVal{Start: "a", End: "b"})).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("name").NotBetween(goqu.RangeVal{Start: "a", End: "b"})).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("x").Between(goqu.RangeVal{Start: goqu.I("y"), End: goqu.I("z")})).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("x").NotBetween(goqu.RangeVal{Start: goqu.I("y"), End: goqu.I("z")})).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.L("(a + b)").Between(goqu.RangeVal{Start: 10, End: 100})).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.L("(a + b)").NotBetween(goqu.RangeVal{Start: 10, End: 100})).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" WHERE ("name" BETWEEN 'a' AND 'b') - // SELECT * FROM "test" WHERE ("name" NOT BETWEEN 'a' AND 'b') - // SELECT * FROM "test" WHERE ("x" BETWEEN "y" AND "z") - // SELECT * FROM "test" WHERE ("x" NOT BETWEEN "y" AND "z") - // SELECT * FROM "test" WHERE ((a + b) BETWEEN 10 AND 100) - // SELECT * FROM "test" WHERE ((a + b) NOT BETWEEN 10 AND 100) -} - -func ExampleRangeMethods_Ex() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Where(goqu.Ex{"name": goqu.Op{"between": goqu.RangeVal{Start: "a", End: "b"}}}).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.Ex{"name": goqu.Op{"notBetween": goqu.RangeVal{Start: "a", End: "b"}}}).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.Ex{"x": goqu.Op{"between": goqu.RangeVal{Start: goqu.I("y"), End: goqu.I("z")}}}).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.Ex{"x": goqu.Op{"notBetween": goqu.RangeVal{Start: goqu.I("y"), End: goqu.I("z")}}}).ToSql() - fmt.Println(sql) - - // Output: - // SELECT * FROM "test" WHERE ("name" BETWEEN 'a' AND 'b') - // SELECT * FROM "test" WHERE ("name" NOT BETWEEN 'a' AND 'b') - // SELECT * FROM "test" WHERE ("x" BETWEEN "y" AND "z") - // SELECT * FROM "test" WHERE ("x" NOT BETWEEN "y" AND "z") -} - -func ExampleStringMethods() { - db := goqu.New("default", driver) - //using identifiers - sql, _, _ := db.From("test").Where(goqu.I("a").Like("%a%")).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").Like(regexp.MustCompile("(a|b)"))).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").NotLike("%a%")).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").NotLike(regexp.MustCompile("(a|b)"))).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").ILike("%a%")).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").ILike(regexp.MustCompile("(a|b)"))).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").NotILike("%a%")).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").NotILike(regexp.MustCompile("(a|b)"))).ToSql() - fmt.Println(sql) - - //using an Ex expression map - sql, _, _ = db.From("test").Where(goqu.Ex{ - "a": goqu.Op{"like": "%a%"}, - "b": goqu.Op{"like": regexp.MustCompile("(a|b)")}, - "c": goqu.Op{"iLike": "%a%"}, - "d": goqu.Op{"iLike": regexp.MustCompile("(a|b)")}, - "e": goqu.Op{"notlike": "%a%"}, - "f": goqu.Op{"notLike": regexp.MustCompile("(a|b)")}, - "g": goqu.Op{"notILike": "%a%"}, - "h": goqu.Op{"notILike": regexp.MustCompile("(a|b)")}, - }).ToSql() - fmt.Println(sql) - - // Output: - // SELECT * FROM "test" WHERE ("a" LIKE '%a%') - // SELECT * FROM "test" WHERE ("a" ~ '(a|b)') - // SELECT * FROM "test" WHERE ("a" NOT LIKE '%a%') - // SELECT * FROM "test" WHERE ("a" !~ '(a|b)') - // SELECT * FROM "test" WHERE ("a" ILIKE '%a%') - // SELECT * FROM "test" WHERE ("a" ~* '(a|b)') - // SELECT * FROM "test" WHERE ("a" NOT ILIKE '%a%') - // SELECT * FROM "test" WHERE ("a" !~* '(a|b)') - // SELECT * FROM "test" WHERE (("a" LIKE '%a%') AND ("b" ~ '(a|b)') AND ("c" ILIKE '%a%') AND ("d" ~* '(a|b)') AND ("e" NOT LIKE '%a%') AND ("f" !~ '(a|b)') AND ("g" NOT ILIKE '%a%') AND ("h" !~* '(a|b)')) -} - -func ExampleBooleanMethods() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Where(goqu.I("a").Is(nil)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").Is(true)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").Is(false)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").IsNot(nil)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").IsNot(true)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").IsNull(), goqu.I("b").IsNull()).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").IsTrue(), goqu.I("b").IsNotTrue()).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.I("a").IsFalse(), goqu.I("b").IsNotFalse()).ToSql() - fmt.Println(sql) - - //with an ex expression map - sql, _, _ = db.From("test").Where(goqu.Ex{ - "a": true, - "b": false, - "c": nil, - "d": goqu.Op{"isNot": true}, - "e": goqu.Op{"isNot": false}, - "f": goqu.Op{"isNot": nil}, - }).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" WHERE ("a" IS NULL) - // SELECT * FROM "test" WHERE ("a" IS TRUE) - // SELECT * FROM "test" WHERE ("a" IS FALSE) - // SELECT * FROM "test" WHERE ("a" IS NOT NULL) - // SELECT * FROM "test" WHERE ("a" IS NOT TRUE) - // SELECT * FROM "test" WHERE (("a" IS NULL) AND ("b" IS NULL)) - // SELECT * FROM "test" WHERE (("a" IS TRUE) AND ("b" IS NOT TRUE)) - // SELECT * FROM "test" WHERE (("a" IS FALSE) AND ("b" IS NOT FALSE)) - // SELECT * FROM "test" WHERE (("a" IS TRUE) AND ("b" IS FALSE) AND ("c" IS NULL) AND ("d" IS NOT TRUE) AND ("e" IS NOT FALSE) AND ("f" IS NOT NULL)) -} - -func ExampleCastMethods() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Where(goqu.I("json1").Cast("TEXT").Neq(goqu.I("json2").Cast("TEXT"))).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" WHERE (CAST("json1" AS TEXT) != CAST("json2" AS TEXT)) -} - -func ExampleCast() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Where(goqu.I("json1").Cast("TEXT").Neq(goqu.I("json2").Cast("TEXT"))).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" WHERE (CAST("json1" AS TEXT) != CAST("json2" AS TEXT)) -} - -func ExampleDistinctMethods() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Select(goqu.COUNT(goqu.I("a").Distinct())).ToSql() - fmt.Println(sql) - // Output: - // SELECT COUNT(DISTINCT("a")) FROM "test" -} - -func ExampleL() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Where(goqu.L("a = 1")).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where(goqu.L("a = 1 AND (b = ? OR ? = ?)", "a", goqu.I("c"), 0.01)).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Where( - goqu.L( - "(? AND ?) OR ?", - goqu.I("a").Eq(1), - goqu.I("b").Eq("b"), - goqu.I("c").In([]string{"a", "b", "c"}), - ), - ).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" WHERE a = 1 - // SELECT * FROM "test" WHERE a = 1 AND (b = 'a' OR "c" = 0.01) - // SELECT * FROM "test" WHERE (("a" = 1) AND ("b" = 'b')) OR ("c" IN ('a', 'b', 'c')) -} - -func ExampleOn() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Join( - goqu.I("my_table"), - goqu.On(goqu.Ex{"my_table.fkey": goqu.I("test.id")}), - ).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("test").Join( - goqu.I("my_table"), - goqu.On(goqu.I("my_table.fkey").Eq(goqu.I("test.id"))), - ).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" INNER JOIN "my_table" ON ("my_table"."fkey" = "test"."id") - // SELECT * FROM "test" INNER JOIN "my_table" ON ("my_table"."fkey" = "test"."id") -} - -func ExampleUsing() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Join(goqu.I("my_table"), goqu.Using(goqu.I("common_column"))).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" INNER JOIN "my_table" USING ("common_column") -} - -func ExampleDataset_As() { - db := goqu.New("default", driver) - ds := db.From("test").As("t") - sql, _, _ := db.From(ds).ToSql() - fmt.Println(sql) - // Output: SELECT * FROM (SELECT * FROM "test") AS "t" -} - -func ExampleDataset_Returning() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test"). - Returning("id"). - ToInsertSql(goqu.Record{"a": "a", "b": "b"}) - fmt.Println(sql) - sql, _, _ = db.From("test"). - Returning(goqu.I("test.*")). - ToInsertSql(goqu.Record{"a": "a", "b": "b"}) - fmt.Println(sql) - sql, _, _ = db.From("test"). - Returning("a", "b"). - ToInsertSql(goqu.Record{"a": "a", "b": "b"}) - fmt.Println(sql) - // Output: - // INSERT INTO "test" ("a", "b") VALUES ('a', 'b') RETURNING "id" - // INSERT INTO "test" ("a", "b") VALUES ('a', 'b') RETURNING "test".* - // INSERT INTO "test" ("a", "b") VALUES ('a', 'b') RETURNING "a", "b" -} - -func ExampleDataset_Union() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test"). - Union(db.From("test2")). - ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test"). - Limit(1). - Union(db.From("test2")). - ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test"). - Limit(1). - Union(db.From("test2"). - Order(goqu.I("id").Desc())). - ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" UNION (SELECT * FROM "test2") - // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" UNION (SELECT * FROM "test2") - // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" UNION (SELECT * FROM (SELECT * FROM "test2" ORDER BY "id" DESC) AS "t1") -} - -func ExampleDataset_UnionAll() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test"). - UnionAll(db.From("test2")). - ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test"). - Limit(1). - UnionAll(db.From("test2")). - ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test"). - Limit(1). - UnionAll(db.From("test2"). - Order(goqu.I("id").Desc())). - ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" UNION ALL (SELECT * FROM "test2") - // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" UNION ALL (SELECT * FROM "test2") - // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" UNION ALL (SELECT * FROM (SELECT * FROM "test2" ORDER BY "id" DESC) AS "t1") -} - -func ExampleDataset_WithCTE() { - db := goqu.New("default", driver) - sql, _, _ := db.From("one"). - With("one", db.From().Select(goqu.L("1"))). - Select(goqu.Star()). - ToSql() - fmt.Println(sql) - sql, _, _ = db.From("derived"). - With("intermed", db.From("test").Select(goqu.Star()).Where(goqu.I("x").Gte(5))). - With("derived", db.From("intermed").Select(goqu.Star()).Where(goqu.I("x").Lt(10))). - Select(goqu.Star()). - ToSql() - fmt.Println(sql) - sql, _, _ = db.From("multi"). - With("multi(x,y)", db.From().Select(goqu.L("1"), goqu.L("2"))). - Select(goqu.I("x"), goqu.I("y")). - ToSql() - fmt.Println(sql) - // Output: - // WITH one AS (SELECT 1) SELECT * FROM "one" - // WITH intermed AS (SELECT * FROM "test" WHERE ("x" >= 5)), derived AS (SELECT * FROM "intermed" WHERE ("x" < 10)) SELECT * FROM "derived" - // WITH multi(x,y) AS (SELECT 1, 2) SELECT "x", "y" FROM "multi" -} - -func ExampleDataset_ModifyWithCTE() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test"). - With("moved_rows", db.From("other").Where(goqu.I("date").Lt(123))). - ToInsertSql(db.From("moved_rows")) - fmt.Println(sql) - sql, _, _ = db.From("test"). - With("check_vals(val)", db.From().Select(goqu.L("123"))). - Where(goqu.I("val").Eq(db.From("check_vals").Select("val"))). - ToDeleteSql() - fmt.Println(sql) - sql, _, _ = db.From("test"). - With("some_vals(val)", db.From().Select(goqu.L("123"))). - Where(goqu.I("val").Eq(db.From("some_vals").Select("val"))). - ToUpdateSql(goqu.Record{"name": "Test"}) - fmt.Println(sql) - // Output: - // WITH moved_rows AS (SELECT * FROM "other" WHERE ("date" < 123)) INSERT INTO "test" SELECT * FROM "moved_rows" - // WITH check_vals(val) AS (SELECT 123) DELETE FROM "test" WHERE ("val" IN (SELECT "val" FROM "check_vals")) - // WITH some_vals(val) AS (SELECT 123) UPDATE "test" SET "name"='Test' WHERE ("val" IN (SELECT "val" FROM "some_vals")) -} - -func ExampleDataset_WithCTERecursive() { - db := goqu.New("default", driver) - sql, _, _ := db.From("nums"). - WithRecursive("nums(x)", - db.From().Select(goqu.L("1")). - UnionAll(db.From("nums"). - Select(goqu.L("x+1")).Where(goqu.I("x").Lt(5)))). - ToSql() - fmt.Println(sql) - // Output: - // WITH RECURSIVE nums(x) AS (SELECT 1 UNION ALL (SELECT x+1 FROM "nums" WHERE ("x" < 5))) SELECT * FROM "nums" -} - -func ExampleDataset_Intersect() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test"). - Intersect(db.From("test2")). - ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test"). - Limit(1). - Intersect(db.From("test2")). - ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test"). - Limit(1). - Intersect(db.From("test2"). - Order(goqu.I("id").Desc())). - ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" INTERSECT (SELECT * FROM "test2") - // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" INTERSECT (SELECT * FROM "test2") - // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" INTERSECT (SELECT * FROM (SELECT * FROM "test2" ORDER BY "id" DESC) AS "t1") -} - -func ExampleDataset_IntersectAll() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test"). - IntersectAll(db.From("test2")). - ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test"). - Limit(1). - IntersectAll(db.From("test2")). - ToSql() - fmt.Println(sql) - sql, _, _ = goqu. - From("test"). - Limit(1). - IntersectAll(db.From("test2"). - Order(goqu.I("id").Desc())). - ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" INTERSECT ALL (SELECT * FROM "test2") - // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" INTERSECT ALL (SELECT * FROM "test2") - // SELECT * FROM (SELECT * FROM "test" LIMIT 1) AS "t1" INTERSECT ALL (SELECT * FROM (SELECT * FROM "test2" ORDER BY "id" DESC) AS "t1") -} - -func ExampleDataset_ClearOffset() { - db := goqu.New("default", driver) - ds := db.From("test"). - Offset(2) - sql, _, _ := ds. - ClearOffset(). - ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" -} - -func ExampleDataset_Offset() { - db := goqu.New("default", driver) - ds := db.From("test"). - Offset(2) - sql, _, _ := ds.ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" OFFSET 2 -} - -func ExampleDataset_Limit() { - db := goqu.New("default", driver) - ds := db.From("test").Limit(10) - sql, _, _ := ds.ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" LIMIT 10 -} - -func ExampleDataset_LimitAll() { - db := goqu.New("default", driver) - ds := db.From("test").LimitAll() - sql, _, _ := ds.ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" LIMIT ALL -} - -func ExampleDataset_ClearLimit() { - db := goqu.New("default", driver) - ds := db.From("test").Limit(10) - sql, _, _ := ds.ClearLimit().ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" -} - -func ExampleDataset_Order() { - db := goqu.New("default", driver) - ds := db.From("test"). - Order(goqu.I("a").Asc()) - sql, _, _ := ds.ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" ORDER BY "a" ASC -} - -func ExampleDataset_OrderAppend() { - db := goqu.New("default", driver) - ds := db.From("test").Order(goqu.I("a").Asc()) - sql, _, _ := ds.OrderAppend(goqu.I("b").Desc().NullsLast()).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" ORDER BY "a" ASC, "b" DESC NULLS LAST -} - -func ExampleDataset_ClearOrder() { - db := goqu.New("default", driver) - ds := db.From("test").Order(goqu.I("a").Asc()) - sql, _, _ := ds.ClearOrder().ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" -} - -func ExampleDataset_Having() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Having(goqu.SUM("income").Gt(1000)).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").GroupBy("age").Having(goqu.SUM("income").Gt(1000)).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" HAVING (SUM("income") > 1000) - // SELECT * FROM "test" GROUP BY "age" HAVING (SUM("income") > 1000) -} - -func ExampleDataset_Where() { - db := goqu.New("default", driver) - - //By default everything is anded together - sql, _, _ := db.From("test").Where(goqu.Ex{ - "a": goqu.Op{"gt": 10}, - "b": goqu.Op{"lt": 10}, - "c": nil, - "d": []string{"a", "b", "c"}, - }).ToSql() - fmt.Println(sql) - - //You can use ExOr to get ORed expressions together - sql, _, _ = db.From("test").Where(goqu.ExOr{ - "a": goqu.Op{"gt": 10}, - "b": goqu.Op{"lt": 10}, - "c": nil, - "d": []string{"a", "b", "c"}, - }).ToSql() - fmt.Println(sql) - - //You can use Or with Ex to Or multiple Ex maps together - sql, _, _ = db.From("test").Where( - goqu.Or( - goqu.Ex{ - "a": goqu.Op{"gt": 10}, - "b": goqu.Op{"lt": 10}, - }, - goqu.Ex{ - "c": nil, - "d": []string{"a", "b", "c"}, - }, - ), - ).ToSql() - fmt.Println(sql) - - //By default everything is anded together - sql, _, _ = db.From("test").Where( - goqu.I("a").Gt(10), - goqu.I("b").Lt(10), - goqu.I("c").IsNull(), - goqu.I("d").In("a", "b", "c"), - ).ToSql() - fmt.Println(sql) - - //You can use a combination of Ors and Ands - sql, _, _ = db.From("test").Where( - goqu.Or( - goqu.I("a").Gt(10), - goqu.And( - goqu.I("b").Lt(10), - goqu.I("c").IsNull(), - ), - ), - ).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" WHERE (("a" > 10) AND ("b" < 10) AND ("c" IS NULL) AND ("d" IN ('a', 'b', 'c'))) - // SELECT * FROM "test" WHERE (("a" > 10) OR ("b" < 10) OR ("c" IS NULL) OR ("d" IN ('a', 'b', 'c'))) - // SELECT * FROM "test" WHERE ((("a" > 10) AND ("b" < 10)) OR (("c" IS NULL) AND ("d" IN ('a', 'b', 'c')))) - // SELECT * FROM "test" WHERE (("a" > 10) AND ("b" < 10) AND ("c" IS NULL) AND ("d" IN ('a', 'b', 'c'))) - // SELECT * FROM "test" WHERE (("a" > 10) OR (("b" < 10) AND ("c" IS NULL))) -} - -func ExampleDataset_Where_prepared() { - db := goqu.New("default", driver) - - //By default everything is anded together - sql, args, _ := db.From("test").Prepared(true).Where(goqu.Ex{ - "a": goqu.Op{"gt": 10}, - "b": goqu.Op{"lt": 10}, - "c": nil, - "d": []string{"a", "b", "c"}, - }).ToSql() - fmt.Println(sql, args) - - //You can use ExOr to get ORed expressions together - sql, args, _ = db.From("test").Prepared(true).Where(goqu.ExOr{ - "a": goqu.Op{"gt": 10}, - "b": goqu.Op{"lt": 10}, - "c": nil, - "d": []string{"a", "b", "c"}, - }).ToSql() - fmt.Println(sql, args) - - //You can use Or with Ex to Or multiple Ex maps together - sql, args, _ = db.From("test").Prepared(true).Where( - goqu.Or( - goqu.Ex{ - "a": goqu.Op{"gt": 10}, - "b": goqu.Op{"lt": 10}, - }, - goqu.Ex{ - "c": nil, - "d": []string{"a", "b", "c"}, - }, - ), - ).ToSql() - fmt.Println(sql, args) - - //By default everything is anded together - sql, args, _ = db.From("test").Prepared(true).Where( - goqu.I("a").Gt(10), - goqu.I("b").Lt(10), - goqu.I("c").IsNull(), - goqu.I("d").In("a", "b", "c"), - ).ToSql() - fmt.Println(sql, args) - - //You can use a combination of Ors and Ands - sql, args, _ = db.From("test").Prepared(true).Where( - goqu.Or( - goqu.I("a").Gt(10), - goqu.And( - goqu.I("b").Lt(10), - goqu.I("c").IsNull(), - ), - ), - ).ToSql() - fmt.Println(sql, args) - // Output: - // SELECT * FROM "test" WHERE (("a" > ?) AND ("b" < ?) AND ("c" IS NULL) AND ("d" IN (?, ?, ?))) [10 10 a b c] - // SELECT * FROM "test" WHERE (("a" > ?) OR ("b" < ?) OR ("c" IS NULL) OR ("d" IN (?, ?, ?))) [10 10 a b c] - // SELECT * FROM "test" WHERE ((("a" > ?) AND ("b" < ?)) OR (("c" IS NULL) AND ("d" IN (?, ?, ?)))) [10 10 a b c] - // SELECT * FROM "test" WHERE (("a" > ?) AND ("b" < ?) AND ("c" IS NULL) AND ("d" IN (?, ?, ?))) [10 10 a b c] - // SELECT * FROM "test" WHERE (("a" > ?) OR (("b" < ?) AND ("c" IS NULL))) [10 10] -} - -func ExampleDataset_ClearWhere() { - db := goqu.New("default", driver) - ds := db.From("test").Where( - goqu.Or( - goqu.I("a").Gt(10), - goqu.And( - goqu.I("b").Lt(10), - goqu.I("c").IsNull(), - ), - ), - ) - sql, _, _ := ds.ClearWhere().ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" -} - -func ExampleDataset_Join() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Join(goqu.I("test2"), goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.Id")})).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").Join(goqu.I("test2"), goqu.Using("common_column")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").Join(db.From("test2").Where(goqu.I("amount").Gt(0)), goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id")))).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").Join(db.From("test2").Where(goqu.I("amount").Gt(0)).As("t"), goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id")))).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" INNER JOIN "test2" ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" INNER JOIN "test2" USING ("common_column") - // SELECT * FROM "test" INNER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" INNER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") - -} - -func ExampleDataset_InnerJoin() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").InnerJoin(goqu.I("test2"), goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.Id")})).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").InnerJoin(goqu.I("test2"), goqu.Using("common_column")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").InnerJoin(db.From("test2").Where(goqu.I("amount").Gt(0)), goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id")))).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").InnerJoin(db.From("test2").Where(goqu.I("amount").Gt(0)).As("t"), goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id")))).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" INNER JOIN "test2" ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" INNER JOIN "test2" USING ("common_column") - // SELECT * FROM "test" INNER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" INNER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") -} - -func ExampleDataset_FullOuterJoin() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").FullOuterJoin(goqu.I("test2"), goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.Id")})).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").FullOuterJoin(goqu.I("test2"), goqu.Using("common_column")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").FullOuterJoin(db.From("test2").Where(goqu.I("amount").Gt(0)), goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id")))).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").FullOuterJoin(db.From("test2").Where(goqu.I("amount").Gt(0)).As("t"), goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id")))).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" FULL OUTER JOIN "test2" ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" FULL OUTER JOIN "test2" USING ("common_column") - // SELECT * FROM "test" FULL OUTER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" FULL OUTER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") -} - -func ExampleDataset_RightOuterJoin() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").RightOuterJoin(goqu.I("test2"), goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.Id")})).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").RightOuterJoin(goqu.I("test2"), goqu.Using("common_column")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").RightOuterJoin( - db.From("test2").Where(goqu.I("amount").Gt(0)), - goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id"))), - ).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").RightOuterJoin( - db.From("test2").Where(goqu.I("amount").Gt(0)).As("t"), - goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id"))), - ).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" RIGHT OUTER JOIN "test2" ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" RIGHT OUTER JOIN "test2" USING ("common_column") - // SELECT * FROM "test" RIGHT OUTER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" RIGHT OUTER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") -} - -func ExampleDataset_LeftOuterJoin() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").LeftOuterJoin(goqu.I("test2"), goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.Id")})).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").LeftOuterJoin(goqu.I("test2"), goqu.Using("common_column")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").LeftOuterJoin(db.From("test2").Where(goqu.I("amount").Gt(0)), goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id")))).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").LeftOuterJoin(db.From("test2").Where(goqu.I("amount").Gt(0)).As("t"), goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id")))).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" LEFT OUTER JOIN "test2" ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" LEFT OUTER JOIN "test2" USING ("common_column") - // SELECT * FROM "test" LEFT OUTER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" LEFT OUTER JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") -} - -func ExampleDataset_FullJoin() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").FullJoin(goqu.I("test2"), goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.Id")})).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").FullJoin(goqu.I("test2"), goqu.Using("common_column")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").FullJoin(db.From("test2").Where(goqu.I("amount").Gt(0)), goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id")))).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").FullJoin(db.From("test2").Where(goqu.I("amount").Gt(0)).As("t"), goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id")))).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" FULL JOIN "test2" ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" FULL JOIN "test2" USING ("common_column") - // SELECT * FROM "test" FULL JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" FULL JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") -} - -func ExampleDataset_RightJoin() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").RightJoin(goqu.I("test2"), goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.Id")})).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").RightJoin(goqu.I("test2"), goqu.Using("common_column")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").RightJoin(db.From("test2").Where(goqu.I("amount").Gt(0)), goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id")))).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").RightJoin(db.From("test2").Where(goqu.I("amount").Gt(0)).As("t"), goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id")))).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" RIGHT JOIN "test2" ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" RIGHT JOIN "test2" USING ("common_column") - // SELECT * FROM "test" RIGHT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" RIGHT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") -} - -func ExampleDataset_LeftJoin() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").LeftJoin(goqu.I("test2"), goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.Id")})).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").LeftJoin(goqu.I("test2"), goqu.Using("common_column")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").LeftJoin(db.From("test2").Where(goqu.I("amount").Gt(0)), goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.Id")))).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").LeftJoin(db.From("test2").Where(goqu.I("amount").Gt(0)).As("t"), goqu.On(goqu.I("test.fkey").Eq(goqu.I("t.Id")))).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" LEFT JOIN "test2" ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" LEFT JOIN "test2" USING ("common_column") - // SELECT * FROM "test" LEFT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) ON ("test"."fkey" = "test2"."Id") - // SELECT * FROM "test" LEFT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" ON ("test"."fkey" = "t"."Id") -} - -func ExampleDataset_NaturalJoin() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").NaturalJoin(goqu.I("test2")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").NaturalJoin(db.From("test2").Where(goqu.I("amount").Gt(0))).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").NaturalJoin(db.From("test2").Where(goqu.I("amount").Gt(0)).As("t")).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" NATURAL JOIN "test2" - // SELECT * FROM "test" NATURAL JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) - // SELECT * FROM "test" NATURAL JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" -} - -func ExampleDataset_NaturalLeftJoin() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").NaturalLeftJoin(goqu.I("test2")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").NaturalLeftJoin(db.From("test2").Where(goqu.I("amount").Gt(0))).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").NaturalLeftJoin(db.From("test2").Where(goqu.I("amount").Gt(0)).As("t")).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" NATURAL LEFT JOIN "test2" - // SELECT * FROM "test" NATURAL LEFT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) - // SELECT * FROM "test" NATURAL LEFT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" -} - -func ExampleDataset_NaturalRightJoin() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").NaturalRightJoin(goqu.I("test2")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").NaturalRightJoin(db.From("test2").Where(goqu.I("amount").Gt(0))).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").NaturalRightJoin(db.From("test2").Where(goqu.I("amount").Gt(0)).As("t")).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" NATURAL RIGHT JOIN "test2" - // SELECT * FROM "test" NATURAL RIGHT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) - // SELECT * FROM "test" NATURAL RIGHT JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" -} - -func ExampleDataset_NaturalFullJoin() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").NaturalFullJoin(goqu.I("test2")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").NaturalFullJoin(db.From("test2").Where(goqu.I("amount").Gt(0))).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").NaturalFullJoin(db.From("test2").Where(goqu.I("amount").Gt(0)).As("t")).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" NATURAL FULL JOIN "test2" - // SELECT * FROM "test" NATURAL FULL JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) - // SELECT * FROM "test" NATURAL FULL JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" -} - -func ExampleDataset_CrossJoin() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").CrossJoin(goqu.I("test2")).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").CrossJoin(db.From("test2").Where(goqu.I("amount").Gt(0))).ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").CrossJoin(db.From("test2").Where(goqu.I("amount").Gt(0)).As("t")).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" CROSS JOIN "test2" - // SELECT * FROM "test" CROSS JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) - // SELECT * FROM "test" CROSS JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" -} - -func ExampleDataset_FromSelf() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").FromSelf().ToSql() - fmt.Println(sql) - sql, _, _ = db.From("test").As("my_test_table").FromSelf().ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM (SELECT * FROM "test") AS "t1" - // SELECT * FROM (SELECT * FROM "test") AS "my_test_table" -} - -func ExampleDataset_From() { - db := goqu.New("default", driver) - ds := db.From("test") - sql, _, _ := ds.From("test2").ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test2" -} - -func ExampleDataset_From_withDataset() { - db := goqu.New("default", driver) - ds := db.From("test") - fromDs := ds.Where(goqu.I("age").Gt(10)) - sql, _, _ := ds.From(fromDs).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM (SELECT * FROM "test" WHERE ("age" > 10)) AS "t1" -} - -func ExampleDataset_From_withAliasedDataset() { - db := goqu.New("default", driver) - ds := db.From("test") - fromDs := ds.Where(goqu.I("age").Gt(10)) - sql, _, _ := ds.From(fromDs.As("test2")).ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM (SELECT * FROM "test" WHERE ("age" > 10)) AS "test2" -} - -func ExampleDataset_Select() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Select("a", "b", "c").ToSql() - fmt.Println(sql) - // Output: - // SELECT "a", "b", "c" FROM "test" -} - -func ExampleDataset_Select_withDataset() { - db := goqu.New("default", driver) - ds := db.From("test") - fromDs := ds.Select("age").Where(goqu.I("age").Gt(10)) - sql, _, _ := ds.From().Select(fromDs).ToSql() - fmt.Println(sql) - // Output: - // SELECT (SELECT "age" FROM "test" WHERE ("age" > 10)) -} - -func ExampleDataset_Select_withAliasedDataset() { - db := goqu.New("default", driver) - ds := db.From("test") - fromDs := ds.Select("age").Where(goqu.I("age").Gt(10)) - sql, _, _ := ds.From().Select(fromDs.As("ages")).ToSql() - fmt.Println(sql) - // Output: - // SELECT (SELECT "age" FROM "test" WHERE ("age" > 10)) AS "ages" -} - -func ExampleDataset_Select_withLiteral() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Select(goqu.L("a + b").As("sum")).ToSql() - fmt.Println(sql) - // Output: - // SELECT a + b AS "sum" FROM "test" -} - -func ExampleDataset_Select_withSqlFunctionExpression() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").Select( - goqu.COUNT("*").As("age_count"), - goqu.MAX("age").As("max_age"), - goqu.AVG("age").As("avg_age"), - ).ToSql() - fmt.Println(sql) - // Output: - // SELECT COUNT(*) AS "age_count", MAX("age") AS "max_age", AVG("age") AS "avg_age" FROM "test" -} - -func ExampleDataset_Select_withStruct() { - db := goqu.New("default", driver) - ds := db.From("test") - - type myStruct struct { - Name string - Address string `db:"address"` - EmailAddress string `db:"email_address"` - } - - // Pass with pointer - sql, _, _ := ds.Select(&myStruct{}).ToSql() - fmt.Println(sql) - - // Pass instance of - sql, _, _ = ds.Select(myStruct{}).ToSql() - fmt.Println(sql) - - type myStruct2 struct { - myStruct - Zipcode string `db:"zipcode"` - } - - // Pass pointer to struct with embedded struct - sql, _, _ = ds.Select(&myStruct2{}).ToSql() - fmt.Println(sql) - - // Pass instance of struct with embedded struct - sql, _, _ = ds.Select(myStruct2{}).ToSql() - fmt.Println(sql) - - var myStructs []myStruct - - // Pass slice of structs, will only select columns from underlying type - sql, _, _ = ds.Select(myStructs).ToSql() - fmt.Println(sql) - - // Output: - // SELECT "address", "email_address", "name" FROM "test" - // SELECT "address", "email_address", "name" FROM "test" - // SELECT "address", "email_address", "name", "zipcode" FROM "test" - // SELECT "address", "email_address", "name", "zipcode" FROM "test" - // SELECT "address", "email_address", "name" FROM "test" -} - -func ExampleDataset_SelectDistinct() { - db := goqu.New("default", driver) - sql, _, _ := db.From("test").SelectDistinct("a", "b").ToSql() - fmt.Println(sql) - // Output: - // SELECT DISTINCT "a", "b" FROM "test" -} - -func ExampleDataset_SelectAppend() { - db := goqu.New("default", driver) - ds := db.From("test").Select("a", "b") - sql, _, _ := ds.SelectAppend("c").ToSql() - fmt.Println(sql) - ds = db.From("test").SelectDistinct("a", "b") - sql, _, _ = ds.SelectAppend("c").ToSql() - fmt.Println(sql) - // Output: - // SELECT "a", "b", "c" FROM "test" - // SELECT DISTINCT "a", "b", "c" FROM "test" -} - -func ExampleDataset_ClearSelect() { - db := goqu.New("default", driver) - ds := db.From("test").Select("a", "b") - sql, _, _ := ds.ClearSelect().ToSql() - fmt.Println(sql) - ds = db.From("test").SelectDistinct("a", "b") - sql, _, _ = ds.ClearSelect().ToSql() - fmt.Println(sql) - // Output: - // SELECT * FROM "test" - // SELECT * FROM "test" -} - -func ExampleDataset_ToSql() { - db := goqu.New("default", driver) - sql, args, _ := db.From("items").Where(goqu.Ex{"a": 1}).ToSql() - fmt.Println(sql, args) - // Output: - // SELECT * FROM "items" WHERE ("a" = 1) [] -} - -func ExampleDataset_ToSql_prepared() { - db := goqu.New("default", driver) - sql, args, _ := db.From("items").Where(goqu.Ex{"a": 1}).Prepared(true).ToSql() - fmt.Println(sql, args) - // Output: - // SELECT * FROM "items" WHERE ("a" = ?) [1] -} - -func ExampleDataset_ToUpdateSql() { - db := goqu.New("default", driver) - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - sql, args, _ := db.From("items").ToUpdateSql( - item{Name: "Test", Address: "111 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").ToUpdateSql( - goqu.Record{"name": "Test", "address": "111 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").ToUpdateSql( - map[string]interface{}{"name": "Test", "address": "111 Test Addr"}, - ) - fmt.Println(sql, args) - - // Output: - // UPDATE "items" SET "address"='111 Test Addr',"name"='Test' [] - // UPDATE "items" SET "address"='111 Test Addr',"name"='Test' [] - // UPDATE "items" SET "address"='111 Test Addr',"name"='Test' [] -} - -func ExampleDataset_ToUpdateSql_prepared() { - db := goqu.New("default", driver) - type item struct { - Address string `db:"address"` - Name string `db:"name"` - } - - sql, args, _ := db.From("items").Prepared(true).ToUpdateSql( - item{Name: "Test", Address: "111 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").Prepared(true).ToUpdateSql( - goqu.Record{"name": "Test", "address": "111 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").Prepared(true).ToUpdateSql( - map[string]interface{}{"name": "Test", "address": "111 Test Addr"}, - ) - fmt.Println(sql, args) - // Output: - // UPDATE "items" SET "address"=?,"name"=? [111 Test Addr Test] - // UPDATE "items" SET "address"=?,"name"=? [111 Test Addr Test] - // UPDATE "items" SET "address"=?,"name"=? [111 Test Addr Test] -} - -func ExampleDataset_ToInsertSql() { - db := goqu.New("default", driver) - type item struct { - Id uint32 `db:"id" goqu:"skipinsert"` - Address string `db:"address"` - Name string `db:"name"` - } - sql, args, _ := db.From("items").ToInsertSql( - item{Name: "Test1", Address: "111 Test Addr"}, - item{Name: "Test2", Address: "112 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").ToInsertSql( - goqu.Record{"name": "Test1", "address": "111 Test Addr"}, - goqu.Record{"name": "Test2", "address": "112 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").ToInsertSql( - []item{ - {Name: "Test1", Address: "111 Test Addr"}, - {Name: "Test2", Address: "112 Test Addr"}, - }) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").ToInsertSql( - []goqu.Record{ - {"name": "Test1", "address": "111 Test Addr"}, - {"name": "Test2", "address": "112 Test Addr"}, - }) - fmt.Println(sql, args) - // Output: - // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') [] - // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') [] - // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') [] - // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') [] -} - -func ExampleDataset_ToInsertSql_prepared() { - db := goqu.New("default", driver) - type item struct { - Id uint32 `db:"id" goqu:"skipinsert"` - Address string `db:"address"` - Name string `db:"name"` - } - - sql, args, _ := db.From("items").Prepared(true).ToInsertSql( - item{Name: "Test1", Address: "111 Test Addr"}, - item{Name: "Test2", Address: "112 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").Prepared(true).ToInsertSql( - goqu.Record{"name": "Test1", "address": "111 Test Addr"}, - goqu.Record{"name": "Test2", "address": "112 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").Prepared(true).ToInsertSql( - []item{ - {Name: "Test1", Address: "111 Test Addr"}, - {Name: "Test2", Address: "112 Test Addr"}, - }) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").Prepared(true).ToInsertSql( - []goqu.Record{ - {"name": "Test1", "address": "111 Test Addr"}, - {"name": "Test2", "address": "112 Test Addr"}, - }) - fmt.Println(sql, args) - // Output: - // INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?) [111 Test Addr Test1 112 Test Addr Test2] - // INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?) [111 Test Addr Test1 112 Test Addr Test2] - // INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?) [111 Test Addr Test1 112 Test Addr Test2] - // INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?) [111 Test Addr Test1 112 Test Addr Test2] -} - -func ExampleDataset_ToInsertIgnore() { - db := goqu.New("mysql", driver) - type item struct { - Id uint32 `db:"id" goqu:"skipinsert"` - Address string `db:"address"` - Name string `db:"name"` - } - sql, args, _ := db.From("items").ToInsertIgnoreSql( - item{Name: "Test1", Address: "111 Test Addr"}, - item{Name: "Test2", Address: "112 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").ToInsertIgnoreSql( - goqu.Record{"name": "Test1", "address": "111 Test Addr"}, - goqu.Record{"name": "Test2", "address": "112 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").ToInsertIgnoreSql( - []item{ - {Name: "Test1", Address: "111 Test Addr"}, - {Name: "Test2", Address: "112 Test Addr"}, - }) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").ToInsertIgnoreSql( - []goqu.Record{ - {"name": "Test1", "address": "111 Test Addr"}, - {"name": "Test2", "address": "112 Test Addr"}, - }) - fmt.Println(sql, args) - // Output: - // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT DO NOTHING [] - // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT DO NOTHING [] - // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT DO NOTHING [] - // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT DO NOTHING [] -} - -func ExampleDataset_ToInsertConflictSql() { - db := goqu.New("mysql", driver) - type item struct { - Id uint32 `db:"id" goqu:"skipinsert"` - Address string `db:"address"` - Name string `db:"name"` - } - sql, args, _ := db.From("items").ToInsertConflictSql( - goqu.DoNothing(), - item{Name: "Test1", Address: "111 Test Addr"}, - item{Name: "Test2", Address: "112 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").ToInsertConflictSql( - goqu.DoUpdate("key", goqu.Record{"updated": goqu.L("NOW()")}), - goqu.Record{"name": "Test1", "address": "111 Test Addr"}, - goqu.Record{"name": "Test2", "address": "112 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").ToInsertConflictSql( - goqu.DoUpdate("key", goqu.Record{"updated": goqu.L("NOW()")}).Where(goqu.I("allow_update").IsTrue()), - []item{ - {Name: "Test1", Address: "111 Test Addr"}, - {Name: "Test2", Address: "112 Test Addr"}, - }) - fmt.Println(sql, args) - // Output: - // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT DO NOTHING [] - // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT (key) DO UPDATE SET "updated"=NOW() [] - // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', 'Test2') ON CONFLICT (key) DO UPDATE SET "updated"=NOW() WHERE ("allow_update" IS TRUE) [] -} - -func ExampleDataset_ToDeleteSql() { - db := goqu.New("default", driver) - sql, args, _ := db.From("items").ToDeleteSql() - fmt.Println(sql, args) - - sql, args, _ = db.From("items"). - Where(goqu.Ex{"id": goqu.Op{"gt": 10}}). - ToDeleteSql() - fmt.Println(sql, args) - - // Output: - // DELETE FROM "items" [] - // DELETE FROM "items" WHERE ("id" > 10) [] -} - -func ExampleDataset_ToDeleteSql_prepared() { - db := goqu.New("default", driver) - sql, args, _ := db.From("items").Prepared(true).ToDeleteSql() - fmt.Println(sql, args) - - sql, args, _ = db.From("items"). - Prepared(true). - Where(goqu.Ex{"id": goqu.Op{"gt": 10}}). - ToDeleteSql() - fmt.Println(sql, args) - - // Output: - // DELETE FROM "items" [] - // DELETE FROM "items" WHERE ("id" > ?) [10] -} - -func ExampleDataset_ToTruncateSql() { - db := goqu.New("default", driver) - sql, args, _ := db.From("items").ToTruncateSql() - fmt.Println(sql, args) - // Output: - // TRUNCATE "items" [] -} - -func ExampleDataset_ToTruncateWithOptsSql() { - db := goqu.New("default", driver) - sql, _, _ := db.From("items"). - ToTruncateWithOptsSql(goqu.TruncateOptions{}) - fmt.Println(sql) - sql, _, _ = db.From("items"). - ToTruncateWithOptsSql(goqu.TruncateOptions{Cascade: true}) - fmt.Println(sql) - sql, _, _ = db.From("items"). - ToTruncateWithOptsSql(goqu.TruncateOptions{Restrict: true}) - fmt.Println(sql) - sql, _, _ = db.From("items"). - ToTruncateWithOptsSql(goqu.TruncateOptions{Identity: "RESTART"}) - fmt.Println(sql) - sql, _, _ = db.From("items"). - ToTruncateWithOptsSql(goqu.TruncateOptions{Identity: "RESTART", Cascade: true}) - fmt.Println(sql) - sql, _, _ = db.From("items"). - ToTruncateWithOptsSql(goqu.TruncateOptions{Identity: "RESTART", Restrict: true}) - fmt.Println(sql) - sql, _, _ = db.From("items"). - ToTruncateWithOptsSql(goqu.TruncateOptions{Identity: "CONTINUE"}) - fmt.Println(sql) - sql, _, _ = db.From("items"). - ToTruncateWithOptsSql(goqu.TruncateOptions{Identity: "CONTINUE", Cascade: true}) - fmt.Println(sql) - sql, _, _ = db.From("items"). - ToTruncateWithOptsSql(goqu.TruncateOptions{Identity: "CONTINUE", Restrict: true}) - fmt.Println(sql) - - // Output: - // TRUNCATE "items" - // TRUNCATE "items" CASCADE - // TRUNCATE "items" RESTRICT - // TRUNCATE "items" RESTART IDENTITY - // TRUNCATE "items" RESTART IDENTITY CASCADE - // TRUNCATE "items" RESTART IDENTITY RESTRICT - // TRUNCATE "items" CONTINUE IDENTITY - // TRUNCATE "items" CONTINUE IDENTITY CASCADE - // TRUNCATE "items" CONTINUE IDENTITY RESTRICT -} - -func ExampleEx() { - db := goqu.New("default", driver) - sql, args, _ := db.From("items").Where(goqu.Ex{ - "col1": "a", - "col2": 1, - "col3": true, - "col4": false, - "col5": nil, - "col6": []string{"a", "b", "c"}, - }).ToSql() - fmt.Println(sql, args) - - // Output: - // SELECT * FROM "items" WHERE (("col1" = 'a') AND ("col2" = 1) AND ("col3" IS TRUE) AND ("col4" IS FALSE) AND ("col5" IS NULL) AND ("col6" IN ('a', 'b', 'c'))) [] - -} - -func ExampleEx_prepared() { - db := goqu.New("default", driver) - sql, args, _ := db.From("items").Prepared(true).Where(goqu.Ex{ - "col1": "a", - "col2": 1, - "col3": true, - "col4": false, - "col5": []string{"a", "b", "c"}, - }).ToSql() - fmt.Println(sql, args) - - // Output: - // SELECT * FROM "items" WHERE (("col1" = ?) AND ("col2" = ?) AND ("col3" IS TRUE) AND ("col4" IS FALSE) AND ("col5" IN (?, ?, ?))) [a 1 a b c] - -} - -func ExampleEx_withOp() { - db := goqu.New("default", driver) - sql, _, _ := db.From("items").Where(goqu.Ex{ - "col1": goqu.Op{"neq": "a"}, - "col3": goqu.Op{"isNot": true}, - "col6": goqu.Op{"notIn": []string{"a", "b", "c"}}, - }).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("items").Where(goqu.Ex{ - "col1": goqu.Op{"gt": 1}, - "col2": goqu.Op{"gte": 1}, - "col3": goqu.Op{"lt": 1}, - "col4": goqu.Op{"lte": 1}, - }).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("items").Where(goqu.Ex{ - "col1": goqu.Op{"like": "a%"}, - "col2": goqu.Op{"notLike": "a%"}, - "col3": goqu.Op{"iLike": "a%"}, - "col4": goqu.Op{"notILike": "a%"}, - }).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("items").Where(goqu.Ex{ - "col1": goqu.Op{"like": regexp.MustCompile("^(a|b)")}, - "col2": goqu.Op{"notLike": regexp.MustCompile("^(a|b)")}, - "col3": goqu.Op{"iLike": regexp.MustCompile("^(a|b)")}, - "col4": goqu.Op{"notILike": regexp.MustCompile("^(a|b)")}, - }).ToSql() - fmt.Println(sql) - - // Output: - // SELECT * FROM "items" WHERE (("col1" != 'a') AND ("col3" IS NOT TRUE) AND ("col6" NOT IN ('a', 'b', 'c'))) - // SELECT * FROM "items" WHERE (("col1" > 1) AND ("col2" >= 1) AND ("col3" < 1) AND ("col4" <= 1)) - // SELECT * FROM "items" WHERE (("col1" LIKE 'a%') AND ("col2" NOT LIKE 'a%') AND ("col3" ILIKE 'a%') AND ("col4" NOT ILIKE 'a%')) - // SELECT * FROM "items" WHERE (("col1" ~ '^(a|b)') AND ("col2" !~ '^(a|b)') AND ("col3" ~* '^(a|b)') AND ("col4" !~* '^(a|b)')) - -} - -func ExampleEx_withOpPrepared() { - db := goqu.New("default", driver) - sql, args, _ := db.From("items").Prepared(true).Where(goqu.Ex{ - "col1": goqu.Op{"neq": "a"}, - "col3": goqu.Op{"isNot": true}, - "col6": goqu.Op{"notIn": []string{"a", "b", "c"}}, - }).ToSql() - fmt.Println(sql, args) - - sql, args, _ = db.From("items").Prepared(true).Where(goqu.Ex{ - "col1": goqu.Op{"gt": 1}, - "col2": goqu.Op{"gte": 1}, - "col3": goqu.Op{"lt": 1}, - "col4": goqu.Op{"lte": 1}, - }).ToSql() - fmt.Println(sql, args) - - sql, args, _ = db.From("items").Prepared(true).Where(goqu.Ex{ - "col1": goqu.Op{"like": "a%"}, - "col2": goqu.Op{"notLike": "a%"}, - "col3": goqu.Op{"iLike": "a%"}, - "col4": goqu.Op{"notILike": "a%"}, - }).ToSql() - fmt.Println(sql, args) - - sql, args, _ = db.From("items").Prepared(true).Where(goqu.Ex{ - "col1": goqu.Op{"like": regexp.MustCompile("^(a|b)")}, - "col2": goqu.Op{"notLike": regexp.MustCompile("^(a|b)")}, - "col3": goqu.Op{"iLike": regexp.MustCompile("^(a|b)")}, - "col4": goqu.Op{"notILike": regexp.MustCompile("^(a|b)")}, - }).ToSql() - fmt.Println(sql, args) - - sql, args, _ = db.From("items").Prepared(true).Where(goqu.Ex{ - "col1": goqu.Op{"between": goqu.RangeVal{Start: 1, End: 10}}, - "col2": goqu.Op{"notbetween": goqu.RangeVal{Start: 1, End: 10}}, - }).ToSql() - fmt.Println(sql, args) - - // Output: - // SELECT * FROM "items" WHERE (("col1" != ?) AND ("col3" IS NOT TRUE) AND ("col6" NOT IN (?, ?, ?))) [a a b c] - // SELECT * FROM "items" WHERE (("col1" > ?) AND ("col2" >= ?) AND ("col3" < ?) AND ("col4" <= ?)) [1 1 1 1] - // SELECT * FROM "items" WHERE (("col1" LIKE ?) AND ("col2" NOT LIKE ?) AND ("col3" ILIKE ?) AND ("col4" NOT ILIKE ?)) [a% a% a% a%] - // SELECT * FROM "items" WHERE (("col1" ~ ?) AND ("col2" !~ ?) AND ("col3" ~* ?) AND ("col4" !~* ?)) [^(a|b) ^(a|b) ^(a|b) ^(a|b)] - // SELECT * FROM "items" WHERE (("col1" BETWEEN ? AND ?) AND ("col2" NOT BETWEEN ? AND ?)) [1 10 1 10] -} - -func ExampleOp() { - db := goqu.New("default", driver) - sql, _, _ := db.From("items").Where(goqu.Ex{ - "col1": goqu.Op{"neq": "a"}, - "col3": goqu.Op{"isNot": true}, - "col6": goqu.Op{"notIn": []string{"a", "b", "c"}}, - }).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("items").Where(goqu.Ex{ - "col1": goqu.Op{"gt": 1}, - "col2": goqu.Op{"gte": 1}, - "col3": goqu.Op{"lt": 1}, - "col4": goqu.Op{"lte": 1}, - }).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("items").Where(goqu.Ex{ - "col1": goqu.Op{"like": "a%"}, - "col2": goqu.Op{"notLike": "a%"}, - "col3": goqu.Op{"iLike": "a%"}, - "col4": goqu.Op{"notILike": "a%"}, - }).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("items").Where(goqu.Ex{ - "col1": goqu.Op{"like": regexp.MustCompile("^(a|b)")}, - "col2": goqu.Op{"notLike": regexp.MustCompile("^(a|b)")}, - "col3": goqu.Op{"iLike": regexp.MustCompile("^(a|b)")}, - "col4": goqu.Op{"notILike": regexp.MustCompile("^(a|b)")}, - }).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("items").Where(goqu.Ex{ - "col1": goqu.Op{"between": goqu.RangeVal{Start: 1, End: 10}}, - "col2": goqu.Op{"notbetween": goqu.RangeVal{Start: 1, End: 10}}, - }).ToSql() - fmt.Println(sql) - - // Output: - // SELECT * FROM "items" WHERE (("col1" != 'a') AND ("col3" IS NOT TRUE) AND ("col6" NOT IN ('a', 'b', 'c'))) - // SELECT * FROM "items" WHERE (("col1" > 1) AND ("col2" >= 1) AND ("col3" < 1) AND ("col4" <= 1)) - // SELECT * FROM "items" WHERE (("col1" LIKE 'a%') AND ("col2" NOT LIKE 'a%') AND ("col3" ILIKE 'a%') AND ("col4" NOT ILIKE 'a%')) - // SELECT * FROM "items" WHERE (("col1" ~ '^(a|b)') AND ("col2" !~ '^(a|b)') AND ("col3" ~* '^(a|b)') AND ("col4" !~* '^(a|b)')) - // SELECT * FROM "items" WHERE (("col1" BETWEEN 1 AND 10) AND ("col2" NOT BETWEEN 1 AND 10)) -} - -func ExampleOp_withMultipleKeys() { - db := goqu.New("default", driver) - sql, _, _ := db.From("items").Where(goqu.Ex{ - "col1": goqu.Op{"is": nil, "eq": 10}, - }).ToSql() - fmt.Println(sql) - - // Output: - //SELECT * FROM "items" WHERE (("col1" = 10) OR ("col1" IS NULL)) -} - -func ExampleExOr() { - db := goqu.New("default", driver) - sql, _, _ := db.From("items").Where(goqu.ExOr{ - "col1": "a", - "col2": 1, - "col3": true, - "col4": false, - "col5": nil, - "col6": []string{"a", "b", "c"}, - }).ToSql() - fmt.Println(sql) - - // Output: - // SELECT * FROM "items" WHERE (("col1" = 'a') OR ("col2" = 1) OR ("col3" IS TRUE) OR ("col4" IS FALSE) OR ("col5" IS NULL) OR ("col6" IN ('a', 'b', 'c'))) - -} - -func ExampleExOr_withOp() { - db := goqu.New("default", driver) - sql, _, _ := db.From("items").Where(goqu.ExOr{ - "col1": goqu.Op{"neq": "a"}, - "col3": goqu.Op{"isNot": true}, - "col6": goqu.Op{"notIn": []string{"a", "b", "c"}}, - }).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("items").Where(goqu.ExOr{ - "col1": goqu.Op{"gt": 1}, - "col2": goqu.Op{"gte": 1}, - "col3": goqu.Op{"lt": 1}, - "col4": goqu.Op{"lte": 1}, - }).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("items").Where(goqu.ExOr{ - "col1": goqu.Op{"like": "a%"}, - "col2": goqu.Op{"notLike": "a%"}, - "col3": goqu.Op{"iLike": "a%"}, - "col4": goqu.Op{"notILike": "a%"}, - }).ToSql() - fmt.Println(sql) - - sql, _, _ = db.From("items").Where(goqu.ExOr{ - "col1": goqu.Op{"like": regexp.MustCompile("^(a|b)")}, - "col2": goqu.Op{"notLike": regexp.MustCompile("^(a|b)")}, - "col3": goqu.Op{"iLike": regexp.MustCompile("^(a|b)")}, - "col4": goqu.Op{"notILike": regexp.MustCompile("^(a|b)")}, - }).ToSql() - fmt.Println(sql) - - // Output: - // SELECT * FROM "items" WHERE (("col1" != 'a') OR ("col3" IS NOT TRUE) OR ("col6" NOT IN ('a', 'b', 'c'))) - // SELECT * FROM "items" WHERE (("col1" > 1) OR ("col2" >= 1) OR ("col3" < 1) OR ("col4" <= 1)) - // SELECT * FROM "items" WHERE (("col1" LIKE 'a%') OR ("col2" NOT LIKE 'a%') OR ("col3" ILIKE 'a%') OR ("col4" NOT ILIKE 'a%')) - // SELECT * FROM "items" WHERE (("col1" ~ '^(a|b)') OR ("col2" !~ '^(a|b)') OR ("col3" ~* '^(a|b)') OR ("col4" !~* '^(a|b)')) - -} - -func ExampleDataset_Prepared() { - - db := goqu.New("default", driver) - sql, args, _ := db.From("items").Prepared(true).Where(goqu.Ex{ - "col1": "a", - "col2": 1, - "col3": true, - "col4": false, - "col5": []string{"a", "b", "c"}, - }).ToSql() - fmt.Println(sql, args) - - sql, args, _ = db.From("items").Prepared(true).ToInsertSql( - goqu.Record{"name": "Test1", "address": "111 Test Addr"}, - goqu.Record{"name": "Test2", "address": "112 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items").Prepared(true).ToUpdateSql( - goqu.Record{"name": "Test", "address": "111 Test Addr"}, - ) - fmt.Println(sql, args) - - sql, args, _ = db.From("items"). - Prepared(true). - Where(goqu.Ex{"id": goqu.Op{"gt": 10}}). - ToDeleteSql() - fmt.Println(sql, args) - - // Output: - // SELECT * FROM "items" WHERE (("col1" = ?) AND ("col2" = ?) AND ("col3" IS TRUE) AND ("col4" IS FALSE) AND ("col5" IN (?, ?, ?))) [a 1 a b c] - // INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?) [111 Test Addr Test1 112 Test Addr Test2] - // UPDATE "items" SET "address"=?,"name"=? [111 Test Addr Test] - // DELETE FROM "items" WHERE ("id" > ?) [10] - -} - -func ExampleSetColumnRenameFunction() { - mDb, mock, _ := sqlmock.New() - - mock.ExpectQuery(`SELECT "ADDRESS", "NAME" FROM "items" LIMIT 1`). - WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"ADDRESS", "NAME"}).FromCSVString("111 Test Addr,Test1")) - - db := goqu.New("db-mock", mDb) - - goqu.SetColumnRenameFunction(strings.ToUpper) - - anonStruct := struct { - Address string - Name string - }{} - found, _ := db.From("items").ScanStruct(&anonStruct) - fmt.Println(found) - fmt.Println(anonStruct.Address) - fmt.Println(anonStruct.Name) - - // Output: - // true - // 111 Test Addr - // Test1 -} diff --git a/exec/query_executor.go b/exec/query_executor.go new file mode 100644 index 00000000..87a895f0 --- /dev/null +++ b/exec/query_executor.go @@ -0,0 +1,221 @@ +package exec + +import ( + "context" + "database/sql" + "reflect" + + "github.com/doug-martin/goqu/v7/internal/errors" + "github.com/doug-martin/goqu/v7/internal/util" +) + +type ( + QueryExecutor struct { + de DbExecutor + err error + query string + args []interface{} + } +) + +var ( + errUnsupportedScanStructType = errors.New("type must be a pointer to a struct when scanning into a struct") + errUnsupportedScanStructsType = errors.New("type must be a pointer to a slice when scanning into structs") + errUnsupportedScanValsType = errors.New("type must be a pointer to a slice when scanning into vals") + errScanValPointer = errors.New("type must be a pointer when scanning into val") + errScanValNonSlice = errors.New("type cannot be a pointer to a slice when scanning into val") +) + +func newQueryExecutor(de DbExecutor, err error, query string, args ...interface{}) QueryExecutor { + return QueryExecutor{de: de, err: err, query: query, args: args} +} + +func (q QueryExecutor) Exec() (sql.Result, error) { + return q.ExecContext(context.Background()) +} + +func (q QueryExecutor) ExecContext(ctx context.Context) (sql.Result, error) { + if q.err != nil { + return nil, q.err + } + return q.de.ExecContext(ctx, q.query, q.args...) +} + +func (q QueryExecutor) Query() (*sql.Rows, error) { + return q.QueryContext(context.Background()) +} + +func (q QueryExecutor) QueryContext(ctx context.Context) (*sql.Rows, error) { + if q.err != nil { + return nil, q.err + } + return q.de.QueryContext(ctx, q.query, q.args...) +} + +// This will execute the SQL and append results to the slice +// var myStructs []MyStruct +// if err := db.From("test").ScanStructs(&myStructs); err != nil{ +// panic(err.Error() +// } +// //use your structs +// +// +// i: A pointer to a slice of structs. +func (q QueryExecutor) ScanStructs(i interface{}) error { + return q.ScanStructsContext(context.Background(), i) +} + +// This will execute the SQL and append results to the slice +// var myStructs []MyStruct +// if err := db.From("test").ScanStructsContext(ctx, &myStructs); err != nil{ +// panic(err.Error() +// } +// //use your structs +// +// +// i: A pointer to a slice of structs. +func (q QueryExecutor) ScanStructsContext(ctx context.Context, i interface{}) error { + val := reflect.ValueOf(i) + if !util.IsPointer(val.Kind()) { + return errUnsupportedScanStructsType + } + val = reflect.Indirect(val) + if !util.IsSlice(val.Kind()) { + return errUnsupportedScanStructsType + } + scanner, err := q.rowsScanner(ctx) + if err != nil { + return err + } + _, err = scanner.ScanStructs(i) + return err +} + +// This will execute the SQL and fill out the struct with the fields returned. +// This method returns a boolean value that is false if no record was found +// var myStruct MyStruct +// found, err := db.From("test").Limit(1).ScanStruct(&myStruct) +// if err != nil{ +// panic(err.Error() +// } +// if !found{ +// fmt.Println("NOT FOUND") +// } +// +// i: A pointer to a struct +func (q QueryExecutor) ScanStruct(i interface{}) (bool, error) { + return q.ScanStructContext(context.Background(), i) +} + +// This will execute the SQL and fill out the struct with the fields returned. +// This method returns a boolean value that is false if no record was found +// var myStruct MyStruct +// found, err := db.From("test").Limit(1).ScanStructContext(ctx, &myStruct) +// if err != nil{ +// panic(err.Error() +// } +// if !found{ +// fmt.Println("NOT FOUND") +// } +// +// i: A pointer to a struct +func (q QueryExecutor) ScanStructContext(ctx context.Context, i interface{}) (bool, error) { + val := reflect.ValueOf(i) + if !util.IsPointer(val.Kind()) { + return false, errUnsupportedScanStructType + } + val = reflect.Indirect(val) + if !util.IsStruct(val.Kind()) { + return false, errUnsupportedScanStructType + } + scanner, err := q.rowsScanner(ctx) + if err != nil { + return false, err + } + return scanner.ScanStructs(i) +} + +// This will execute the SQL and append results to the slice. +// var ids []uint32 +// if err := db.From("test").Select("id").ScanVals(&ids); err != nil{ +// panic(err.Error() +// } +// +// i: Takes a pointer to a slice of primitive values. +func (q QueryExecutor) ScanVals(i interface{}) error { + return q.ScanValsContext(context.Background(), i) +} + +// This will execute the SQL and append results to the slice. +// var ids []uint32 +// if err := db.From("test").Select("id").ScanValsContext(ctx, &ids); err != nil{ +// panic(err.Error() +// } +// +// i: Takes a pointer to a slice of primitive values. +func (q QueryExecutor) ScanValsContext(ctx context.Context, i interface{}) error { + val := reflect.ValueOf(i) + if !util.IsPointer(val.Kind()) { + return errUnsupportedScanValsType + } + val = reflect.Indirect(val) + if !util.IsSlice(val.Kind()) { + return errUnsupportedScanValsType + } + scanner, err := q.rowsScanner(ctx) + if err != nil { + return err + } + _, err = scanner.ScanVals(i) + return err +} + +// This will execute the SQL and set the value of the primitive. This method will return false if no record is found. +// var id uint32 +// found, err := db.From("test").Select("id").Limit(1).ScanVal(&id) +// if err != nil{ +// panic(err.Error() +// } +// if !found{ +// fmt.Println("NOT FOUND") +// } +// +// i: Takes a pointer to a primitive value. +func (q QueryExecutor) ScanVal(i interface{}) (bool, error) { + return q.ScanValContext(context.Background(), i) +} + +// This will execute the SQL and set the value of the primitive. This method will return false if no record is found. +// var id uint32 +// found, err := db.From("test").Select("id").Limit(1).ScanValContext(ctx, &id) +// if err != nil{ +// panic(err.Error() +// } +// if !found{ +// fmt.Println("NOT FOUND") +// } +// +// i: Takes a pointer to a primitive value. +func (q QueryExecutor) ScanValContext(ctx context.Context, i interface{}) (bool, error) { + val := reflect.ValueOf(i) + if !util.IsPointer(val.Kind()) { + return false, errScanValPointer + } + val = reflect.Indirect(val) + if util.IsSlice(val.Kind()) { + return false, errScanValNonSlice + } + rows, err := q.QueryContext(ctx) + if err != nil { + return false, err + } + return NewScanner(rows).ScanVals(i) +} + +func (q QueryExecutor) rowsScanner(ctx context.Context) (Scanner, error) { + rows, err := q.QueryContext(ctx) + if err != nil { + return nil, err + } + return NewScanner(rows), nil +} diff --git a/exec/query_factory.go b/exec/query_factory.go new file mode 100644 index 00000000..1994ec50 --- /dev/null +++ b/exec/query_factory.go @@ -0,0 +1,35 @@ +package exec + +import ( + "context" + "database/sql" + + "github.com/doug-martin/goqu/v7/internal/sb" +) + +type ( + DbExecutor interface { + ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) + QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) + } + QueryFactory interface { + FromSQL(sql string, args ...interface{}) QueryExecutor + FromSQLBuilder(b sb.SQLBuilder) QueryExecutor + } + querySupport struct { + de DbExecutor + } +) + +func NewQueryFactory(de DbExecutor) QueryFactory { + return &querySupport{de} +} + +func (qs *querySupport) FromSQL(query string, args ...interface{}) QueryExecutor { + return newQueryExecutor(qs.de, nil, query, args...) +} + +func (qs *querySupport) FromSQLBuilder(b sb.SQLBuilder) QueryExecutor { + query, args, err := b.ToSQL() + return newQueryExecutor(qs.de, err, query, args...) +} diff --git a/exec/scanner.go b/exec/scanner.go new file mode 100644 index 00000000..db4e56a8 --- /dev/null +++ b/exec/scanner.go @@ -0,0 +1,122 @@ +package exec + +import ( + "database/sql" + "reflect" + + "github.com/doug-martin/goqu/v7/exp" + "github.com/doug-martin/goqu/v7/internal/errors" + "github.com/doug-martin/goqu/v7/internal/util" +) + +type ( + Scanner interface { + ScanStructs(i interface{}) (bool, error) + ScanVals(i interface{}) (bool, error) + } + scanner struct { + rows *sql.Rows + } +) + +func unableToFindFieldError(col string) error { + return errors.New(`unable to find corresponding field to column "%s" returned by query`, col) +} + +func NewScanner(rows *sql.Rows) Scanner { + return &scanner{rows: rows} +} + +// This will execute the SQL and append results to the slice +// var myStructs []MyStruct +// if err := From("test").ScanStructs(&myStructs); err != nil{ +// panic(err.Error() +// } +// //use your structs +// +// +// i: A pointer to a slice of structs. +func (q *scanner) ScanStructs(i interface{}) (found bool, err error) { + defer q.rows.Close() + cm, err := util.GetColumnMap(i) + if err != nil { + return found, err + } + var results []map[string]interface{} + columns, err := q.rows.Columns() + if err != nil { + return false, err + } + for q.rows.Next() { + record, err := q.scanIntoRecord(columns, cm) + if err != nil { + return found, err + } + + results = append(results, record) + } + if q.rows.Err() != nil { + return false, q.rows.Err() + } + if len(results) > 0 { + found = true + util.AssignStructVals(i, results, cm) + } + return found, nil +} + +// This will execute the SQL and append results to the slice. +// var ids []uint32 +// if err := From("test").Select("id").ScanVals(&ids); err != nil{ +// panic(err.Error() +// } +// +// i: Takes a pointer to a slice of primitive values. +func (q *scanner) ScanVals(i interface{}) (found bool, err error) { + defer q.rows.Close() + val := reflect.Indirect(reflect.ValueOf(i)) + t, _, isSliceOfPointers := util.GetTypeInfo(i, val) + switch val.Kind() { + case reflect.Slice: + for q.rows.Next() { + found = true + row := reflect.New(t) + if err = q.rows.Scan(row.Interface()); err != nil { + return found, err + } + if isSliceOfPointers { + val.Set(reflect.Append(val, row)) + } else { + val.Set(reflect.Append(val, reflect.Indirect(row))) + } + } + default: + for q.rows.Next() { + found = true + if err = q.rows.Scan(i); err != nil { + return false, err + } + } + + } + return found, q.rows.Err() +} + +func (q *scanner) scanIntoRecord(columns []string, cm util.ColumnMap) (record exp.Record, err error) { + scans := make([]interface{}, len(columns)) + for i, col := range columns { + if data, ok := cm[col]; ok { + scans[i] = reflect.New(data.GoType).Interface() + } else { + return record, unableToFindFieldError(col) + } + } + if err := q.rows.Scan(scans...); err != nil { + return record, err + } + record = exp.Record{} + for index, col := range columns { + record[col] = scans[index] + } + return record, nil +} diff --git a/crud_exec_test.go b/exec/scanner_test.go similarity index 57% rename from crud_exec_test.go rename to exec/scanner_test.go index 878506f1..2e54bbd5 100644 --- a/crud_exec_test.go +++ b/exec/scanner_test.go @@ -1,10 +1,9 @@ -package goqu +package exec import ( "context" + "database/sql" "fmt" - "strings" - "sync" "testing" "github.com/DATA-DOG/go-sqlmock" @@ -34,104 +33,131 @@ type TestEmbeddedPtrCrudActionItem struct { Age int64 `db:"age"` } +type mockDB struct { + db *sql.DB +} + +func newMockDb(db *sql.DB) DbExecutor { + return &mockDB{db: db} +} + +func (m *mockDB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + return m.db.ExecContext(ctx, query, args...) +} +func (m *mockDB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { + return m.db.QueryContext(ctx, query, args...) +} + type crudExecTest struct { suite.Suite } -func (me *crudExecTest) TestWithError() { - t := me.T() +func (cet *crudExecTest) TestWithError() { + t := cet.T() ctx := context.Background() mDb, _, err := sqlmock.New() assert.NoError(t, err) - db := New("db-mock", mDb) + db := newMockDb(mDb) expectedErr := fmt.Errorf("crud exec error") - exec := newCrudExec(db, expectedErr, `SELECT * FROM "items"`) + e := newQueryExecutor(db, expectedErr, `SELECT * FROM "items"`) var items []TestCrudActionItem - assert.EqualError(t, exec.ScanStructs(&items), expectedErr.Error()) - assert.EqualError(t, exec.ScanStructsContext(ctx, &items), expectedErr.Error()) - found, err := exec.ScanStruct(&TestCrudActionItem{}) + assert.EqualError(t, e.ScanStructs(&items), expectedErr.Error()) + assert.EqualError(t, e.ScanStructsContext(ctx, &items), expectedErr.Error()) + found, err := e.ScanStruct(&TestCrudActionItem{}) assert.EqualError(t, err, expectedErr.Error()) assert.False(t, found) - found, err = exec.ScanStructContext(ctx, &TestCrudActionItem{}) + found, err = e.ScanStructContext(ctx, &TestCrudActionItem{}) assert.EqualError(t, err, expectedErr.Error()) assert.False(t, found) var vals []string - assert.EqualError(t, exec.ScanVals(&vals), expectedErr.Error()) - assert.EqualError(t, exec.ScanValsContext(ctx, &vals), expectedErr.Error()) + assert.EqualError(t, e.ScanVals(&vals), expectedErr.Error()) + assert.EqualError(t, e.ScanValsContext(ctx, &vals), expectedErr.Error()) var val string - found, err = exec.ScanVal(&val) + found, err = e.ScanVal(&val) assert.EqualError(t, err, expectedErr.Error()) assert.False(t, found) - found, err = exec.ScanValContext(ctx, &val) + found, err = e.ScanValContext(ctx, &val) assert.EqualError(t, err, expectedErr.Error()) assert.False(t, found) } -func (me *crudExecTest) TestScanStructs() { - t := me.T() +func (cet *crudExecTest) TestScanStructs() { + t := cet.T() ctx := context.Background() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectQuery(`SELECT \* FROM "items"`). - WillReturnError(fmt.Errorf("query error")) + WillReturnError(fmt.Errorf("queryExecutor error")) mock.ExpectQuery(`SELECT \* FROM "items"`). - WillReturnError(fmt.Errorf("query error")) + WillReturnError(fmt.Errorf("queryExecutor error")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).FromCSVString("111 Test Addr,Test1,111-111-1111,20\n211 Test Addr,Test2,222-222-2222,30")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}). + FromCSVString("111 Test Addr,Test1,111-111-1111,20\n211 Test Addr,Test2,222-222-2222,30")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).FromCSVString("111 Test Addr,Test1,111-111-1111,20\n211 Test Addr,Test2,222-222-2222,30")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}). + FromCSVString("111 Test Addr,Test1,111-111-1111,20\n211 Test Addr,Test2,222-222-2222,30")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).FromCSVString("111 Test Addr,Test1,111-111-1111,20\n211 Test Addr,Test2,222-222-2222,30")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}). + FromCSVString("111 Test Addr,Test1,111-111-1111,20\n211 Test Addr,Test2,222-222-2222,30")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).FromCSVString("111 Test Addr,Test1,111-111-1111,20\n211 Test Addr,Test2,222-222-2222,30")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}). + FromCSVString("111 Test Addr,Test1,111-111-1111,20\n211 Test Addr,Test2,222-222-2222,30")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).FromCSVString("111 Test Addr,Test1,111-111-1111,20\n211 Test Addr,Test2,222-222-2222,30")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}). + FromCSVString("111 Test Addr,Test1,111-111-1111,20\n211 Test Addr,Test2,222-222-2222,30")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).FromCSVString("111 Test Addr,Test1,111-111-1111,20\n211 Test Addr,Test2,222-222-2222,30")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}). + FromCSVString("111 Test Addr,Test1,111-111-1111,20\n211 Test Addr,Test2,222-222-2222,30")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name"}). + FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2")) - db := New("db-mock", mDb) - exec := newCrudExec(db, nil, `SELECT * FROM "items"`) + db := newMockDb(mDb) + e := newQueryExecutor(db, nil, `SELECT * FROM "items"`) var items []TestCrudActionItem - assert.EqualError(t, exec.ScanStructs(items), "goqu: Type must be a pointer to a slice when calling ScanStructs") - assert.EqualError(t, exec.ScanStructsContext(ctx, items), "goqu: Type must be a pointer to a slice when calling ScanStructs") - assert.EqualError(t, exec.ScanStructs(&TestCrudActionItem{}), "goqu: Type must be a pointer to a slice when calling ScanStructs") - assert.EqualError(t, exec.ScanStructsContext(ctx, &TestCrudActionItem{}), "goqu: Type must be a pointer to a slice when calling ScanStructs") - assert.EqualError(t, exec.ScanStructs(&items), "query error") - assert.EqualError(t, exec.ScanStructsContext(ctx, &items), "query error") - - assert.NoError(t, exec.ScanStructs(&items)) + assert.Equal(t, errUnsupportedScanStructsType, e.ScanStructs(items)) + assert.Equal(t, errUnsupportedScanStructsType, e.ScanStructsContext(ctx, items)) + assert.Equal(t, errUnsupportedScanStructsType, e.ScanStructs(&TestCrudActionItem{})) + assert.Equal(t, errUnsupportedScanStructsType, e.ScanStructsContext(ctx, &TestCrudActionItem{})) + assert.EqualError(t, e.ScanStructs(&items), "queryExecutor error") + assert.EqualError(t, e.ScanStructsContext(ctx, &items), "queryExecutor error") + + assert.NoError(t, e.ScanStructs(&items)) assert.Len(t, items, 2) assert.Equal(t, items[0].Address, "111 Test Addr") assert.Equal(t, items[0].Name, "Test1") @@ -140,7 +166,7 @@ func (me *crudExecTest) TestScanStructs() { assert.Equal(t, items[1].Name, "Test2") items = nil - assert.NoError(t, exec.ScanStructsContext(ctx, &items)) + assert.NoError(t, e.ScanStructsContext(ctx, &items)) assert.Len(t, items, 2) assert.Equal(t, items[0].Address, "111 Test Addr") assert.Equal(t, items[0].Name, "Test1") @@ -149,7 +175,7 @@ func (me *crudExecTest) TestScanStructs() { assert.Equal(t, items[1].Name, "Test2") var composed []TestComposedCrudActionItem - assert.NoError(t, exec.ScanStructs(&composed)) + assert.NoError(t, e.ScanStructs(&composed)) assert.Len(t, composed, 2) assert.Equal(t, composed[0].Address, "111 Test Addr") assert.Equal(t, composed[0].Name, "Test1") @@ -162,7 +188,7 @@ func (me *crudExecTest) TestScanStructs() { assert.Equal(t, composed[1].Age, int64(30)) composed = nil - assert.NoError(t, exec.ScanStructsContext(ctx, &composed)) + assert.NoError(t, e.ScanStructsContext(ctx, &composed)) assert.Len(t, composed, 2) assert.Equal(t, composed[0].Address, "111 Test Addr") assert.Equal(t, composed[0].Name, "Test1") @@ -175,7 +201,7 @@ func (me *crudExecTest) TestScanStructs() { assert.Equal(t, composed[1].Age, int64(30)) var pointers []*TestCrudActionItem - assert.NoError(t, exec.ScanStructs(&pointers)) + assert.NoError(t, e.ScanStructs(&pointers)) assert.Len(t, pointers, 2) assert.Equal(t, pointers[0].Address, "111 Test Addr") assert.Equal(t, pointers[0].Name, "Test1") @@ -184,7 +210,7 @@ func (me *crudExecTest) TestScanStructs() { assert.Equal(t, pointers[1].Name, "Test2") pointers = nil - assert.NoError(t, exec.ScanStructsContext(ctx, &pointers)) + assert.NoError(t, e.ScanStructsContext(ctx, &pointers)) assert.Len(t, pointers, 2) assert.Equal(t, pointers[0].Address, "111 Test Addr") assert.Equal(t, pointers[0].Name, "Test1") @@ -193,7 +219,7 @@ func (me *crudExecTest) TestScanStructs() { assert.Equal(t, pointers[1].Name, "Test2") var composedPointers []*TestComposedCrudActionItem - assert.NoError(t, exec.ScanStructs(&composedPointers)) + assert.NoError(t, e.ScanStructs(&composedPointers)) assert.Len(t, composedPointers, 2) assert.Equal(t, composedPointers[0].Address, "111 Test Addr") assert.Equal(t, composedPointers[0].Name, "Test1") @@ -206,7 +232,7 @@ func (me *crudExecTest) TestScanStructs() { assert.Equal(t, composedPointers[1].Age, int64(30)) composedPointers = nil - assert.NoError(t, exec.ScanStructsContext(ctx, &composedPointers)) + assert.NoError(t, e.ScanStructsContext(ctx, &composedPointers)) assert.Len(t, composedPointers, 2) assert.Equal(t, composedPointers[0].Address, "111 Test Addr") assert.Equal(t, composedPointers[0].Name, "Test1") @@ -219,7 +245,7 @@ func (me *crudExecTest) TestScanStructs() { assert.Equal(t, composedPointers[1].Age, int64(30)) var embeddedPtrs []*TestEmbeddedPtrCrudActionItem - assert.NoError(t, exec.ScanStructs(&embeddedPtrs)) + assert.NoError(t, e.ScanStructs(&embeddedPtrs)) assert.Len(t, embeddedPtrs, 2) assert.Equal(t, embeddedPtrs[0].Address, "111 Test Addr") assert.Equal(t, embeddedPtrs[0].Name, "Test1") @@ -232,7 +258,7 @@ func (me *crudExecTest) TestScanStructs() { assert.Equal(t, embeddedPtrs[1].Age, int64(30)) embeddedPtrs = nil - assert.NoError(t, exec.ScanStructsContext(ctx, &embeddedPtrs)) + assert.NoError(t, e.ScanStructsContext(ctx, &embeddedPtrs)) assert.Len(t, embeddedPtrs, 2) assert.Equal(t, embeddedPtrs[0].Address, "111 Test Addr") assert.Equal(t, embeddedPtrs[0].Name, "Test1") @@ -245,7 +271,7 @@ func (me *crudExecTest) TestScanStructs() { assert.Equal(t, embeddedPtrs[1].Age, int64(30)) var noTags []TestCrudActionNoTagsItem - assert.NoError(t, exec.ScanStructs(&noTags)) + assert.NoError(t, e.ScanStructs(&noTags)) assert.Len(t, noTags, 2) assert.Equal(t, noTags[0].Address, "111 Test Addr") assert.Equal(t, noTags[0].Name, "Test1") @@ -254,7 +280,7 @@ func (me *crudExecTest) TestScanStructs() { assert.Equal(t, noTags[1].Name, "Test2") noTags = nil - assert.NoError(t, exec.ScanStructsContext(ctx, &noTags)) + assert.NoError(t, e.ScanStructsContext(ctx, &noTags)) assert.Len(t, noTags, 2) assert.Equal(t, noTags[0].Address, "111 Test Addr") assert.Equal(t, noTags[0].Name, "Test1") @@ -263,53 +289,13 @@ func (me *crudExecTest) TestScanStructs() { assert.Equal(t, noTags[1].Name, "Test2") } -func (me *crudExecTest) TestColumnRename() { - t := me.T() - // different key names are used each time to circumvent the caching that happens - // it seems like a solid assumption that when people use this feature, - // they would simply set a renaming function once at startup, - // and not change between requests like this - lowerAnon := struct { - FirstLower string - LastLower string - }{} - lowerColumnMap, lowerErr := getColumnMap(&lowerAnon) - assert.NoError(t, lowerErr) - - var lowerKeys []string - for key := range lowerColumnMap { - lowerKeys = append(lowerKeys, key) - } - assert.Contains(t, lowerKeys, "firstlower") - assert.Contains(t, lowerKeys, "lastlower") - - // changing rename function - SetColumnRenameFunction(strings.ToUpper) - - upperAnon := struct { - FirstUpper string - LastUpper string - }{} - upperColumnMap, upperErr := getColumnMap(&upperAnon) - assert.NoError(t, upperErr) - - var upperKeys []string - for key := range upperColumnMap { - upperKeys = append(upperKeys, key) - } - assert.Contains(t, upperKeys, "FIRSTUPPER") - assert.Contains(t, upperKeys, "LASTUPPER") - - SetColumnRenameFunction(defaultColumnRenameFunction) -} - -func (me *crudExecTest) TestScanStruct() { - t := me.T() +func (cet *crudExecTest) TestScanStruct() { + t := cet.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectQuery(`SELECT \* FROM "items"`). - WillReturnError(fmt.Errorf("query error")) + WillReturnError(fmt.Errorf("queryExecutor error")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). @@ -317,39 +303,41 @@ func (me *crudExecTest) TestScanStruct() { mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).FromCSVString("111 Test Addr,Test1,111-111-1111,20")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}). + FromCSVString("111 Test Addr,Test1,111-111-1111,20")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). - WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).FromCSVString("111 Test Addr,Test1,111-111-1111,20")) + WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}). + FromCSVString("111 Test Addr,Test1,111-111-1111,20")) mock.ExpectQuery(`SELECT \* FROM "items"`). WithArgs(). WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1")) - db := New("db-mock", mDb) - exec := newCrudExec(db, nil, `SELECT * FROM "items"`) + db := newMockDb(mDb) + e := newQueryExecutor(db, nil, `SELECT * FROM "items"`) var slicePtr []TestCrudActionItem var item TestCrudActionItem - found, err := exec.ScanStruct(item) - assert.EqualError(t, err, "goqu: Type must be a pointer to a struct when calling ScanStruct") + found, err := e.ScanStruct(item) + assert.Equal(t, errUnsupportedScanStructType, err) assert.False(t, found) - found, err = exec.ScanStruct(&slicePtr) - assert.EqualError(t, err, "goqu: Type must be a pointer to a struct when calling ScanStruct") + found, err = e.ScanStruct(&slicePtr) + assert.Equal(t, errUnsupportedScanStructType, err) assert.False(t, found) - found, err = exec.ScanStruct(&item) - assert.EqualError(t, err, "query error") + found, err = e.ScanStruct(&item) + assert.EqualError(t, err, "queryExecutor error") assert.False(t, found) - found, err = exec.ScanStruct(&item) + found, err = e.ScanStruct(&item) assert.NoError(t, err) assert.True(t, found) assert.Equal(t, item.Address, "111 Test Addr") assert.Equal(t, item.Name, "Test1") var composed TestComposedCrudActionItem - found, err = exec.ScanStruct(&composed) + found, err = e.ScanStruct(&composed) assert.NoError(t, err) assert.True(t, found) assert.Equal(t, composed.Address, "111 Test Addr") @@ -358,7 +346,7 @@ func (me *crudExecTest) TestScanStruct() { assert.Equal(t, composed.Age, int64(20)) var embeddedPtr TestEmbeddedPtrCrudActionItem - found, err = exec.ScanStruct(&embeddedPtr) + found, err = e.ScanStruct(&embeddedPtr) assert.NoError(t, err) assert.True(t, found) assert.Equal(t, embeddedPtr.Address, "111 Test Addr") @@ -367,20 +355,20 @@ func (me *crudExecTest) TestScanStruct() { assert.Equal(t, embeddedPtr.Age, int64(20)) var noTag TestCrudActionNoTagsItem - found, err = exec.ScanStruct(&noTag) + found, err = e.ScanStruct(&noTag) assert.NoError(t, err) assert.True(t, found) assert.Equal(t, noTag.Address, "111 Test Addr") assert.Equal(t, noTag.Name, "Test1") } -func (me *crudExecTest) TestScanVals() { - t := me.T() +func (cet *crudExecTest) TestScanVals() { + t := cet.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectQuery(`SELECT "id" FROM "items"`). - WillReturnError(fmt.Errorf("query error")) + WillReturnError(fmt.Errorf("queryExecutor error")) mock.ExpectQuery(`SELECT "id" FROM "items"`). WithArgs(). @@ -390,87 +378,57 @@ func (me *crudExecTest) TestScanVals() { WithArgs(). WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("1\n2")) - db := New("db-mock", mDb) - exec := newCrudExec(db, nil, `SELECT "id" FROM "items"`) + db := newMockDb(mDb) + e := newQueryExecutor(db, nil, `SELECT "id" FROM "items"`) var id int64 var ids []int64 - assert.EqualError(t, exec.ScanVals(ids), "goqu: Type must be a pointer to a slice when calling ScanVals") - assert.EqualError(t, exec.ScanVals(&id), "goqu: Type must be a pointer to a slice when calling ScanVals") - assert.EqualError(t, exec.ScanVals(&ids), "query error") + assert.Equal(t, errUnsupportedScanValsType, e.ScanVals(ids)) + assert.Equal(t, errUnsupportedScanValsType, e.ScanVals(&id)) + assert.EqualError(t, e.ScanVals(&ids), "queryExecutor error") - assert.NoError(t, exec.ScanVals(&ids)) + assert.NoError(t, e.ScanVals(&ids)) assert.Equal(t, ids, []int64{1, 2}) var pointers []*int64 - assert.NoError(t, exec.ScanVals(&pointers)) + assert.NoError(t, e.ScanVals(&pointers)) assert.Len(t, pointers, 2) assert.Equal(t, *pointers[0], int64(1)) assert.Equal(t, *pointers[1], int64(2)) } -func (me *crudExecTest) TestScanVal() { - t := me.T() +func (cet *crudExecTest) TestScanVal() { + t := cet.T() mDb, mock, err := sqlmock.New() assert.NoError(t, err) mock.ExpectQuery(`SELECT "id" FROM "items"`). - WillReturnError(fmt.Errorf("query error")) + WillReturnError(fmt.Errorf("queryExecutor error")) mock.ExpectQuery(`SELECT "id" FROM "items"`). WithArgs(). WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("1")) - db := New("db-mock", mDb) - exec := newCrudExec(db, nil, `SELECT "id" FROM "items"`) + db := newMockDb(mDb) + e := newQueryExecutor(db, nil, `SELECT "id" FROM "items"`) var id int64 var ids []int64 - found, err := exec.ScanVal(id) - assert.EqualError(t, err, "goqu: Type must be a pointer when calling ScanVal") + found, err := e.ScanVal(id) + assert.Equal(t, errScanValPointer, err) assert.False(t, found) - found, err = exec.ScanVal(&ids) - assert.EqualError(t, err, "goqu: Cannot scan into a slice when calling ScanVal") + found, err = e.ScanVal(&ids) + assert.Equal(t, errScanValNonSlice, err) assert.False(t, found) - found, err = exec.ScanVal(&id) - assert.EqualError(t, err, "query error") + found, err = e.ScanVal(&id) + assert.EqualError(t, err, "queryExecutor error") assert.False(t, found) - var ptrId int64 - found, err = exec.ScanVal(&ptrId) + var ptrID int64 + found, err = e.ScanVal(&ptrID) assert.NoError(t, err) - assert.Equal(t, ptrId, int64(1)) -} - -func (me *crudExecTest) TestParallelGetColumnMap() { - t := me.T() - - type item struct { - id uint - name string - } - - wg := sync.WaitGroup{} - - wg.Add(1) - go func() { - i := item{} - m, err := getColumnMap(i) - assert.NoError(t, err) - assert.NotNil(t, m) - wg.Done() - }() - - wg.Add(1) - go func() { - i := item{} - m, err := getColumnMap(i) - assert.NoError(t, err) - assert.NotNil(t, m) - wg.Done() - }() - - wg.Wait() + assert.True(t, found) + assert.Equal(t, ptrID, int64(1)) } func TestCrudExecSuite(t *testing.T) { diff --git a/exp/alias.go b/exp/alias.go new file mode 100644 index 00000000..81b6f95c --- /dev/null +++ b/exp/alias.go @@ -0,0 +1,38 @@ +package exp + +import "fmt" + +type ( + aliasExpression struct { + aliased Expression + alias IdentifierExpression + } +) + +// used internally by other expressions to create a new aliased expression +func aliased(exp Expression, alias interface{}) AliasedExpression { + switch v := alias.(type) { + case string: + return aliasExpression{aliased: exp, alias: ParseIdentifier(v)} + case IdentifierExpression: + return aliasExpression{aliased: exp, alias: v} + default: + panic(fmt.Sprintf("Cannot create alias from %+v", v)) + } +} + +func (ae aliasExpression) Clone() Expression { + return aliasExpression{aliased: ae.aliased, alias: ae.alias.Clone().(IdentifierExpression)} +} + +func (ae aliasExpression) Expression() Expression { + return ae +} + +func (ae aliasExpression) Aliased() Expression { + return ae.aliased +} + +func (ae aliasExpression) GetAs() IdentifierExpression { + return ae.alias +} diff --git a/exp/bool.go b/exp/bool.go new file mode 100644 index 00000000..8f00c85f --- /dev/null +++ b/exp/bool.go @@ -0,0 +1,156 @@ +package exp + +import ( + "reflect" + "regexp" +) + +type boolean struct { + lhs Expression + rhs interface{} + op BooleanOperation +} + +func (b boolean) Clone() Expression { + return boolean{op: b.op, lhs: b.lhs.Clone(), rhs: b.rhs} +} + +func (b boolean) Expression() Expression { + return b +} + +func (b boolean) RHS() interface{} { + return b.rhs +} + +func (b boolean) LHS() Expression { + return b.lhs +} + +func (b boolean) Op() BooleanOperation { + return b.op +} + +// used internally to create an equality BooleanExpression +func eq(lhs Expression, rhs interface{}) BooleanExpression { + return checkBoolExpType(EqOp, lhs, rhs, false) +} + +// used internally to create an in-equality BooleanExpression +func neq(lhs Expression, rhs interface{}) BooleanExpression { + return checkBoolExpType(EqOp, lhs, rhs, true) +} + +// used internally to create an gt comparison BooleanExpression +func gt(lhs Expression, rhs interface{}) BooleanExpression { + return boolean{op: GtOp, lhs: lhs, rhs: rhs} +} + +// used internally to create an gte comparison BooleanExpression +func gte(lhs Expression, rhs interface{}) BooleanExpression { + return boolean{op: GteOp, lhs: lhs, rhs: rhs} +} + +// used internally to create an lt comparison BooleanExpression +func lt(lhs Expression, rhs interface{}) BooleanExpression { + return boolean{op: LtOp, lhs: lhs, rhs: rhs} +} + +// used internally to create an lte comparison BooleanExpression +func lte(lhs Expression, rhs interface{}) BooleanExpression { + return boolean{op: LteOp, lhs: lhs, rhs: rhs} +} + +// used internally to create an IN BooleanExpression +func in(lhs Expression, vals ...interface{}) BooleanExpression { + if len(vals) == 1 && reflect.Indirect(reflect.ValueOf(vals[0])).Kind() == reflect.Slice { + return boolean{op: InOp, lhs: lhs, rhs: vals[0]} + } + return boolean{op: InOp, lhs: lhs, rhs: vals} +} + +// used internally to create a NOT IN BooleanExpression +func notIn(lhs Expression, vals ...interface{}) BooleanExpression { + if len(vals) == 1 && reflect.Indirect(reflect.ValueOf(vals[0])).Kind() == reflect.Slice { + return boolean{op: NotInOp, lhs: lhs, rhs: vals[0]} + } + return boolean{op: NotInOp, lhs: lhs, rhs: vals} +} + +// used internally to create an IS BooleanExpression +func is(lhs Expression, val interface{}) BooleanExpression { + return checkBoolExpType(IsOp, lhs, val, false) +} + +// used internally to create an IS NOT BooleanExpression +func isNot(lhs Expression, val interface{}) BooleanExpression { + return checkBoolExpType(IsOp, lhs, val, true) +} + +// used internally to create a LIKE BooleanExpression +func like(lhs Expression, val interface{}) BooleanExpression { + return checkLikeExp(LikeOp, lhs, val, false) +} + +// used internally to create an ILIKE BooleanExpression +func iLike(lhs Expression, val interface{}) BooleanExpression { + return checkLikeExp(ILikeOp, lhs, val, false) +} + +// used internally to create a NOT LIKE BooleanExpression +func notLike(lhs Expression, val interface{}) BooleanExpression { + return checkLikeExp(LikeOp, lhs, val, true) +} + +// used internally to create a NOT ILIKE BooleanExpression +func notILike(lhs Expression, val interface{}) BooleanExpression { + return checkLikeExp(ILikeOp, lhs, val, true) +} + +// checks an like rhs to create the proper like expression for strings or regexps +func checkLikeExp(op BooleanOperation, lhs Expression, val interface{}, invert bool) BooleanExpression { + rhs := val + + if t, ok := val.(*regexp.Regexp); ok { + if op == LikeOp { + op = RegexpLikeOp + } else if op == ILikeOp { + op = RegexpILikeOp + } + rhs = t.String() + } + if invert { + op = operatorInversions[op] + } + return boolean{op: op, lhs: lhs, rhs: rhs} +} + +// checks a boolean operation normalizing the operation based on the RHS (e.g. "a" = true vs "a" IS TRUE +func checkBoolExpType(op BooleanOperation, lhs Expression, rhs interface{}, invert bool) BooleanExpression { + if rhs == nil { + op = IsOp + } else { + switch reflect.Indirect(reflect.ValueOf(rhs)).Kind() { + case reflect.Bool: + op = IsOp + case reflect.Slice: + // if its a slice of bytes dont treat as an IN + if _, ok := rhs.([]byte); !ok { + op = InOp + } + case reflect.Struct: + switch rhs.(type) { + case SQLExpression: + op = InOp + case AppendableExpression: + op = InOp + case *regexp.Regexp: + return checkLikeExp(LikeOp, lhs, rhs, invert) + } + } + } + if invert { + op = operatorInversions[op] + } + return boolean{op: op, lhs: lhs, rhs: rhs} +} diff --git a/exp/cast.go b/exp/cast.go new file mode 100644 index 00000000..0df80fa8 --- /dev/null +++ b/exp/cast.go @@ -0,0 +1,52 @@ +package exp + +type cast struct { + casted Expression + t LiteralExpression +} + +// Creates a new Casted expression +// Cast(I("a"), "NUMERIC") -> CAST("a" AS NUMERIC) +func NewCastExpression(e Expression, t string) CastExpression { + return cast{casted: e, t: NewLiteralExpression(t)} +} + +func (c cast) Casted() Expression { + return c.casted +} + +func (c cast) Type() LiteralExpression { + return c.t +} + +func (c cast) Clone() Expression { + return cast{casted: c.casted.Clone(), t: c.t} +} + +func (c cast) Expression() Expression { return c } +func (c cast) As(val interface{}) AliasedExpression { return aliased(c, val) } +func (c cast) Eq(val interface{}) BooleanExpression { return eq(c, val) } +func (c cast) Neq(val interface{}) BooleanExpression { return neq(c, val) } +func (c cast) Gt(val interface{}) BooleanExpression { return gt(c, val) } +func (c cast) Gte(val interface{}) BooleanExpression { return gte(c, val) } +func (c cast) Lt(val interface{}) BooleanExpression { return lt(c, val) } +func (c cast) Lte(val interface{}) BooleanExpression { return lte(c, val) } +func (c cast) Asc() OrderedExpression { return asc(c) } +func (c cast) Desc() OrderedExpression { return desc(c) } +func (c cast) Like(i interface{}) BooleanExpression { return like(c, i) } +func (c cast) NotLike(i interface{}) BooleanExpression { return notLike(c, i) } +func (c cast) ILike(i interface{}) BooleanExpression { return iLike(c, i) } +func (c cast) NotILike(i interface{}) BooleanExpression { return notILike(c, i) } +func (c cast) In(i ...interface{}) BooleanExpression { return in(c, i...) } +func (c cast) NotIn(i ...interface{}) BooleanExpression { return notIn(c, i...) } +func (c cast) Is(i interface{}) BooleanExpression { return is(c, i) } +func (c cast) IsNot(i interface{}) BooleanExpression { return isNot(c, i) } +func (c cast) IsNull() BooleanExpression { return is(c, nil) } +func (c cast) IsNotNull() BooleanExpression { return isNot(c, nil) } +func (c cast) IsTrue() BooleanExpression { return is(c, true) } +func (c cast) IsNotTrue() BooleanExpression { return isNot(c, true) } +func (c cast) IsFalse() BooleanExpression { return is(c, false) } +func (c cast) IsNotFalse() BooleanExpression { return isNot(c, nil) } +func (c cast) Distinct() SQLFunctionExpression { return NewSQLFunctionExpression("DISTINCT", c) } +func (c cast) Between(val RangeVal) RangeExpression { return between(c, val) } +func (c cast) NotBetween(val RangeVal) RangeExpression { return notBetween(c, val) } diff --git a/exp/clauses.go b/exp/clauses.go new file mode 100644 index 00000000..b7b9ea10 --- /dev/null +++ b/exp/clauses.go @@ -0,0 +1,353 @@ +package exp + +type ( + Clauses interface { + HasSources() bool + IsDefaultSelect() bool + clone() *clauses + + Select() ColumnListExpression + SelectAppend(cl ColumnListExpression) Clauses + SetSelect(cl ColumnListExpression) Clauses + + SelectDistinct() ColumnListExpression + HasSelectDistinct() bool + SetSelectDistinct(cl ColumnListExpression) Clauses + + From() ColumnListExpression + SetFrom(cl ColumnListExpression) Clauses + + HasAlias() bool + Alias() IdentifierExpression + SetAlias(ie IdentifierExpression) Clauses + + Joins() JoinExpressions + JoinsAppend(jc JoinExpression) Clauses + + Where() ExpressionList + ClearWhere() Clauses + WhereAppend(expressions ...Expression) Clauses + + Having() ExpressionList + ClearHaving() Clauses + HavingAppend(expressions ...Expression) Clauses + + Order() ColumnListExpression + HasOrder() bool + ClearOrder() Clauses + SetOrder(oes ...OrderedExpression) Clauses + OrderAppend(...OrderedExpression) Clauses + + GroupBy() ColumnListExpression + SetGroupBy(cl ColumnListExpression) Clauses + + Limit() interface{} + HasLimit() bool + ClearLimit() Clauses + SetLimit(limit interface{}) Clauses + + Offset() uint + ClearOffset() Clauses + SetOffset(offset uint) Clauses + + Compounds() []CompoundExpression + CompoundsAppend(ce CompoundExpression) Clauses + + Lock() Lock + SetLock(l Lock) Clauses + + CommonTables() []CommonTableExpression + CommonTablesAppend(cte CommonTableExpression) Clauses + + Returning() ColumnListExpression + HasReturning() bool + SetReturning(cl ColumnListExpression) Clauses + } + clauses struct { + commonTables []CommonTableExpression + selectColumns ColumnListExpression + selectDistinct ColumnListExpression + from ColumnListExpression + joins JoinExpressions + where ExpressionList + alias IdentifierExpression + groupBy ColumnListExpression + having ExpressionList + order ColumnListExpression + limit interface{} + offset uint + returning ColumnListExpression + compounds []CompoundExpression + lock Lock + } +) + +func NewClauses() Clauses { + return &clauses{ + selectColumns: NewColumnListExpression(Star()), + } +} + +func (c *clauses) HasSources() bool { + return c.from != nil && len(c.from.Columns()) > 0 +} + +func (c *clauses) IsDefaultSelect() bool { + ret := false + if c.selectColumns != nil { + selects := c.selectColumns.Columns() + if len(selects) == 1 { + if l, ok := selects[0].(LiteralExpression); ok && l.Literal() == "*" { + ret = true + } + } + } + return ret +} + +func (c *clauses) clone() *clauses { + return &clauses{ + commonTables: c.commonTables, + selectColumns: c.selectColumns, + selectDistinct: c.selectDistinct, + from: c.from, + joins: c.joins, + where: c.where, + alias: c.alias, + groupBy: c.groupBy, + having: c.having, + order: c.order, + limit: c.limit, + offset: c.offset, + returning: c.returning, + compounds: c.compounds, + lock: c.lock, + } +} + +func (c *clauses) CommonTables() []CommonTableExpression { + return c.commonTables +} +func (c *clauses) CommonTablesAppend(cte CommonTableExpression) Clauses { + ret := c.clone() + ret.commonTables = append(ret.commonTables, cte) + return ret +} + +func (c *clauses) Select() ColumnListExpression { + return c.selectColumns +} +func (c *clauses) SelectAppend(cl ColumnListExpression) Clauses { + ret := c.clone() + if ret.selectDistinct != nil { + ret.selectDistinct = ret.selectDistinct.Append(cl.Columns()...) + } else { + ret.selectColumns = ret.selectColumns.Append(cl.Columns()...) + } + return ret +} + +func (c *clauses) SetSelect(cl ColumnListExpression) Clauses { + ret := c.clone() + ret.selectDistinct = nil + ret.selectColumns = cl + return ret +} + +func (c *clauses) SelectDistinct() ColumnListExpression { + return c.selectDistinct +} +func (c *clauses) HasSelectDistinct() bool { + return c.selectDistinct != nil +} +func (c *clauses) SetSelectDistinct(cl ColumnListExpression) Clauses { + ret := c.clone() + ret.selectColumns = nil + ret.selectDistinct = cl + return ret +} + +func (c *clauses) From() ColumnListExpression { + return c.from +} +func (c *clauses) SetFrom(cl ColumnListExpression) Clauses { + ret := c.clone() + ret.from = cl + return ret +} + +func (c *clauses) HasAlias() bool { + return c.alias != nil +} + +func (c *clauses) Alias() IdentifierExpression { + return c.alias +} + +func (c *clauses) SetAlias(ie IdentifierExpression) Clauses { + ret := c.clone() + ret.alias = ie + return ret +} + +func (c *clauses) Joins() JoinExpressions { + return c.joins +} +func (c *clauses) JoinsAppend(jc JoinExpression) Clauses { + ret := c.clone() + ret.joins = append(ret.joins, jc) + return ret +} + +func (c *clauses) Where() ExpressionList { + return c.where +} + +func (c *clauses) ClearWhere() Clauses { + ret := c.clone() + ret.where = nil + return ret +} + +func (c *clauses) WhereAppend(expressions ...Expression) Clauses { + expLen := len(expressions) + if expLen == 0 { + return c + } + ret := c.clone() + if ret.where == nil { + ret.where = NewExpressionList(AndType, expressions...) + } else { + ret.where = ret.where.Append(expressions...) + } + return ret +} + +func (c *clauses) Having() ExpressionList { + return c.having +} + +func (c *clauses) ClearHaving() Clauses { + ret := c.clone() + ret.having = nil + return ret +} + +func (c *clauses) HavingAppend(expressions ...Expression) Clauses { + expLen := len(expressions) + if expLen == 0 { + return c + } + ret := c.clone() + if ret.having == nil { + ret.having = NewExpressionList(AndType, expressions...) + } else { + ret.having = ret.having.Append(expressions...) + } + return ret +} + +func (c *clauses) Lock() Lock { + return c.lock +} +func (c *clauses) SetLock(l Lock) Clauses { + ret := c.clone() + ret.lock = l + return ret +} + +func (c *clauses) Order() ColumnListExpression { + return c.order +} + +func (c *clauses) HasOrder() bool { + return c.order != nil +} + +func (c *clauses) ClearOrder() Clauses { + ret := c.clone() + ret.order = nil + return ret +} + +func (c *clauses) SetOrder(oes ...OrderedExpression) Clauses { + ret := c.clone() + ret.order = NewOrderedColumnList(oes...) + return ret +} + +func (c *clauses) OrderAppend(oes ...OrderedExpression) Clauses { + if c.order == nil { + return c.SetOrder(oes...) + } + ret := c.clone() + ret.order = ret.order.Append(NewOrderedColumnList(oes...).Columns()...) + return ret +} + +func (c *clauses) GroupBy() ColumnListExpression { + return c.groupBy +} +func (c *clauses) SetGroupBy(cl ColumnListExpression) Clauses { + ret := c.clone() + ret.groupBy = cl + return ret +} + +func (c *clauses) Limit() interface{} { + return c.limit +} + +func (c *clauses) HasLimit() bool { + return c.limit != nil +} + +func (c *clauses) ClearLimit() Clauses { + ret := c.clone() + ret.limit = nil + return ret +} + +func (c *clauses) SetLimit(limit interface{}) Clauses { + ret := c.clone() + ret.limit = limit + return ret +} + +func (c *clauses) Offset() uint { + return c.offset +} + +func (c *clauses) ClearOffset() Clauses { + ret := c.clone() + ret.offset = 0 + return ret +} +func (c *clauses) SetOffset(offset uint) Clauses { + ret := c.clone() + ret.offset = offset + return ret +} + +func (c *clauses) Compounds() []CompoundExpression { + return c.compounds +} +func (c *clauses) CompoundsAppend(ce CompoundExpression) Clauses { + ret := c.clone() + ret.compounds = append(ret.compounds, ce) + return ret +} + +func (c *clauses) Returning() ColumnListExpression { + return c.returning +} + +func (c *clauses) HasReturning() bool { + return c.returning != nil +} + +func (c *clauses) SetReturning(cl ColumnListExpression) Clauses { + ret := c.clone() + ret.returning = cl + return ret +} diff --git a/exp/clauses_test.go b/exp/clauses_test.go new file mode 100644 index 00000000..4e65c43a --- /dev/null +++ b/exp/clauses_test.go @@ -0,0 +1,563 @@ +package exp + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type testSQLExpression string + +func (tse testSQLExpression) Expression() Expression { + return tse +} + +func (tse testSQLExpression) Clone() Expression { + return tse +} + +func (tse testSQLExpression) ToSQL() (sql string, args []interface{}, err error) { + return "", nil, nil +} + +type clausesTest struct { + suite.Suite +} + +func TestClausesSuite(t *testing.T) { + suite.Run(t, new(clausesTest)) +} + +func (ct *clausesTest) TestHasSources() { + t := ct.T() + c := NewClauses() + c2 := c.SetFrom(NewColumnListExpression("test")) + + assert.False(t, c.HasSources()) + + assert.True(t, c2.HasSources()) +} + +func (ct *clausesTest) TestIsDefaultSelect() { + t := ct.T() + c := NewClauses() + c2 := c.SelectAppend(NewColumnListExpression("a")) + + assert.True(t, c.IsDefaultSelect()) + + assert.False(t, c2.IsDefaultSelect()) +} + +func (ct *clausesTest) TestSelect() { + t := ct.T() + c := NewClauses() + c2 := c.SetSelect(NewColumnListExpression("a")) + + assert.Equal(t, NewColumnListExpression(Star()), c.Select()) + + assert.Equal(t, NewColumnListExpression("a"), c2.Select()) +} + +func (ct *clausesTest) TestSelectAppend() { + t := ct.T() + c := NewClauses() + c2 := c.SelectAppend(NewColumnListExpression("a")) + + assert.Equal(t, NewColumnListExpression(Star()), c.Select()) + assert.Nil(t, c.SelectDistinct()) + + assert.Equal(t, NewColumnListExpression(Star(), "a"), c2.Select()) + assert.Nil(t, c2.SelectDistinct()) +} + +func (ct *clausesTest) TestSetSelect() { + t := ct.T() + c := NewClauses() + c2 := c.SetSelect(NewColumnListExpression("a")) + + assert.Equal(t, NewColumnListExpression(Star()), c.Select()) + assert.Nil(t, c.SelectDistinct()) + + assert.Equal(t, NewColumnListExpression("a"), c2.Select()) + assert.Nil(t, c2.SelectDistinct()) + +} + +func (ct *clausesTest) TestSelectDistinct() { + t := ct.T() + c := NewClauses() + c2 := c.SetSelectDistinct(NewColumnListExpression("a")) + + assert.Nil(t, c.SelectDistinct()) + assert.Equal(t, NewColumnListExpression(Star()), c.Select()) + + assert.Equal(t, NewColumnListExpression("a"), c2.SelectDistinct()) + assert.Nil(t, c2.Select()) +} + +func (ct *clausesTest) TestSetSelectDistinct() { + t := ct.T() + c := NewClauses() + c2 := c.SetSelectDistinct(NewColumnListExpression("a")) + + assert.Nil(t, c.SelectDistinct()) + assert.Equal(t, NewColumnListExpression(Star()), c.Select()) + + assert.Equal(t, NewColumnListExpression("a"), c2.SelectDistinct()) + assert.Nil(t, c2.Select()) +} + +func (ct *clausesTest) TestFrom() { + t := ct.T() + c := NewClauses() + c2 := c.SetFrom(NewColumnListExpression("a")) + + assert.Nil(t, c.From()) + + assert.Equal(t, NewColumnListExpression("a"), c2.From()) +} + +func (ct *clausesTest) TestSetFrom() { + t := ct.T() + c := NewClauses() + c2 := c.SetFrom(NewColumnListExpression("a")) + + assert.Nil(t, c.From()) + + assert.Equal(t, NewColumnListExpression("a"), c2.From()) +} +func (ct *clausesTest) TestHasAlias() { + t := ct.T() + c := NewClauses() + c2 := c.SetAlias(NewIdentifierExpression("", "", "a")) + + assert.False(t, c.HasAlias()) + + assert.True(t, c2.HasAlias()) +} + +func (ct *clausesTest) TestAlias() { + t := ct.T() + c := NewClauses() + a := NewIdentifierExpression("", "a", "") + c2 := c.SetAlias(a) + + assert.Nil(t, c.Alias()) + + assert.Equal(t, a, c2.Alias()) +} + +func (ct *clausesTest) TestSetAlias() { + t := ct.T() + c := NewClauses() + a := NewIdentifierExpression("", "a", "") + c2 := c.SetAlias(a) + + assert.Nil(t, c.Alias()) + + assert.Equal(t, a, c2.Alias()) +} + +func (ct *clausesTest) TestJoins() { + t := ct.T() + + jc := NewConditionedJoinExpression( + LeftJoinType, + NewIdentifierExpression("", "test", ""), + nil, + ) + c := NewClauses() + c2 := c.JoinsAppend(jc) + + assert.Nil(t, c.Joins()) + + assert.Equal(t, JoinExpressions{jc}, c2.Joins()) +} + +func (ct *clausesTest) TestJoinsAppend() { + t := ct.T() + jc := NewConditionedJoinExpression( + LeftJoinType, + NewIdentifierExpression("", "test", ""), + nil, + ) + jc2 := NewUnConditionedJoinExpression( + LeftJoinType, + NewIdentifierExpression("", "test", ""), + ) + c := NewClauses() + c2 := c.JoinsAppend(jc) + c3 := c2.JoinsAppend(jc2) + + assert.Nil(t, c.Joins()) + + assert.Equal(t, JoinExpressions{jc}, c2.Joins()) + assert.Equal(t, JoinExpressions{jc, jc2}, c3.Joins()) +} + +func (ct *clausesTest) TestWhere() { + t := ct.T() + w := Ex{"a": 1} + + c := NewClauses() + c2 := c.WhereAppend(w) + + assert.Nil(t, c.Where()) + + assert.Equal(t, NewExpressionList(AndType, w), c2.Where()) +} + +func (ct *clausesTest) TestClearWhere() { + t := ct.T() + w := Ex{"a": 1} + + c := NewClauses().WhereAppend(w) + c2 := c.ClearWhere() + + assert.Equal(t, NewExpressionList(AndType, w), c.Where()) + + assert.Nil(t, c2.Where()) +} + +func (ct *clausesTest) TestWhereAppend() { + t := ct.T() + w := Ex{"a": 1} + w2 := Ex{"b": 2} + + c := NewClauses() + c2 := c.WhereAppend(w) + + c3 := c.WhereAppend(w).WhereAppend(w2) + + c4 := c.WhereAppend(w, w2) + + assert.Nil(t, c.Where()) + + assert.Equal(t, NewExpressionList(AndType, w), c2.Where()) + assert.Equal(t, NewExpressionList(AndType, w).Append(w2), c3.Where()) + assert.Equal(t, NewExpressionList(AndType, w, w2), c4.Where()) +} + +func (ct *clausesTest) TestHaving() { + t := ct.T() + w := Ex{"a": 1} + + c := NewClauses() + c2 := c.HavingAppend(w) + + assert.Nil(t, c.Having()) + + assert.Equal(t, NewExpressionList(AndType, w), c2.Having()) +} + +func (ct *clausesTest) TestClearHaving() { + t := ct.T() + w := Ex{"a": 1} + + c := NewClauses().HavingAppend(w) + c2 := c.ClearHaving() + + assert.Equal(t, NewExpressionList(AndType, w), c.Having()) + + assert.Nil(t, c2.Having()) +} + +func (ct *clausesTest) TestHavingAppend() { + t := ct.T() + w := Ex{"a": 1} + w2 := Ex{"b": 2} + + c := NewClauses() + c2 := c.HavingAppend(w) + + c3 := c.HavingAppend(w).HavingAppend(w2) + + c4 := c.HavingAppend(w, w2) + + assert.Nil(t, c.Having()) + + assert.Equal(t, NewExpressionList(AndType, w), c2.Having()) + assert.Equal(t, NewExpressionList(AndType, w).Append(w2), c3.Having()) + assert.Equal(t, NewExpressionList(AndType, w, w2), c4.Having()) +} + +func (ct *clausesTest) TestOrder() { + t := ct.T() + oe := NewIdentifierExpression("", "", "a").Desc() + + c := NewClauses() + c2 := c.SetOrder(oe) + + assert.Nil(t, c.Order()) + + assert.Equal(t, NewColumnListExpression(oe), c2.Order()) +} + +func (ct *clausesTest) TestHasOrder() { + t := ct.T() + oe := NewIdentifierExpression("", "", "a").Desc() + + c := NewClauses() + c2 := c.SetOrder(oe) + + assert.False(t, c.HasOrder()) + + assert.True(t, c2.HasOrder()) +} + +func (ct *clausesTest) TestClearOrder() { + t := ct.T() + oe := NewIdentifierExpression("", "", "a").Desc() + + c := NewClauses().SetOrder(oe) + c2 := c.ClearOrder() + + assert.Equal(t, NewColumnListExpression(oe), c.Order()) + + assert.Nil(t, c2.Order()) +} + +func (ct *clausesTest) TestSetOrder() { + t := ct.T() + oe := NewIdentifierExpression("", "", "a").Desc() + oe2 := NewIdentifierExpression("", "", "b").Desc() + + c := NewClauses().SetOrder(oe) + c2 := c.SetOrder(oe2) + + assert.Equal(t, NewColumnListExpression(oe), c.Order()) + + assert.Equal(t, NewColumnListExpression(oe2), c2.Order()) +} + +func (ct *clausesTest) TestOrderAppend() { + t := ct.T() + oe := NewIdentifierExpression("", "", "a").Desc() + oe2 := NewIdentifierExpression("", "", "b").Desc() + + c := NewClauses().SetOrder(oe) + c2 := c.OrderAppend(oe2) + + assert.Equal(t, NewColumnListExpression(oe), c.Order()) + + assert.Equal(t, NewColumnListExpression(oe, oe2), c2.Order()) +} + +func (ct *clausesTest) TestGroupBy() { + t := ct.T() + g := NewColumnListExpression(NewIdentifierExpression("", "", "a")) + + c := NewClauses() + c2 := c.SetGroupBy(g) + + assert.Nil(t, c.GroupBy()) + + assert.Equal(t, g, c2.GroupBy()) +} + +func (ct *clausesTest) TestSetGroupBy() { + t := ct.T() + g := NewColumnListExpression(NewIdentifierExpression("", "", "a")) + g2 := NewColumnListExpression(NewIdentifierExpression("", "", "b")) + + c := NewClauses().SetGroupBy(g) + c2 := c.SetGroupBy(g2) + + assert.Equal(t, g, c.GroupBy()) + + assert.Equal(t, g2, c2.GroupBy()) +} + +func (ct *clausesTest) TestLimit() { + t := ct.T() + l := 1 + + c := NewClauses() + c2 := c.SetLimit(l) + + assert.Nil(t, c.Limit()) + + assert.Equal(t, l, c2.Limit()) +} + +func (ct *clausesTest) TestHasLimit() { + t := ct.T() + l := 1 + + c := NewClauses() + c2 := c.SetLimit(l) + + assert.False(t, c.HasLimit()) + + assert.True(t, c2.HasLimit()) +} + +func (ct *clausesTest) TestCLearLimit() { + t := ct.T() + l := 1 + + c := NewClauses().SetLimit(l) + c2 := c.ClearLimit() + + assert.True(t, c.HasLimit()) + + assert.False(t, c2.HasLimit()) +} + +func (ct *clausesTest) TestSetLimit() { + t := ct.T() + l := 1 + l2 := 2 + + c := NewClauses().SetLimit(l) + c2 := c.SetLimit(2) + + assert.Equal(t, l, c.Limit()) + + assert.Equal(t, l2, c2.Limit()) +} + +func (ct *clausesTest) TestOffset() { + t := ct.T() + o := uint(1) + + c := NewClauses() + c2 := c.SetOffset(o) + + assert.Equal(t, uint(0), c.Offset()) + + assert.Equal(t, o, c2.Offset()) +} + +func (ct *clausesTest) TestClearOffset() { + t := ct.T() + o := uint(1) + + c := NewClauses().SetOffset(o) + c2 := c.ClearOffset() + + assert.Equal(t, o, c.Offset()) + + assert.Equal(t, uint(0), c2.Offset()) +} + +func (ct *clausesTest) TestSetOffset() { + t := ct.T() + o := uint(1) + o2 := uint(2) + + c := NewClauses().SetOffset(o) + c2 := c.SetOffset(2) + + assert.Equal(t, o, c.Offset()) + + assert.Equal(t, o2, c2.Offset()) +} + +func (ct *clausesTest) TestCompounds() { + t := ct.T() + + ce := NewCompoundExpression(UnionCompoundType, testSQLExpression("test_ce")) + + c := NewClauses() + c2 := c.CompoundsAppend(ce) + + assert.Nil(t, c.Compounds()) + + assert.Equal(t, []CompoundExpression{ce}, c2.Compounds()) +} +func (ct *clausesTest) TestCompoundsAppend() { + t := ct.T() + + ce := NewCompoundExpression(UnionCompoundType, testSQLExpression("test_ce")) + ce2 := NewCompoundExpression(UnionCompoundType, testSQLExpression("test_ce_2")) + + c := NewClauses().CompoundsAppend(ce) + c2 := c.CompoundsAppend(ce2) + + assert.Equal(t, []CompoundExpression{ce}, c.Compounds()) + + assert.Equal(t, []CompoundExpression{ce, ce2}, c2.Compounds()) +} + +func (ct *clausesTest) TestLock() { + t := ct.T() + + l := NewLock(ForUpdate, Wait) + + c := NewClauses() + c2 := c.SetLock(l) + + assert.Nil(t, c.Lock()) + + assert.Equal(t, l, c2.Lock()) +} + +func (ct *clausesTest) TestSetLock() { + t := ct.T() + + l := NewLock(ForUpdate, Wait) + l2 := NewLock(ForUpdate, NoWait) + + c := NewClauses().SetLock(l) + c2 := c.SetLock(l2) + + assert.Equal(t, l, c.Lock()) + + assert.Equal(t, l2, c2.Lock()) +} + +func (ct *clausesTest) TestCommonTables() { + t := ct.T() + + cte := NewCommonTableExpression(true, "test", testSQLExpression("test_cte")) + + c := NewClauses() + c2 := c.CommonTablesAppend(cte) + + assert.Nil(t, c.CommonTables()) + + assert.Equal(t, []CommonTableExpression{cte}, c2.CommonTables()) +} + +func (ct *clausesTest) TestAddCommonTablesAppend() { + t := ct.T() + + cte := NewCommonTableExpression(true, "test", testSQLExpression("test_cte")) + cte2 := NewCommonTableExpression(true, "test", testSQLExpression("test_cte2")) + + c := NewClauses().CommonTablesAppend(cte) + c2 := c.CommonTablesAppend(cte2) + + assert.Equal(t, []CommonTableExpression{cte}, c.CommonTables()) + + assert.Equal(t, []CommonTableExpression{cte, cte2}, c2.CommonTables()) +} + +func (ct *clausesTest) TestReturning() { + t := ct.T() + + cl := NewColumnListExpression(NewIdentifierExpression("", "", "col")) + + c := NewClauses() + c2 := c.SetReturning(cl) + + assert.Nil(t, c.Returning()) + + assert.Equal(t, cl, c2.Returning()) +} + +func (ct *clausesTest) TestSetReturning() { + t := ct.T() + + cl := NewColumnListExpression(NewIdentifierExpression("", "", "col")) + cl2 := NewColumnListExpression(NewIdentifierExpression("", "", "col2")) + + c := NewClauses().SetReturning(cl) + c2 := c.SetReturning(cl2) + + assert.Equal(t, cl, c.Returning()) + + assert.Equal(t, cl2, c2.Returning()) +} diff --git a/exp/col.go b/exp/col.go new file mode 100644 index 00000000..6ed010f3 --- /dev/null +++ b/exp/col.go @@ -0,0 +1,84 @@ +package exp + +import ( + "fmt" + "reflect" + "sort" + + "github.com/doug-martin/goqu/v7/internal/util" +) + +type columnList struct { + columns []Expression +} + +func NewColumnListExpression(vals ...interface{}) ColumnListExpression { + var cols []Expression + for _, val := range vals { + switch t := val.(type) { + case string: + cols = append(cols, ParseIdentifier(t)) + case Expression: + cols = append(cols, t) + default: + _, valKind, _ := util.GetTypeInfo(val, reflect.Indirect(reflect.ValueOf(val))) + + if valKind == reflect.Struct { + cm, err := util.GetColumnMap(val) + if err != nil { + panic(err.Error()) + } + var structCols []string + for key, col := range cm { + if !col.Transient { + structCols = append(structCols, key) + } + } + sort.Strings(structCols) + for _, col := range structCols { + cols = append(cols, ParseIdentifier(col)) + } + } else { + panic(fmt.Sprintf("Cannot created expression from %+v", val)) + } + + } + } + return columnList{columns: cols} +} + +func NewOrderedColumnList(vals ...OrderedExpression) ColumnListExpression { + exps := make([]interface{}, len(vals)) + for i, col := range vals { + exps[i] = col.Expression() + } + return NewColumnListExpression(exps...) +} + +func (cl columnList) Clone() Expression { + newExps := make([]Expression, len(cl.columns)) + for i, exp := range cl.columns { + newExps[i] = exp.Clone() + } + return columnList{columns: newExps} +} + +func (cl columnList) Expression() Expression { + return cl +} + +func (cl columnList) IsEmpty() bool { + return len(cl.columns) == 0 +} + +func (cl columnList) Columns() []Expression { + return cl.columns +} + +func (cl columnList) Append(cols ...Expression) ColumnListExpression { + ret := columnList{} + exps := append(ret.columns, cl.columns...) + exps = append(exps, cols...) + ret.columns = exps + return ret +} diff --git a/exp/compound.go b/exp/compound.go new file mode 100644 index 00000000..5f795f44 --- /dev/null +++ b/exp/compound.go @@ -0,0 +1,19 @@ +package exp + +type compound struct { + t CompoundType + rhs Expression +} + +func NewCompoundExpression(ct CompoundType, rhs Expression) CompoundExpression { + return compound{t: ct, rhs: rhs} +} + +func (c compound) Expression() Expression { return c } + +func (c compound) Clone() Expression { + return compound{t: c.t, rhs: c.rhs.Clone().(SQLExpression)} +} + +func (c compound) Type() CompoundType { return c.t } +func (c compound) RHS() Expression { return c.rhs } diff --git a/exp/conflict.go b/exp/conflict.go new file mode 100644 index 00000000..4c7c5ead --- /dev/null +++ b/exp/conflict.go @@ -0,0 +1,86 @@ +package exp + +type ( + doNothingConflict struct{} + // ConflictUpdate is the struct that represents the UPDATE fragment of an + // INSERT ... ON CONFLICT/ON DUPLICATE KEY DO UPDATE statement + conflictUpdate struct { + target string + update interface{} + whereClause ExpressionList + } +) + +// Creates a conflict struct to be passed to InsertConflict to ignore constraint errors +// InsertConflict(DoNothing(),...) -> INSERT INTO ... ON CONFLICT DO NOTHING +func NewDoNothingConflictExpression() ConflictExpression { + return &doNothingConflict{} +} + +func (c doNothingConflict) Expression() Expression { + return c +} + +func (c doNothingConflict) Clone() Expression { + return c +} + +func (c doNothingConflict) Action() ConflictAction { + return DoNothingConflictAction +} + +// Creates a ConflictUpdate struct to be passed to InsertConflict +// Represents a ON CONFLICT DO UPDATE portion of an INSERT statement (ON DUPLICATE KEY UPDATE for mysql) +// +// InsertConflict(DoUpdate("target_column", update),...) -> +// INSERT INTO ... ON CONFLICT DO UPDATE SET a=b +// InsertConflict(DoUpdate("target_column", update).Where(Ex{"a": 1},...) -> +// INSERT INTO ... ON CONFLICT DO UPDATE SET a=b WHERE a=1 +func NewDoUpdateConflictExpression(target string, update interface{}) ConflictUpdateExpression { + return &conflictUpdate{target: target, update: update} +} + +func (c conflictUpdate) Expression() Expression { + return c +} + +func (c conflictUpdate) Clone() Expression { + return &conflictUpdate{ + target: c.target, + update: c.update, + whereClause: c.whereClause.Clone().(ExpressionList), + } +} + +func (c conflictUpdate) Action() ConflictAction { + return DoUpdateConflictAction +} + +// Returns the target conflict column. Only necessary for Postgres. +// Will return an error for mysql/sqlite. Will also return an error if missing from a postgres ConflictUpdate. +func (c conflictUpdate) TargetColumn() string { + return c.target +} + +// Returns the Updates which represent the ON CONFLICT DO UPDATE portion of an insert statement. If nil, +// there are no updates. +func (c conflictUpdate) Update() interface{} { + return c.update +} + +// Append to the existing Where clause for an ON CONFLICT DO UPDATE ... WHERE ... +// InsertConflict(DoNothing(),...) -> INSERT INTO ... ON CONFLICT DO NOTHING +func (c *conflictUpdate) Where(expressions ...Expression) ConflictUpdateExpression { + if c.whereClause == nil { + c.whereClause = NewExpressionList(AndType, expressions...) + } else { + c.whereClause = c.whereClause.Append(expressions...) + } + return c +} + +// Append to the existing Where clause for an ON CONFLICT DO UPDATE ... WHERE ... +// InsertConflict(DoNothing(),...) -> INSERT INTO ... ON CONFLICT DO NOTHING +func (c *conflictUpdate) WhereClause() ExpressionList { + return c.whereClause +} diff --git a/exp/cte.go b/exp/cte.go new file mode 100644 index 00000000..4282d0b6 --- /dev/null +++ b/exp/cte.go @@ -0,0 +1,23 @@ +package exp + +type commonExpr struct { + recursive bool + name LiteralExpression + subQuery Expression +} + +// Creates a new WITH common table expression for a SQLExpression, typically Datasets'. This function is used +// internally by Dataset when a CTE is added to another Dataset +func NewCommonTableExpression(recursive bool, name string, subQuery Expression) CommonTableExpression { + return commonExpr{recursive: recursive, name: NewLiteralExpression(name), subQuery: subQuery} +} + +func (ce commonExpr) Expression() Expression { return ce } + +func (ce commonExpr) Clone() Expression { + return commonExpr{recursive: ce.recursive, name: ce.name, subQuery: ce.subQuery.Clone().(SQLExpression)} +} + +func (ce commonExpr) IsRecursive() bool { return ce.recursive } +func (ce commonExpr) Name() LiteralExpression { return ce.name } +func (ce commonExpr) SubQuery() Expression { return ce.subQuery } diff --git a/exp/exp.go b/exp/exp.go new file mode 100644 index 00000000..0f37b37b --- /dev/null +++ b/exp/exp.go @@ -0,0 +1,508 @@ +package exp + +import ( + "fmt" + + "github.com/doug-martin/goqu/v7/internal/sb" +) + +// Behaviors +type ( + + // Interface that an expression should implement if it can be aliased. + Aliaseable interface { + // Returns an AliasedExpression + // I("col").As("other_col") //"col" AS "other_col" + // I("col").As(I("other_col")) //"col" AS "other_col" + As(interface{}) AliasedExpression + } + + // Interface that an expression should implement if it can be casted to another SQL type . + Castable interface { + // Casts an expression to the specified type + // I("a").Cast("numeric")//CAST("a" AS numeric) + Cast(val string) CastExpression + } + + Inable interface { + // Creates a Boolean expression for IN clauses + // I("col").In([]string{"a", "b", "c"}) //("col" IN ('a', 'b', 'c')) + In(...interface{}) BooleanExpression + // Creates a Boolean expression for NOT IN clauses + // I("col").NotIn([]string{"a", "b", "c"}) //("col" NOT IN ('a', 'b', 'c')) + NotIn(...interface{}) BooleanExpression + } + + Isable interface { + // Creates an Boolean expression IS clauses + // ds.Where(I("a").Is(nil)) //("a" IS NULL) + // ds.Where(I("a").Is(true)) //("a" IS TRUE) + // ds.Where(I("a").Is(false)) //("a" IS FALSE) + Is(interface{}) BooleanExpression + // Creates an Boolean expression IS NOT clauses + // ds.Where(I("a").IsNot(nil)) //("a" IS NOT NULL) + // ds.Where(I("a").IsNot(true)) //("a" IS NOT TRUE) + // ds.Where(I("a").IsNot(false)) //("a" IS NOT FALSE) + IsNot(interface{}) BooleanExpression + // Shortcut for Is(nil) + IsNull() BooleanExpression + // Shortcut for IsNot(nil) + IsNotNull() BooleanExpression + // Shortcut for Is(true) + IsTrue() BooleanExpression + // Shortcut for IsNot(true) + IsNotTrue() BooleanExpression + // Shortcut for Is(false) + IsFalse() BooleanExpression + // Shortcut for IsNot(false) + IsNotFalse() BooleanExpression + } + + Likeable interface { + // Creates an Boolean expression for LIKE clauses + // ds.Where(I("a").Like("a%")) //("a" LIKE 'a%') + Like(interface{}) BooleanExpression + // Creates an Boolean expression for NOT LIKE clauses + // ds.Where(I("a").NotLike("a%")) //("a" NOT LIKE 'a%') + NotLike(interface{}) BooleanExpression + // Creates an Boolean expression for case insensitive LIKE clauses + // ds.Where(I("a").ILike("a%")) //("a" ILIKE 'a%') + ILike(interface{}) BooleanExpression + // Creates an Boolean expression for case insensitive NOT LIKE clauses + // ds.Where(I("a").NotILike("a%")) //("a" NOT ILIKE 'a%') + NotILike(interface{}) BooleanExpression + } + + // Interface that an expression should implement if it can be compared with other values. + Comparable interface { + // Creates a Boolean expression comparing equality + // I("col").Eq(1) //("col" = 1) + Eq(interface{}) BooleanExpression + // Creates a Boolean expression comparing in-equality + // I("col").Neq(1) //("col" != 1) + Neq(interface{}) BooleanExpression + // Creates a Boolean expression for greater than comparisons + // I("col").Gt(1) //("col" > 1) + Gt(interface{}) BooleanExpression + // Creates a Boolean expression for greater than or equal to than comparisons + // I("col").Gte(1) //("col" >= 1) + Gte(interface{}) BooleanExpression + // Creates a Boolean expression for less than comparisons + // I("col").Lt(1) //("col" < 1) + Lt(interface{}) BooleanExpression + // Creates a Boolean expression for less than or equal to comparisons + // I("col").Lte(1) //("col" <= 1) + Lte(interface{}) BooleanExpression + } + + // Interface that an expression should implement if it can be used in a DISTINCT epxression. + Distinctable interface { + // Creates a DISTINCT clause + // I("a").Distinct() //DISTINCT("a") + Distinct() SQLFunctionExpression + } + + // Interface that an expression should implement if it can be ORDERED. + Orderable interface { + // Creates an Ordered Expression for sql ASC order + // ds.Order(I("a").Asc()) //ORDER BY "a" ASC + Asc() OrderedExpression + // Creates an Ordered Expression for sql DESC order + // ds.Order(I("a").Desc()) //ORDER BY "a" DESC + Desc() OrderedExpression + } + + Rangeable interface { + // Creates a Range expression for between comparisons + // I("col").Between(RangeVal{Start:1, End:10}) //("col" BETWEEN 1 AND 10) + Between(RangeVal) RangeExpression + // Creates a Range expression for between comparisons + // I("col").NotBetween(RangeVal{Start:1, End:10}) //("col" NOT BETWEEN 1 AND 10) + NotBetween(RangeVal) RangeExpression + } + + Updateable interface { + // Used internally by update sql + Set(interface{}) UpdateExpression + } +) + +type ( + // Alternative to writing map[string]interface{}. Can be used for Inserts, Updates or Deletes + Record map[string]interface{} + // Parent of all expression types + Expression interface { + Clone() Expression + Expression() Expression + } + // An Expression that generates its own sql (e.g Dataset) + SQLExpression interface { + Expression + ToSQL() (string, []interface{}, error) + } + + AppendableExpression interface { + Expression + AppendSQL(b sb.SQLBuilder) + GetClauses() Clauses + } + // Expression for Aliased expressions + // I("a").As("b") -> "a" AS "b" + // SUM("a").As(I("a_sum")) -> SUM("a") AS "a_sum" + AliasedExpression interface { + Expression + // Returns the Epxression being aliased + Aliased() Expression + // Returns the alias value as an identiier expression + GetAs() IdentifierExpression + } + + BooleanOperation int + BooleanExpression interface { + Expression + // Returns the operator for the expression + Op() BooleanOperation + // The left hand side of the expression (e.g. I("a") + LHS() Expression + // The right hand side of the expression could be a primitive value, dataset, or expression + RHS() interface{} + } + // An Expression that represents another Expression casted to a SQL type + CastExpression interface { + Expression + Aliaseable + Comparable + Inable + Isable + Likeable + Orderable + Distinctable + Rangeable + // The exression being casted + Casted() Expression + // The the SQL type to cast the expression to + Type() LiteralExpression + } + // A list of columns. Typically used internally by Select, Order, From + ColumnListExpression interface { + Expression + // Returns the list of columns + Columns() []Expression + // Returns true if the column list is empty + IsEmpty() bool + // Returns a new ColumnListExpression with the columns appended. + Append(...Expression) ColumnListExpression + } + CompoundType int + CompoundExpression interface { + Expression + Type() CompoundType + RHS() Expression + } + // An Expression that the ON CONFLICT/ON DUPLICATE KEY portion of an INSERT statement + ConflictAction int + ConflictExpression interface { + Expression + Action() ConflictAction + } + ConflictUpdateExpression interface { + ConflictExpression + TargetColumn() string + Where(expressions ...Expression) ConflictUpdateExpression + WhereClause() ExpressionList + Update() interface{} + } + CommonTableExpression interface { + Expression + IsRecursive() bool + // Returns the alias name for the extracted expression + Name() LiteralExpression + // Returns the Expression being extracted + SubQuery() Expression + } + ExpressionListType int + // A list of expressions that should be joined together + // And(I("a").Eq(10), I("b").Eq(11)) //(("a" = 10) AND ("b" = 11)) + // Or(I("a").Eq(10), I("b").Eq(11)) //(("a" = 10) OR ("b" = 11)) + ExpressionList interface { + Expression + // Returns type (e.g. OR, AND) + Type() ExpressionListType + // Slice of expressions that should be joined together + Expressions() []Expression + // Returns a new expression list with the given expressions appended to the current Expressions list + Append(...Expression) ExpressionList + } + // An Identifier that can contain schema, table and column identifiers + IdentifierExpression interface { + Expression + Aliaseable + Comparable + Inable + Isable + Likeable + Rangeable + Orderable + Updateable + Distinctable + Castable + // Returns a new IdentifierExpression with the specified schema + Schema(string) IdentifierExpression + // Returns the current schema + GetSchema() string + // Returns a new IdentifierExpression with the specified table + Table(string) IdentifierExpression + // Returns the current table + GetTable() string + // Returns a new IdentifierExpression with the specified column + Col(interface{}) IdentifierExpression + // Returns the current column + GetCol() interface{} + // Returns a new IdentifierExpression with the column set to * + // I("my_table").All() //"my_table".* + All() IdentifierExpression + } + InsertExpression interface { + Expression + IsEmpty() bool + IsInsertFrom() bool + From() AppendableExpression + Cols() ColumnListExpression + SetCols(cols ColumnListExpression) InsertExpression + Vals() [][]interface{} + SetVals([][]interface{}) InsertExpression + OnConflict() ConflictExpression + SetOnConflict(ce ConflictExpression) InsertExpression + DoNothing() InsertExpression + DoUpdate(target string, update interface{}) InsertExpression + } + + JoinType int + JoinExpression interface { + Expression + JoinType() JoinType + IsConditioned() bool + Table() Expression + } + // Parent type for join expressions + ConditionedJoinExpression interface { + JoinExpression + Condition() JoinCondition + IsConditionEmpty() bool + } + + // Expression for representing "literal" sql. + // L("col = 1") -> col = 1) + // L("? = ?", I("col"), 1) -> "col" = 1 + LiteralExpression interface { + Expression + Aliaseable + Comparable + Isable + Inable + Likeable + Rangeable + Orderable + // Returns the literal sql + Literal() string + // Arguments to be replaced within the sql + Args() []interface{} + } + + nullSortType int + sortDirection int + // An expression for specifying sort order and options + OrderedExpression interface { + Expression + // The expression being sorted + SortExpression() Expression + // Sort direction (e.g. ASC, DESC) + IsAsc() bool + // If the adapter supports it null sort type (e.g. NULLS FIRST, NULLS LAST) + NullSortType() nullSortType + // Returns a new OrderedExpression with NullSortType set to NULLS_FIRST + NullsFirst() OrderedExpression + // Returns a new OrderedExpression with NullSortType set to NULLS_LAST + NullsLast() OrderedExpression + } + + RangeOperation int + RangeExpression interface { + Expression + // Returns the operator for the expression + Op() RangeOperation + // The left hand side of the expression (e.g. I("a") + LHS() Expression + // The right hand side of the expression could be a primitive value, dataset, or expression + RHS() RangeVal + } + RangeVal interface { + Start() interface{} + End() interface{} + } + + // Expression for representing a SQLFunction(e.g. COUNT, SUM, MIN, MAX...) + SQLFunctionExpression interface { + Expression + Aliaseable + Rangeable + Comparable + Isable + Inable + Likeable + // The function name + Name() string + // Arguments to be passed to the function + Args() []interface{} + } + + UpdateExpression interface { + Col() IdentifierExpression + Val() interface{} + } +) + +const ( + UnionCompoundType CompoundType = iota + UnionAllCompoundType + IntersectCompoundType + IntersectAllCompoundType + + DoNothingConflictAction ConflictAction = iota + DoUpdateConflictAction + + AndType ExpressionListType = iota + OrType + + InnerJoinType JoinType = iota + FullOuterJoinType + RightOuterJoinType + LeftOuterJoinType + FullJoinType + RightJoinType + LeftJoinType + NaturalJoinType + NaturalLeftJoinType + NaturalRightJoinType + NaturalFullJoinType + CrossJoinType + + UsingJoinCondType JoinConditionType = iota + OnJoinCondType + + // Default null sort type with no null sort order + NoNullsSortType nullSortType = iota + // NULLS FIRST + NullsFirstSortType + // NULLS LAST + NullsLastSortType + // ASC + ascDir sortDirection = iota + // DESC + descSortDir + + // BETWEEN + BetweenOp RangeOperation = iota + // NOT BETWEEN + NotBetweenOp + + // = + EqOp BooleanOperation = iota + // != or <> + NeqOp + // IS + IsOp + // IS NOT + IsNotOp + // > + GtOp + // >= + GteOp + // < + LtOp + // <= + LteOp + // IN + InOp + // NOT IN + NotInOp + // LIKE, LIKE BINARY... + LikeOp + // NOT LIKE, NOT LIKE BINARY... + NotLikeOp + // ILIKE, LIKE + ILikeOp + // NOT ILIKE, NOT LIKE + NotILikeOp + // ~, REGEXP BINARY + RegexpLikeOp + // !~, NOT REGEXP BINARY + RegexpNotLikeOp + // ~*, REGEXP + RegexpILikeOp + // !~*, NOT REGEXP + RegexpNotILikeOp +) + +var ( + ConditionedJoinTypes = map[JoinType]bool{ + InnerJoinType: true, + FullOuterJoinType: true, + RightOuterJoinType: true, + LeftOuterJoinType: true, + FullJoinType: true, + RightJoinType: true, + LeftJoinType: true, + } + // used internally for inverting operators + operatorInversions = map[BooleanOperation]BooleanOperation{ + IsOp: IsNotOp, + EqOp: NeqOp, + GtOp: LteOp, + GteOp: LtOp, + LtOp: GteOp, + LteOp: GtOp, + InOp: NotInOp, + LikeOp: NotLikeOp, + ILikeOp: NotILikeOp, + RegexpLikeOp: RegexpNotLikeOp, + RegexpILikeOp: RegexpNotILikeOp, + IsNotOp: IsOp, + NeqOp: EqOp, + NotInOp: InOp, + NotLikeOp: LikeOp, + NotILikeOp: ILikeOp, + RegexpNotLikeOp: RegexpLikeOp, + RegexpNotILikeOp: RegexpILikeOp, + } +) + +func (jt JoinType) String() string { + switch jt { + case InnerJoinType: + return "InnerJoinType" + case FullOuterJoinType: + return "FullOuterJoinType" + case RightOuterJoinType: + return "RightOuterJoinType" + case LeftOuterJoinType: + return "LeftOuterJoinType" + case FullJoinType: + return "FullJoinType" + case RightJoinType: + return "RightJoinType" + case LeftJoinType: + return "LeftJoinType" + case NaturalJoinType: + return "NaturalJoinType" + case NaturalLeftJoinType: + return "NaturalLeftJoinType" + case NaturalRightJoinType: + return "NaturalRightJoinType" + case NaturalFullJoinType: + return "NaturalFullJoinType" + case CrossJoinType: + return "CrossJoinType" + } + return fmt.Sprintf("%d", jt) +} diff --git a/exp/exp_list.go b/exp/exp_list.go new file mode 100644 index 00000000..afd1e73b --- /dev/null +++ b/exp/exp_list.go @@ -0,0 +1,143 @@ +package exp + +import ( + "sort" + "strings" + + "github.com/doug-martin/goqu/v7/internal/errors" +) + +type ( + expressionList struct { + operator ExpressionListType + expressions []Expression + } +) + +// A list of expressions that should be ORed together +// Or(I("a").Eq(10), I("b").Eq(11)) //(("a" = 10) OR ("b" = 11)) +func NewExpressionList(operator ExpressionListType, expressions ...Expression) ExpressionList { + return expressionList{operator: operator, expressions: expressions} +} + +func (el expressionList) Clone() Expression { + newExps := make([]Expression, len(el.expressions)) + for i, exp := range el.expressions { + newExps[i] = exp.Clone() + } + return expressionList{operator: el.operator, expressions: newExps} +} + +func (el expressionList) Expression() Expression { + return el +} + +func (el expressionList) Type() ExpressionListType { + return el.operator +} + +func (el expressionList) Expressions() []Expression { + return el.expressions +} + +func (el expressionList) Append(expressions ...Expression) ExpressionList { + ret := new(expressionList) + ret.operator = el.operator + exps := make([]Expression, len(el.expressions)) + copy(exps, el.expressions) + exps = append(exps, expressions...) + ret.expressions = exps + return ret +} + +func getExMapKeys(ex map[string]interface{}) []string { + var keys []string + for key := range ex { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +func mapToExpressionList(ex map[string]interface{}, eType ExpressionListType) (ExpressionList, error) { + keys := getExMapKeys(ex) + ret := make([]Expression, len(keys)) + for i, key := range keys { + lhs := ParseIdentifier(key) + rhs := ex[key] + var exp Expression + if op, ok := rhs.(Op); ok { + ors, err := createOredExpressionFromMap(lhs, op) + if err != nil { + return nil, err + } + exp = NewExpressionList(OrType, ors...) + } else { + exp = lhs.Eq(rhs) + } + ret[i] = exp + } + if eType == OrType { + return NewExpressionList(OrType, ret...), nil + } + return NewExpressionList(AndType, ret...), nil +} + +func createOredExpressionFromMap(lhs IdentifierExpression, op Op) ([]Expression, error) { + opKeys := getExMapKeys(op) + ors := make([]Expression, len(opKeys)) + for j, opKey := range opKeys { + if exp, err := createExpressionFromOp(lhs, opKey, op); err != nil { + return nil, err + } else if exp != nil { + ors[j] = exp + } + } + return ors, nil +} + +func createExpressionFromOp(lhs IdentifierExpression, opKey string, op Op) (exp Expression, err error) { + switch strings.ToLower(opKey) { + case "eq": + exp = lhs.Eq(op[opKey]) + case "neq": + exp = lhs.Neq(op[opKey]) + case "is": + exp = lhs.Is(op[opKey]) + case "isnot": + exp = lhs.IsNot(op[opKey]) + case "gt": + exp = lhs.Gt(op[opKey]) + case "gte": + exp = lhs.Gte(op[opKey]) + case "lt": + exp = lhs.Lt(op[opKey]) + case "lte": + exp = lhs.Lte(op[opKey]) + case "in": + exp = lhs.In(op[opKey]) + case "notin": + exp = lhs.NotIn(op[opKey]) + case "like": + exp = lhs.Like(op[opKey]) + case "notlike": + exp = lhs.NotLike(op[opKey]) + case "ilike": + exp = lhs.ILike(op[opKey]) + case "notilike": + exp = lhs.NotILike(op[opKey]) + case "between": + rangeVal, ok := op[opKey].(RangeVal) + if ok { + exp = lhs.Between(rangeVal) + } + case "notbetween": + rangeVal, ok := op[opKey].(RangeVal) + if ok { + exp = lhs.NotBetween(rangeVal) + } + default: + err = errors.New("unsupported expression type %s", op) + } + return exp, err +} diff --git a/exp/exp_map.go b/exp/exp_map.go new file mode 100644 index 00000000..4c639a54 --- /dev/null +++ b/exp/exp_map.go @@ -0,0 +1,48 @@ +package exp + +type ( + // A map of expressions to be ANDed together where the keys are string that will be used as Identifiers and values + // will be used in a boolean operation. + // The Ex map can be used in tandem with Op map to create more complex expression such as LIKE, GT, LT... + // See examples. + Ex map[string]interface{} + // A map of expressions to be ORed together where the keys are string that will be used as Identifiers and values + // will be used in a boolean operation. + // The Ex map can be used in tandem with Op map to create more complex expression such as LIKE, GT, LT... + // See examples. + ExOr map[string]interface{} + // Used in tandem with the Ex map to create complex comparisons such as LIKE, GT, LT... See examples + Op map[string]interface{} +) + +func (e Ex) Expression() Expression { + return e +} + +func (e Ex) Clone() Expression { + ret := Ex{} + for key, val := range e { + ret[key] = val + } + return ret +} + +func (e Ex) ToExpressions() (ExpressionList, error) { + return mapToExpressionList(e, AndType) +} + +func (eo ExOr) Expression() Expression { + return eo +} + +func (eo ExOr) Clone() Expression { + ret := Ex{} + for key, val := range eo { + ret[key] = val + } + return ret +} + +func (eo ExOr) ToExpressions() (ExpressionList, error) { + return mapToExpressionList(eo, OrType) +} diff --git a/exp/exp_test.go b/exp/exp_test.go new file mode 100644 index 00000000..acbd4c19 --- /dev/null +++ b/exp/exp_test.go @@ -0,0 +1,7 @@ +package exp + +// todo move to expressions +// func (me *datasetTest) TestLiteralUnsupportedType() { +// t := me.T() +// assert.EqualError(t, From("test").Literal(NewSQLBuilder(false), struct{}{}), "goqu: Unable to encode value {}") +// } diff --git a/exp/func.go b/exp/func.go new file mode 100644 index 00000000..df60df9f --- /dev/null +++ b/exp/func.go @@ -0,0 +1,48 @@ +package exp + +type ( + sqlFunctionExpression struct { + name string + args []interface{} + } +) + +// Creates a new SQLFunctionExpression with the given name and arguments +func NewSQLFunctionExpression(name string, args ...interface{}) SQLFunctionExpression { + return sqlFunctionExpression{name: name, args: args} +} + +func (sfe sqlFunctionExpression) Clone() Expression { + return sqlFunctionExpression{name: sfe.name, args: sfe.args} +} + +func (sfe sqlFunctionExpression) Expression() Expression { return sfe } +func (sfe sqlFunctionExpression) Args() []interface{} { return sfe.args } +func (sfe sqlFunctionExpression) Name() string { return sfe.name } +func (sfe sqlFunctionExpression) As(val interface{}) AliasedExpression { return aliased(sfe, val) } +func (sfe sqlFunctionExpression) Eq(val interface{}) BooleanExpression { return eq(sfe, val) } +func (sfe sqlFunctionExpression) Neq(val interface{}) BooleanExpression { return neq(sfe, val) } +func (sfe sqlFunctionExpression) Gt(val interface{}) BooleanExpression { return gt(sfe, val) } +func (sfe sqlFunctionExpression) Gte(val interface{}) BooleanExpression { return gte(sfe, val) } +func (sfe sqlFunctionExpression) Lt(val interface{}) BooleanExpression { return lt(sfe, val) } +func (sfe sqlFunctionExpression) Lte(val interface{}) BooleanExpression { return lte(sfe, val) } +func (sfe sqlFunctionExpression) Between(val RangeVal) RangeExpression { return between(sfe, val) } +func (sfe sqlFunctionExpression) NotBetween(val RangeVal) RangeExpression { return notBetween(sfe, val) } +func (sfe sqlFunctionExpression) Like(val interface{}) BooleanExpression { return like(sfe, val) } +func (sfe sqlFunctionExpression) NotLike(val interface{}) BooleanExpression { return notLike(sfe, val) } +func (sfe sqlFunctionExpression) ILike(val interface{}) BooleanExpression { return iLike(sfe, val) } +func (sfe sqlFunctionExpression) NotILike(val interface{}) BooleanExpression { + return notILike(sfe, val) +} +func (sfe sqlFunctionExpression) In(vals ...interface{}) BooleanExpression { return in(sfe, vals...) } +func (sfe sqlFunctionExpression) NotIn(vals ...interface{}) BooleanExpression { + return notIn(sfe, vals...) +} +func (sfe sqlFunctionExpression) Is(val interface{}) BooleanExpression { return is(sfe, val) } +func (sfe sqlFunctionExpression) IsNot(val interface{}) BooleanExpression { return isNot(sfe, val) } +func (sfe sqlFunctionExpression) IsNull() BooleanExpression { return is(sfe, nil) } +func (sfe sqlFunctionExpression) IsNotNull() BooleanExpression { return isNot(sfe, nil) } +func (sfe sqlFunctionExpression) IsTrue() BooleanExpression { return is(sfe, true) } +func (sfe sqlFunctionExpression) IsNotTrue() BooleanExpression { return isNot(sfe, true) } +func (sfe sqlFunctionExpression) IsFalse() BooleanExpression { return is(sfe, false) } +func (sfe sqlFunctionExpression) IsNotFalse() BooleanExpression { return isNot(sfe, false) } diff --git a/exp/ident.go b/exp/ident.go new file mode 100644 index 00000000..2c4534d5 --- /dev/null +++ b/exp/ident.go @@ -0,0 +1,133 @@ +package exp + +import "strings" + +type ( + identifier struct { + schema string + table string + col interface{} + } +) + +func ParseIdentifier(ident string) IdentifierExpression { + parts := strings.Split(ident, ".") + switch len(parts) { + case 2: + return NewIdentifierExpression("", parts[0], parts[1]) + case 3: + return NewIdentifierExpression(parts[0], parts[1], parts[2]) + } + return NewIdentifierExpression("", "", ident) +} + +func NewIdentifierExpression(schema, table, col string) IdentifierExpression { + return identifier{}.Schema(schema).Table(table).Col(col) +} + +func (i identifier) clone() identifier { + return identifier{schema: i.schema, table: i.table, col: i.col} +} + +func (i identifier) Clone() Expression { + return i.clone() +} + +// Sets the table on the current identifier +// I("col").Table("table") -> "table"."col" //postgres +// I("col").Table("table") -> `table`.`col` //mysql +// I("col").Table("table") -> `table`.`col` //sqlite3 +func (i identifier) Table(table string) IdentifierExpression { + i.table = table + return i +} + +func (i identifier) GetTable() string { + return i.table +} + +// Sets the table on the current identifier +// I("table").Schema("schema") -> "schema"."table" //postgres +// I("col").Schema("table") -> `schema`.`table` //mysql +// I("col").Schema("table") -> `schema`.`table` //sqlite3 +func (i identifier) Schema(schema string) IdentifierExpression { + i.schema = schema + return i +} + +func (i identifier) GetSchema() string { + return i.schema +} + +// Sets the table on the current identifier +// I("table").Col("col") -> "table"."col" //postgres +// I("table").Schema("col") -> `table`.`col` //mysql +// I("table").Schema("col") -> `table`.`col` //sqlite3 +func (i identifier) Col(col interface{}) IdentifierExpression { + if col == "*" { + i.col = Star() + } else { + i.col = col + } + return i +} + +func (i identifier) Expression() Expression { return i } + +// Qualifies the epression with a * literal (e.g. "table".*) +func (i identifier) All() IdentifierExpression { return i.Col("*") } + +// Gets the column identifier +func (i identifier) GetCol() interface{} { return i.col } + +// Used within updates to set a column value +func (i identifier) Set(val interface{}) UpdateExpression { return set(i, val) } + +// Alias an identifier (e.g "my_col" AS "other_col") +func (i identifier) As(val interface{}) AliasedExpression { return aliased(i, val) } + +// Returns a BooleanExpression for equality (e.g "my_col" = 1) +func (i identifier) Eq(val interface{}) BooleanExpression { return eq(i, val) } + +// Returns a BooleanExpression for in equality (e.g "my_col" != 1) +func (i identifier) Neq(val interface{}) BooleanExpression { return neq(i, val) } + +// Returns a BooleanExpression for checking that a identifier is greater than another value (e.g "my_col" > 1) +func (i identifier) Gt(val interface{}) BooleanExpression { return gt(i, val) } + +// Returns a BooleanExpression for checking that a identifier is greater than or equal to another value +// (e.g "my_col" >= 1) +func (i identifier) Gte(val interface{}) BooleanExpression { return gte(i, val) } + +// Returns a BooleanExpression for checking that a identifier is less than another value (e.g "my_col" < 1) +func (i identifier) Lt(val interface{}) BooleanExpression { return lt(i, val) } + +// Returns a BooleanExpression for checking that a identifier is less than or equal to another value +// (e.g "my_col" <= 1) +func (i identifier) Lte(val interface{}) BooleanExpression { return lte(i, val) } + +// Returns a BooleanExpression for checking that a identifier is in a list of values or (e.g "my_col" > 1) +func (i identifier) In(vals ...interface{}) BooleanExpression { return in(i, vals...) } +func (i identifier) NotIn(vals ...interface{}) BooleanExpression { return notIn(i, vals...) } +func (i identifier) Like(val interface{}) BooleanExpression { return like(i, val) } +func (i identifier) NotLike(val interface{}) BooleanExpression { return notLike(i, val) } +func (i identifier) ILike(val interface{}) BooleanExpression { return iLike(i, val) } +func (i identifier) NotILike(val interface{}) BooleanExpression { return notILike(i, val) } +func (i identifier) Is(val interface{}) BooleanExpression { return is(i, val) } +func (i identifier) IsNot(val interface{}) BooleanExpression { return isNot(i, val) } +func (i identifier) IsNull() BooleanExpression { return is(i, nil) } +func (i identifier) IsNotNull() BooleanExpression { return isNot(i, nil) } +func (i identifier) IsTrue() BooleanExpression { return is(i, true) } +func (i identifier) IsNotTrue() BooleanExpression { return isNot(i, true) } +func (i identifier) IsFalse() BooleanExpression { return is(i, false) } +func (i identifier) IsNotFalse() BooleanExpression { return isNot(i, false) } +func (i identifier) Asc() OrderedExpression { return asc(i) } +func (i identifier) Desc() OrderedExpression { return desc(i) } +func (i identifier) Distinct() SQLFunctionExpression { return NewSQLFunctionExpression("DISTINCT", i) } +func (i identifier) Cast(t string) CastExpression { return NewCastExpression(i, t) } + +// Returns a RangeExpression for checking that a identifier is between two values (e.g "my_col" BETWEEN 1 AND 10) +func (i identifier) Between(val RangeVal) RangeExpression { return between(i, val) } + +// Returns a RangeExpression for checking that a identifier is between two values (e.g "my_col" BETWEEN 1 AND 10) +func (i identifier) NotBetween(val RangeVal) RangeExpression { return notBetween(i, val) } diff --git a/exp/insert.go b/exp/insert.go new file mode 100644 index 00000000..c738eeb7 --- /dev/null +++ b/exp/insert.go @@ -0,0 +1,182 @@ +package exp + +import ( + "reflect" + "sort" + + "github.com/doug-martin/goqu/v7/internal/errors" + "github.com/doug-martin/goqu/v7/internal/tag" + "github.com/doug-martin/goqu/v7/internal/util" +) + +type ( + insert struct { + from AppendableExpression + cols ColumnListExpression + vals [][]interface{} + onConflict ConflictExpression + } +) + +func NewInsertExpression(rows ...interface{}) (insertExpression InsertExpression, err error) { + switch len(rows) { + case 0: + return new(insert), nil + case 1: + val := reflect.ValueOf(rows[0]) + if val.Kind() == reflect.Slice { + vals := make([]interface{}, val.Len()) + for i := 0; i < val.Len(); i++ { + vals[i] = val.Index(i).Interface() + } + return NewInsertExpression(vals...) + } + if ae, ok := rows[0].(AppendableExpression); ok { + return &insert{from: ae}, nil + } + + } + return newInsert(rows...) +} + +func (i *insert) Expression() Expression { + return i +} + +func (i *insert) Clone() Expression { + return i.clone() +} + +func (i *insert) clone() *insert { + return &insert{from: i.from, cols: i.cols, vals: i.vals, onConflict: i.onConflict} +} + +func (i *insert) IsEmpty() bool { + return i.from == nil && (i.cols == nil || i.cols.IsEmpty()) +} + +func (i *insert) IsInsertFrom() bool { + return i.from != nil +} +func (i *insert) From() AppendableExpression { + return i.from +} +func (i *insert) Cols() ColumnListExpression { + return i.cols +} + +func (i *insert) SetCols(cols ColumnListExpression) InsertExpression { + ci := i.clone() + ci.cols = cols + return ci +} + +func (i *insert) Vals() [][]interface{} { + return i.vals +} + +func (i *insert) SetVals(vals [][]interface{}) InsertExpression { + ci := i.clone() + ci.vals = vals + return ci +} + +func (i *insert) OnConflict() ConflictExpression { + return i.onConflict +} + +func (i *insert) SetOnConflict(ce ConflictExpression) InsertExpression { + ci := i.clone() + ci.onConflict = ce + return ci +} + +func (i *insert) DoNothing() InsertExpression { + return i.SetOnConflict(NewDoNothingConflictExpression()) + +} +func (i *insert) DoUpdate(target string, update interface{}) InsertExpression { + return i.SetOnConflict(NewDoUpdateConflictExpression(target, update)) +} + +// parses the rows gathering and sorting unique columns and values for each record +func newInsert(rows ...interface{}) (insertExp InsertExpression, err error) { + var mapKeys util.ValueSlice + rowValue := reflect.Indirect(reflect.ValueOf(rows[0])) + rowType := rowValue.Type() + rowKind := rowValue.Kind() + vals := make([][]interface{}, len(rows)) + var columns ColumnListExpression + for i, row := range rows { + if rowType != reflect.Indirect(reflect.ValueOf(row)).Type() { + return nil, errors.New( + "rows must be all the same type expected %+v got %+v", + rowType, + reflect.Indirect(reflect.ValueOf(row)).Type(), + ) + } + newRowValue := reflect.Indirect(reflect.ValueOf(row)) + switch rowKind { + case reflect.Map: + if columns == nil { + mapKeys = util.ValueSlice(newRowValue.MapKeys()) + sort.Sort(mapKeys) + colKeys := make([]interface{}, len(mapKeys)) + for j, key := range mapKeys { + colKeys[j] = key.Interface() + } + columns = NewColumnListExpression(colKeys...) + } + newMapKeys := util.ValueSlice(newRowValue.MapKeys()) + if len(newMapKeys) != len(mapKeys) { + return nil, errors.New("rows with different value length expected %d got %d", len(mapKeys), len(newMapKeys)) + } + if !mapKeys.Equal(newMapKeys) { + return nil, errors.New("rows with different keys expected %s got %s", mapKeys.String(), newMapKeys.String()) + } + rowVals := make([]interface{}, len(mapKeys)) + for j, key := range mapKeys { + rowVals[j] = newRowValue.MapIndex(key).Interface() + } + vals[i] = rowVals + case reflect.Struct: + rowCols, rowVals := getFieldsValues(newRowValue) + if columns == nil { + columns = NewColumnListExpression(rowCols...) + } + vals[i] = rowVals + default: + return nil, errors.New( + "unsupported insert must be map, goqu.Record, or struct type got: %T", + row, + ) + } + } + return &insert{cols: columns, vals: vals}, nil +} + +func getFieldsValues(value reflect.Value) (rowCols, rowVals []interface{}) { + if value.IsValid() { + for i := 0; i < value.NumField(); i++ { + v := value.Field(i) + t := value.Type().Field(i) + if !t.Anonymous { + if canInsertField(&t) { + rowCols = append(rowCols, t.Tag.Get("db")) + rowVals = append(rowVals, v.Interface()) + } + } else { + cols, vals := getFieldsValues(reflect.Indirect(reflect.ValueOf(v.Interface()))) + rowCols = append(rowCols, cols...) + rowVals = append(rowVals, vals...) + } + } + } + return rowCols, rowVals +} + +func canInsertField(field *reflect.StructField) bool { + goquTag := tag.New("goqu", field.Tag) + dbTag := tag.New("db", field.Tag) + return !goquTag.Contains("skipinsert") && !(dbTag.IsEmpty() || dbTag.Equals("-")) +} diff --git a/exp/insert_test.go b/exp/insert_test.go new file mode 100644 index 00000000..5e2d3dea --- /dev/null +++ b/exp/insert_test.go @@ -0,0 +1,229 @@ +package exp + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type testAppendableExpression struct { + AppendableExpression + sql string + args []interface{} + err error + clauses Clauses +} + +func newTestAppendableExpression(sql string, args []interface{}, err error, clauses Clauses) AppendableExpression { + if clauses == nil { + clauses = NewClauses() + } + return &testAppendableExpression{sql: sql, args: args, err: err, clauses: clauses} +} + +func (tae *testAppendableExpression) Expression() Expression { + return tae +} + +func (tae *testAppendableExpression) GetClauses() Clauses { + return tae.clauses +} + +func (tae *testAppendableExpression) Clone() Expression { + return tae +} + +type insertExpressionTestSuite struct { + suite.Suite +} + +func (iets *insertExpressionTestSuite) TestNewInsertExpression_withDifferentRecordTypes() { + t := iets.T() + type testRecord struct { + C string `db:"c"` + } + type testRecord2 struct { + C string `db:"c"` + } + _, err := NewInsertExpression( + testRecord{C: "v1"}, + Record{"c": "v2"}, + ) + assert.EqualError(t, err, "goqu: rows must be all the same type expected exp.testRecord got exp.Record") + _, err = NewInsertExpression( + testRecord{C: "v1"}, + testRecord2{C: "v2"}, + ) + assert.EqualError(t, err, "goqu: rows must be all the same type expected exp.testRecord got exp.testRecord2") +} + +func (iets *insertExpressionTestSuite) TestNewInsertExpression_withInvalidValue() { + t := iets.T() + _, err := NewInsertExpression(true) + assert.EqualError(t, err, "goqu: unsupported insert must be map, goqu.Record, or struct type got: bool") +} + +func (iets *insertExpressionTestSuite) TestNewInsertExpression_withNoValues() { + t := iets.T() + ie, err := NewInsertExpression() + assert.NoError(t, err) + eie := new(insert) + assert.Equal(t, eie, ie) + assert.True(t, ie.IsEmpty()) + assert.False(t, ie.IsInsertFrom()) +} + +func (iets *insertExpressionTestSuite) TestNewInsertExpression_appendableExpression() { + t := iets.T() + + ae := newTestAppendableExpression("test ae", nil, nil, nil) + + ie, err := NewInsertExpression(ae) + assert.NoError(t, err) + eie := &insert{from: ae} + assert.Equal(t, eie, ie) + assert.False(t, ie.IsEmpty()) + assert.True(t, ie.IsInsertFrom()) +} + +func (iets *insertExpressionTestSuite) TestNewInsertExpression_withRecords() { + t := iets.T() + ie, err := NewInsertExpression(Record{"c": "a"}, Record{"c": "b"}) + assert.NoError(t, err) + eie := new(insert). + SetCols(NewColumnListExpression("c")). + SetVals([][]interface{}{{"a"}, {"b"}}) + assert.Equal(t, eie, ie) + assert.False(t, ie.IsEmpty()) + assert.False(t, ie.IsInsertFrom()) +} + +func (iets *insertExpressionTestSuite) TestNewInsertExpression_withRecordOfDifferentLength() { + t := iets.T() + _, err := NewInsertExpression(Record{"c": "a"}, Record{"c": "b", "c2": "d"}) + assert.EqualError(t, err, "goqu: rows with different value length expected 1 got 2") +} + +func (iets *insertExpressionTestSuite) TestNewInsertExpression_withRecordWithDifferentkeys() { + t := iets.T() + _, err := NewInsertExpression(Record{"c1": "a"}, Record{"c2": "b"}) + assert.EqualError(t, err, `goqu: rows with different keys expected ["c1"] got ["c2"]`) +} + +func (iets *insertExpressionTestSuite) TestNewInsertExpression_withMap() { + t := iets.T() + ie, err := NewInsertExpression( + map[string]interface{}{"c": "a"}, + map[string]interface{}{"c": "b"}, + ) + assert.NoError(t, err) + eie := new(insert). + SetCols(NewColumnListExpression("c")). + SetVals([][]interface{}{{"a"}, {"b"}}) + assert.Equal(t, eie, ie) + assert.False(t, ie.IsEmpty()) + assert.False(t, ie.IsInsertFrom()) +} + +func (iets *insertExpressionTestSuite) TestNewInsertExpression_withStructs() { + type testRecord struct { + C string `db:"c"` + } + t := iets.T() + ie, err := NewInsertExpression( + testRecord{C: "a"}, + testRecord{C: "b"}, + ) + assert.NoError(t, err) + eie := new(insert). + SetCols(NewColumnListExpression("c")). + SetVals([][]interface{}{{"a"}, {"b"}}) + assert.Equal(t, eie, ie) + assert.False(t, ie.IsEmpty()) + assert.False(t, ie.IsInsertFrom()) +} + +func (iets *insertExpressionTestSuite) TestNewInsertExpression_withStructPointers() { + type testRecord struct { + C string `db:"c"` + } + t := iets.T() + ie, err := NewInsertExpression( + &testRecord{C: "a"}, + &testRecord{C: "b"}, + ) + assert.NoError(t, err) + eie := new(insert). + SetCols(NewColumnListExpression("c")). + SetVals([][]interface{}{{"a"}, {"b"}}) + assert.Equal(t, eie, ie) + assert.False(t, ie.IsEmpty()) + assert.False(t, ie.IsInsertFrom()) +} + +func (iets *insertExpressionTestSuite) TestNewInsertExpression_withStructsWithEmbeddedStructs() { + type Phone struct { + Primary string `db:"primary_phone"` + Home string `db:"home_phone"` + } + type item struct { + Phone + Address string `db:"address"` + Name string `db:"name"` + } + t := iets.T() + ie, err := NewInsertExpression( + item{Address: "111 Test Addr", Name: "Test1", Phone: Phone{Home: "123123", Primary: "456456"}}, + item{Address: "211 Test Addr", Name: "Test2", Phone: Phone{Home: "123123", Primary: "456456"}}, + item{Address: "311 Test Addr", Name: "Test3", Phone: Phone{Home: "123123", Primary: "456456"}}, + item{Address: "411 Test Addr", Name: "Test4", Phone: Phone{Home: "123123", Primary: "456456"}}, + ) + assert.NoError(t, err) + eie := new(insert). + SetCols(NewColumnListExpression("primary_phone", "home_phone", "address", "name")). + SetVals([][]interface{}{ + {"456456", "123123", "111 Test Addr", "Test1"}, + {"456456", "123123", "211 Test Addr", "Test2"}, + {"456456", "123123", "311 Test Addr", "Test3"}, + {"456456", "123123", "411 Test Addr", "Test4"}, + }) + assert.Equal(t, eie, ie) + assert.False(t, ie.IsEmpty()) + assert.False(t, ie.IsInsertFrom()) +} + +func (iets *insertExpressionTestSuite) TestNewInsertExpression_withStructsWithEmbeddedStructPointers() { + type Phone struct { + Primary string `db:"primary_phone"` + Home string `db:"home_phone"` + } + type item struct { + *Phone + Address string `db:"address"` + Name string `db:"name"` + } + t := iets.T() + ie, err := NewInsertExpression( + item{Address: "111 Test Addr", Name: "Test1", Phone: &Phone{Home: "123123", Primary: "456456"}}, + item{Address: "211 Test Addr", Name: "Test2", Phone: &Phone{Home: "123123", Primary: "456456"}}, + item{Address: "311 Test Addr", Name: "Test3", Phone: &Phone{Home: "123123", Primary: "456456"}}, + item{Address: "411 Test Addr", Name: "Test4", Phone: &Phone{Home: "123123", Primary: "456456"}}, + ) + assert.NoError(t, err) + eie := new(insert). + SetCols(NewColumnListExpression("primary_phone", "home_phone", "address", "name")). + SetVals([][]interface{}{ + {"456456", "123123", "111 Test Addr", "Test1"}, + {"456456", "123123", "211 Test Addr", "Test2"}, + {"456456", "123123", "311 Test Addr", "Test3"}, + {"456456", "123123", "411 Test Addr", "Test4"}, + }) + assert.Equal(t, eie, ie) + assert.False(t, ie.IsEmpty()) + assert.False(t, ie.IsInsertFrom()) +} + +func TestInsertExpressionSuite(t *testing.T) { + suite.Run(t, new(insertExpressionTestSuite)) +} diff --git a/exp/join.go b/exp/join.go new file mode 100644 index 00000000..44ca93f7 --- /dev/null +++ b/exp/join.go @@ -0,0 +1,140 @@ +package exp + +type ( + joinExpression struct { + isConditioned bool + // The JoinType + joinType JoinType + // The table expressions (e.g. LEFT JOIN "my_table", ON (....)) + table Expression + } + // todo fix this to use new interfaces + // Container for all joins within a dataset + conditionedJoin struct { + joinExpression + // The condition to join (e.g. USING("a", "b"), ON("my_table"."fkey" = "other_table"."id") + condition JoinCondition + } + JoinExpressions []JoinExpression +) + +func NewUnConditionedJoinExpression(joinType JoinType, table Expression) JoinExpression { + return joinExpression{ + joinType: joinType, + table: table, + isConditioned: false, + } +} + +func (je joinExpression) Clone() Expression { + return je +} + +func (je joinExpression) Expression() Expression { + return je +} + +func (je joinExpression) IsConditioned() bool { + return je.isConditioned +} + +func (je joinExpression) JoinType() JoinType { + return je.joinType +} + +func (je joinExpression) Table() Expression { + return je.table +} + +func NewConditionedJoinExpression(joinType JoinType, table Expression, condition JoinCondition) ConditionedJoinExpression { + return conditionedJoin{ + joinExpression: joinExpression{ + joinType: joinType, + table: table, + isConditioned: true, + }, + condition: condition, + } +} + +func (je conditionedJoin) Clone() Expression { + return je +} + +func (je conditionedJoin) Expression() Expression { + return je +} + +func (je conditionedJoin) Condition() JoinCondition { + return je.condition +} + +func (je conditionedJoin) IsConditionEmpty() bool { + return je.condition == nil || je.condition.IsEmpty() +} + +func (jes JoinExpressions) Clone() JoinExpressions { + ret := make(JoinExpressions, len(jes)) + for i, jc := range jes { + ret[i] = jc.Clone().(JoinExpression) + } + return ret +} + +type ( + JoinConditionType int + JoinCondition interface { + Type() JoinConditionType + IsEmpty() bool + } + JoinOnCondition interface { + JoinCondition + On() ExpressionList + } + JoinUsingCondition interface { + JoinCondition + Using() ColumnListExpression + } + joinOnCondition struct { + on ExpressionList + } + + joinUsingCondition struct { + using ColumnListExpression + } +) + +// Creates a new ON clause to be used within a join +// ds.Join(I("my_table"), On(I("my_table.fkey").Eq(I("other_table.id"))) +func NewJoinOnCondition(expressions ...Expression) JoinCondition { + return joinOnCondition{on: NewExpressionList(AndType, expressions...)} +} + +func (joc joinOnCondition) Type() JoinConditionType { + return OnJoinCondType +} + +func (joc joinOnCondition) On() ExpressionList { + return joc.on +} + +func (joc joinOnCondition) IsEmpty() bool { + return len(joc.on.Expressions()) == 0 +} + +// Creates a new USING clause to be used within a join +func NewJoinUsingCondition(expressions ...interface{}) JoinCondition { + return joinUsingCondition{using: NewColumnListExpression(expressions...)} +} + +func (juc joinUsingCondition) Type() JoinConditionType { + return UsingJoinCondType +} + +func (juc joinUsingCondition) Using() ColumnListExpression { + return juc.using +} + +func (juc joinUsingCondition) IsEmpty() bool { + return len(juc.using.Columns()) == 0 +} diff --git a/exp/literal.go b/exp/literal.go new file mode 100644 index 00000000..ea58178e --- /dev/null +++ b/exp/literal.go @@ -0,0 +1,62 @@ +package exp + +type ( + literal struct { + literal string + args []interface{} + } +) + +// Creates a new SQL literal with the provided arguments. +// L("a = 1") -> a = 1 +// You can also you placeholders. All placeholders within a Literal are represented by '?' +// L("a = ?", "b") -> a = 'b' +// Literals can also contain placeholders for other expressions +// L("(? AND ?) OR (?)", I("a").Eq(1), I("b").Eq("b"), I("c").In([]string{"a", "b", "c"})) +func NewLiteralExpression(sql string, args ...interface{}) LiteralExpression { + return literal{literal: sql, args: args} +} + +// Returns a literal for the '*' operator +func Star() LiteralExpression { + return NewLiteralExpression("*") +} + +func (l literal) Clone() Expression { + return NewLiteralExpression(l.literal, l.args...) +} + +func (l literal) Literal() string { + return l.literal +} + +func (l literal) Args() []interface{} { + return l.args +} + +func (l literal) Expression() Expression { return l } +func (l literal) As(val interface{}) AliasedExpression { return aliased(l, val) } +func (l literal) Eq(val interface{}) BooleanExpression { return eq(l, val) } +func (l literal) Neq(val interface{}) BooleanExpression { return neq(l, val) } +func (l literal) Gt(val interface{}) BooleanExpression { return gt(l, val) } +func (l literal) Gte(val interface{}) BooleanExpression { return gte(l, val) } +func (l literal) Lt(val interface{}) BooleanExpression { return lt(l, val) } +func (l literal) Lte(val interface{}) BooleanExpression { return lte(l, val) } +func (l literal) Asc() OrderedExpression { return asc(l) } +func (l literal) Desc() OrderedExpression { return desc(l) } +func (l literal) Between(val RangeVal) RangeExpression { return between(l, val) } +func (l literal) NotBetween(val RangeVal) RangeExpression { return notBetween(l, val) } +func (l literal) Like(val interface{}) BooleanExpression { return like(l, val) } +func (l literal) NotLike(val interface{}) BooleanExpression { return notLike(l, val) } +func (l literal) ILike(val interface{}) BooleanExpression { return iLike(l, val) } +func (l literal) NotILike(val interface{}) BooleanExpression { return notILike(l, val) } +func (l literal) In(vals ...interface{}) BooleanExpression { return in(l, vals...) } +func (l literal) NotIn(vals ...interface{}) BooleanExpression { return notIn(l, vals...) } +func (l literal) Is(val interface{}) BooleanExpression { return is(l, val) } +func (l literal) IsNot(val interface{}) BooleanExpression { return isNot(l, val) } +func (l literal) IsNull() BooleanExpression { return is(l, nil) } +func (l literal) IsNotNull() BooleanExpression { return isNot(l, nil) } +func (l literal) IsTrue() BooleanExpression { return is(l, true) } +func (l literal) IsNotTrue() BooleanExpression { return isNot(l, true) } +func (l literal) IsFalse() BooleanExpression { return is(l, false) } +func (l literal) IsNotFalse() BooleanExpression { return isNot(l, false) } diff --git a/exp/lock.go b/exp/lock.go new file mode 100644 index 00000000..e4548a22 --- /dev/null +++ b/exp/lock.go @@ -0,0 +1,41 @@ +package exp + +type ( + LockStrength int + WaitOption int + Lock interface { + Strength() LockStrength + WaitOption() WaitOption + } + lock struct { + strength LockStrength + waitOption WaitOption + } +) + +const ( + ForNolock LockStrength = iota + ForUpdate + ForNoKeyUpdate + ForShare + ForKeyShare + + Wait WaitOption = iota + NoWait + SkipLocked +) + +func NewLock(strength LockStrength, option WaitOption) Lock { + return lock{ + strength: strength, + waitOption: option, + } +} + +func (l lock) Strength() LockStrength { + return l.strength +} + +func (l lock) WaitOption() WaitOption { + return l.waitOption +} diff --git a/exp/order.go b/exp/order.go new file mode 100644 index 00000000..f3e6ea0a --- /dev/null +++ b/exp/order.go @@ -0,0 +1,47 @@ +package exp + +type ( + orderedExpression struct { + sortExpression Expression + direction sortDirection + nullSortType nullSortType + } +) + +// used internally to create a new SORT_ASC OrderedExpression +func asc(exp Expression) OrderedExpression { + return orderedExpression{sortExpression: exp, direction: ascDir, nullSortType: NoNullsSortType} +} + +// used internally to create a new SORT_DESC OrderedExpression +func desc(exp Expression) OrderedExpression { + return orderedExpression{sortExpression: exp, direction: descSortDir, nullSortType: NoNullsSortType} +} + +func (oe orderedExpression) Clone() Expression { + return orderedExpression{sortExpression: oe.sortExpression, direction: oe.direction, nullSortType: oe.nullSortType} +} + +func (oe orderedExpression) Expression() Expression { + return oe +} + +func (oe orderedExpression) SortExpression() Expression { + return oe.sortExpression +} + +func (oe orderedExpression) IsAsc() bool { + return oe.direction == ascDir +} + +func (oe orderedExpression) NullSortType() nullSortType { + return oe.nullSortType +} + +func (oe orderedExpression) NullsFirst() OrderedExpression { + return orderedExpression{sortExpression: oe.sortExpression, direction: oe.direction, nullSortType: NullsFirstSortType} +} + +func (oe orderedExpression) NullsLast() OrderedExpression { + return orderedExpression{sortExpression: oe.sortExpression, direction: oe.direction, nullSortType: NullsLastSortType} +} diff --git a/exp/range.go b/exp/range.go new file mode 100644 index 00000000..c019ae8d --- /dev/null +++ b/exp/range.go @@ -0,0 +1,57 @@ +package exp + +type ( + ranged struct { + lhs Expression + rhs RangeVal + op RangeOperation + } + rangeVal struct { + start interface{} + end interface{} + } +) + +// used internally to create an BETWEEN comparison RangeExpression +func between(lhs Expression, rhs RangeVal) RangeExpression { + return ranged{op: BetweenOp, lhs: lhs, rhs: rhs} +} + +// used internally to create an NOT BETWEEN comparison RangeExpression +func notBetween(lhs Expression, rhs RangeVal) RangeExpression { + return ranged{op: NotBetweenOp, lhs: lhs, rhs: rhs} +} + +func (r ranged) Clone() Expression { + return ranged{op: r.op, lhs: r.lhs.Clone(), rhs: r.rhs} +} + +func (r ranged) Expression() Expression { + return r +} + +func (r ranged) RHS() RangeVal { + return r.rhs +} + +func (r ranged) LHS() Expression { + return r.lhs +} + +func (r ranged) Op() RangeOperation { + return r.op +} + +// Creates a new Range to be used with a Between expression +// exp.C("col").Between(exp.Range(1, 10)) +func NewRangeVal(start, end interface{}) RangeVal { + return rangeVal{start: start, end: end} +} + +func (rv rangeVal) Start() interface{} { + return rv.start +} + +func (rv rangeVal) End() interface{} { + return rv.end +} diff --git a/exp/truncate.go b/exp/truncate.go new file mode 100644 index 00000000..1486a003 --- /dev/null +++ b/exp/truncate.go @@ -0,0 +1,11 @@ +package exp + +// Options to use when generating a TRUNCATE statement +type TruncateOptions struct { + // Set to true to add CASCADE to the TRUNCATE statement + Cascade bool + // Set to true to add RESTRICT to the TRUNCATE statement + Restrict bool + // Set to true to specify IDENTITY options, (e.g. RESTART, CONTINUE) to the TRUNCATE statement + Identity string +} diff --git a/exp/update.go b/exp/update.go new file mode 100644 index 00000000..cbf54b43 --- /dev/null +++ b/exp/update.go @@ -0,0 +1,80 @@ +package exp + +import ( + "reflect" + "sort" + + "github.com/doug-martin/goqu/v7/internal/errors" + "github.com/doug-martin/goqu/v7/internal/tag" + "github.com/doug-martin/goqu/v7/internal/util" +) + +type ( + update struct { + col IdentifierExpression + val interface{} + } +) + +func set(col IdentifierExpression, val interface{}) UpdateExpression { + return update{col: col, val: val} +} + +func NewUpdateExpressions(update interface{}) (updates []UpdateExpression, err error) { + if u, ok := update.(UpdateExpression); ok { + updates = append(updates, u) + return updates, nil + } + updateValue := reflect.Indirect(reflect.ValueOf(update)) + switch updateValue.Kind() { + case reflect.Map: + keys := util.ValueSlice(updateValue.MapKeys()) + sort.Sort(keys) + for _, key := range keys { + updates = append(updates, ParseIdentifier(key.String()).Set(updateValue.MapIndex(key).Interface())) + } + case reflect.Struct: + updates = getUpdateExpressionsStruct(updateValue) + default: + return nil, errors.New("unsupported update interface type %+v", updateValue.Type()) + } + return updates, nil +} + +func getUpdateExpressionsStruct(value reflect.Value) (updates []UpdateExpression) { + for i := 0; i < value.NumField(); i++ { + v := value.Field(i) + t := value.Type().Field(i) + if !t.Anonymous { + if canUpdateField(&t) { + updates = append(updates, ParseIdentifier(t.Tag.Get("db")).Set(v.Interface())) + } + } else { + updates = append(updates, getUpdateExpressionsStruct(reflect.Indirect(reflect.ValueOf(v.Interface())))...) + } + } + + return updates +} + +func canUpdateField(field *reflect.StructField) bool { + goquTag := tag.New("goqu", field.Tag) + dbTag := tag.New("db", field.Tag) + return !goquTag.Contains("skipupdate") && !(dbTag.IsEmpty() || dbTag.Equals("-")) +} + +func (u update) Expression() Expression { + return u +} + +func (u update) Clone() Expression { + return update{col: u.col.Clone().(IdentifierExpression), val: u.val} +} + +func (u update) Col() IdentifierExpression { + return u.col +} + +func (u update) Val() interface{} { + return u.val +} diff --git a/expressions.go b/expressions.go index 46b0ff45..ca75f0dc 100644 --- a/expressions.go +++ b/expressions.go @@ -1,1535 +1,206 @@ package goqu import ( - "fmt" - "reflect" - "regexp" - "sort" - "strings" + "github.com/doug-martin/goqu/v7/exp" ) type ( - //Alternative to writing map[string]interface{}. Can be used for Inserts, Updates or Deletes - Record map[string]interface{} - //Parent of all expression types - Expression interface { - Clone() Expression - Expression() Expression - } - //An Expression that generates its own sql (e.g Dataset) - SqlExpression interface { - Expression - ToSql() (string, []interface{}, error) - } -) - -type ( - ExpressionListType int - //A list of expressions that should be joined together - // And(I("a").Eq(10), I("b").Eq(11)) //(("a" = 10) AND ("b" = 11)) - // Or(I("a").Eq(10), I("b").Eq(11)) //(("a" = 10) OR ("b" = 11)) - ExpressionList interface { - Expression - //Returns type (e.g. OR, AND) - Type() ExpressionListType - //Slice of expressions that should be joined togehter - Expressions() []Expression - //Returns a new expression list with the given expressions appended to the current Expressions list - Append(...Expression) ExpressionList - } - expressionList struct { - operator ExpressionListType - expressions []Expression - } + Expression = exp.Expression + Ex = exp.Ex + ExOr = exp.ExOr + Op = exp.Op + Record = exp.Record + // Options to use when generating a TRUNCATE statement + TruncateOptions = exp.TruncateOptions ) const ( - AND_TYPE ExpressionListType = iota - OR_TYPE + Wait = exp.Wait + NoWait = exp.NoWait + SkipLocked = exp.SkipLocked ) -func getExMapKeys(ex map[string]interface{}) []string { - var keys []string - for key, _ := range ex { - keys = append(keys, key) - } - sort.Strings(keys) - return keys +// Creates a new Casted expression +// Cast(I("a"), "NUMERIC") -> CAST("a" AS NUMERIC) +func Cast(e exp.Expression, t string) exp.CastExpression { + return exp.NewCastExpression(e, t) } -func mapToExpressionList(ex map[string]interface{}, eType ExpressionListType) (ExpressionList, error) { - keys := getExMapKeys(ex) - ret := make([]Expression, len(keys)) - for i, key := range keys { - lhs := I(key) - rhs := ex[key] - var exp Expression - if op, ok := rhs.(Op); ok { - opKeys := getExMapKeys(op) - ors := make([]Expression, len(opKeys)) - for j, opKey := range opKeys { - var ored Expression - switch strings.ToLower(opKey) { - case "eq": - ored = lhs.Eq(op[opKey]) - case "neq": - ored = lhs.Neq(op[opKey]) - case "is": - ored = lhs.Is(op[opKey]) - case "isnot": - ored = lhs.IsNot(op[opKey]) - case "gt": - ored = lhs.Gt(op[opKey]) - case "gte": - ored = lhs.Gte(op[opKey]) - case "lt": - ored = lhs.Lt(op[opKey]) - case "lte": - ored = lhs.Lte(op[opKey]) - case "in": - ored = lhs.In(op[opKey]) - case "notin": - ored = lhs.NotIn(op[opKey]) - case "like": - ored = lhs.Like(op[opKey]) - case "notlike": - ored = lhs.NotLike(op[opKey]) - case "ilike": - ored = lhs.ILike(op[opKey]) - case "notilike": - ored = lhs.NotILike(op[opKey]) - case "between": - rangeVal, ok := op[opKey].(RangeVal) - if ok { - ored = lhs.Between(rangeVal) - } - case "notbetween": - rangeVal, ok := op[opKey].(RangeVal) - if ok { - ored = lhs.NotBetween(rangeVal) - } - default: - return nil, NewGoquError("Unsupported expression type %s", op) - } - ors[j] = ored - } - exp = Or(ors...) - } else { - exp = lhs.Eq(rhs) - } - ret[i] = exp - } - if eType == OR_TYPE { - return Or(ret...), nil - } - return And(ret...), nil +// Creates a conflict struct to be passed to InsertConflict to ignore constraint errors +// InsertConflict(DoNothing(),...) -> INSERT INTO ... ON CONFLICT DO NOTHING +func DoNothing() exp.ConflictExpression { + return exp.NewDoNothingConflictExpression() +} + +// Creates a ConflictUpdate struct to be passed to InsertConflict +// Represents a ON CONFLICT DO UPDATE portion of an INSERT statement (ON DUPLICATE KEY UPDATE for mysql) +// +// InsertConflict(DoUpdate("target_column", update),...) -> +// INSERT INTO ... ON CONFLICT DO UPDATE SET a=b +// InsertConflict(DoUpdate("target_column", update).Where(Ex{"a": 1},...) -> +// INSERT INTO ... ON CONFLICT DO UPDATE SET a=b WHERE a=1 +func DoUpdate(target string, update interface{}) exp.ConflictUpdateExpression { + return exp.NewDoUpdateConflictExpression(target, update) } // A list of expressions that should be ORed together // Or(I("a").Eq(10), I("b").Eq(11)) //(("a" = 10) OR ("b" = 11)) -func Or(expressions ...Expression) expressionList { - return expressionList{operator: OR_TYPE, expressions: expressions} +func Or(expressions ...exp.Expression) exp.ExpressionList { + return exp.NewExpressionList(exp.OrType, expressions...) } // A list of expressions that should be ANDed together // And(I("a").Eq(10), I("b").Eq(11)) //(("a" = 10) AND ("b" = 11)) -func And(expressions ...Expression) expressionList { - return expressionList{operator: AND_TYPE, expressions: expressions} -} - -func (me expressionList) Clone() Expression { - newExps := make([]Expression, len(me.expressions)) - for i, exp := range me.expressions { - newExps[i] = exp.Clone() - } - return expressionList{operator: me.operator, expressions: newExps} -} - -func (me expressionList) Expression() Expression { - return me -} - -func (me expressionList) Type() ExpressionListType { - return me.operator -} - -func (me expressionList) Expressions() []Expression { - return me.expressions -} - -func (me expressionList) Append(expressions ...Expression) ExpressionList { - ret := new(expressionList) - ret.operator = me.operator - exps := make([]Expression, len(me.expressions)) - copy(exps, me.expressions) - for _, exp := range expressions { - exps = append(exps, exp) - } - ret.expressions = exps - return ret +func And(expressions ...exp.Expression) exp.ExpressionList { + return exp.NewExpressionList(exp.AndType, expressions...) } -type ( - //A map of expressions to be ANDed together where the keys are string that will be used as Identifiers and values will be used in a boolean operation. - //The Ex map can be used in tandem with Op map to create more complex expression such as LIKE, GT, LT... See examples. - Ex map[string]interface{} - //A map of expressions to be ORed together where the keys are string that will be used as Identifiers and values will be used in a boolean operation. - //The Ex map can be used in tandem with Op map to create more complex expression such as LIKE, GT, LT... See examples. - ExOr map[string]interface{} - //Used in tandem with the Ex map to create complex comparisons such as LIKE, GT, LT... See examples - Op map[string]interface{} -) - -func (me Ex) Expression() Expression { - return me -} - -func (me Ex) Clone() Expression { - ret := Ex{} - for key, val := range me { - ret[key] = val - } - return ret -} - -func (me Ex) ToExpressions() (ExpressionList, error) { - return mapToExpressionList(me, AND_TYPE) -} - -func (me ExOr) Expression() Expression { - return me -} - -func (me ExOr) Clone() Expression { - ret := Ex{} - for key, val := range me { - ret[key] = val - } - return ret +// Creates a new SQLFunctionExpression with the given name and arguments +func Func(name string, args ...interface{}) exp.SQLFunctionExpression { + return exp.NewSQLFunctionExpression(name, args...) } -func (me ExOr) ToExpressions() (ExpressionList, error) { - return mapToExpressionList(me, OR_TYPE) -} - -type ( - //A list of columns. Typically used internally by Select, Order, From - ColumnList interface { - Expression - //Returns the list of columns - Columns() []Expression - //Returns a new ColumnList with the columns appended. - Append(...Expression) ColumnList - } - columnList struct { - columns []Expression - } -) - -func emptyCols() ColumnList { - return columnList{} -} - -func cols(vals ...interface{}) ColumnList { - var cols []Expression - for _, val := range vals { - switch val.(type) { - case string: - cols = append(cols, I(val.(string))) - case Expression: - cols = append(cols, val.(Expression)) - default: - _, valKind, _ := getTypeInfo(val, reflect.Indirect(reflect.ValueOf(val))) - - if valKind == reflect.Struct { - cm, err := getColumnMap(val) - if err != nil { - panic(err.Error()) - } - var structCols []string - for key, col := range cm { - if !col.Transient { - structCols = append(structCols, key) - } - } - sort.Strings(structCols) - for _, col := range structCols { - cols = append(cols, I(col)) - } - } else { - panic(fmt.Sprintf("Cannot created expression from %+v", val)) - } - - } - } - return columnList{columns: cols} -} - -func orderList(vals ...OrderedExpression) ColumnList { - exps := make([]Expression, len(vals)) - for i, col := range vals { - exps[i] = col.Expression() - } - return columnList{columns: exps} -} - -func (me columnList) Clone() Expression { - newExps := make([]Expression, len(me.columns)) - for i, exp := range me.columns { - newExps[i] = exp.Clone() - } - return columnList{columns: newExps} -} - -func (me columnList) Expression() Expression { - return me -} - -func (me columnList) Columns() []Expression { - return me.columns -} - -func (me columnList) Append(cols ...Expression) ColumnList { - ret := new(columnList) - exps := append(ret.columns, me.columns...) - for _, exp := range cols { - exps = append(exps, exp) - } - ret.columns = exps - return ret -} - -type ( - LockStrength int - WaitOption int - Lock struct { - Strength LockStrength - WaitOption WaitOption - } -) - -const ( - FOR_NOLOCK LockStrength = iota - FOR_UPDATE - FOR_NO_KEY_UPDATE - FOR_SHARE - FOR_KEY_SHARE - - WAIT WaitOption = iota - NOWAIT - SKIP_LOCKED -) - -type ( - JoinType int - JoinCondition int - //Parent type for join expressions - joinExpression interface { - Expression - JoinCondition() JoinCondition - } - //Container for all joins within a dataset - JoiningClause struct { - //The JoinType - JoinType JoinType - //If this is a conditioned join (e.g. NATURAL, or INNER) - IsConditioned bool - //The table expressions (e.g. LEFT JOIN "my_table", ON (....)) - Table Expression - //The condition to join (e.g. USING("a", "b"), ON("my_table"."fkey" = "other_table"."id") - Condition joinExpression - } - JoiningClauses []JoiningClause - joinClause struct { - joinCondition JoinCondition - } -) - -const ( - INNER_JOIN JoinType = iota - FULL_OUTER_JOIN - RIGHT_OUTER_JOIN - LEFT_OUTER_JOIN - FULL_JOIN - RIGHT_JOIN - LEFT_JOIN - NATURAL_JOIN - NATURAL_LEFT_JOIN - NATURAL_RIGHT_JOIN - NATURAL_FULL_JOIN - CROSS_JOIN - - USING_COND JoinCondition = iota - ON_COND -) - -func (me JoiningClause) Clone() JoiningClause { - return JoiningClause{JoinType: me.JoinType, IsConditioned: me.IsConditioned, Table: me.Table.Clone(), Condition: me.Condition.Clone().(joinExpression)} -} - -func (me JoiningClauses) Clone() JoiningClauses { - ret := make(JoiningClauses, len(me)) - for i, jc := range me { - ret[i] = jc.Clone() - } - return ret -} - -func (me joinClause) Clone() Expression { - return joinClause{me.joinCondition} -} - -func (me joinClause) Expression() Expression { - return me -} - -func (me joinClause) JoinCondition() JoinCondition { - return me.joinCondition -} - -type ( - //A join expression that uses an ON clause - JoinOnExpression interface { - joinExpression - On() ExpressionList - } - joinOnClause struct { - joinClause - on ExpressionList - } -) - -//Creates a new ON clause to be used within a join -// ds.Join(I("my_table"), On(I("my_table.fkey").Eq(I("other_table.id"))) -func On(expressions ...Expression) joinExpression { - return joinOnClause{joinClause{ON_COND}, And(expressions...)} -} - -func (me joinOnClause) Clone() Expression { - return joinOnClause{me.joinClause.Clone().(joinClause), me.on.Clone().(ExpressionList)} -} - -func (me joinOnClause) Expression() Expression { - return me -} - -func (me joinOnClause) On() ExpressionList { - return me.on -} - -type ( - JoinUsingExpression interface { - joinExpression - Using() ColumnList - } - //A join expression that uses an USING clause - joinUsingClause struct { - joinClause - using ColumnList - } -) - -//Creates a new USING clause to be used within a join -func Using(expressions ...interface{}) joinExpression { - return joinUsingClause{joinClause{USING_COND}, cols(expressions...)} -} - -func (me joinUsingClause) Clone() Expression { - return joinUsingClause{me.joinClause.Clone().(joinClause), me.using.Clone().(ColumnList)} -} - -func (me joinUsingClause) Expression() Expression { - return me -} - -func (me joinUsingClause) Using() ColumnList { - return me.using -} - -type ( - //Interface that an expression should implement if it can be aliased. - AliasMethods interface { - //Returns an AliasedExpression - // I("col").As("other_col") //"col" AS "other_col" - // I("col").As(I("other_col")) //"col" AS "other_col" - As(interface{}) AliasedExpression - } - //Interface that an expression should implement if it can be compared with other values. - ComparisonMethods interface { - //Creates a Boolean expression comparing equality - // I("col").Eq(1) //("col" = 1) - Eq(interface{}) BooleanExpression - //Creates a Boolean expression comparing in-equality - // I("col").Neq(1) //("col" != 1) - Neq(interface{}) BooleanExpression - //Creates a Boolean expression for greater than comparisons - // I("col").Gt(1) //("col" > 1) - Gt(interface{}) BooleanExpression - //Creates a Boolean expression for greater than or equal to than comparisons - // I("col").Gte(1) //("col" >= 1) - Gte(interface{}) BooleanExpression - //Creates a Boolean expression for less than comparisons - // I("col").Lt(1) //("col" < 1) - Lt(interface{}) BooleanExpression - //Creates a Boolean expression for less than or equal to comparisons - // I("col").Lte(1) //("col" <= 1) - Lte(interface{}) BooleanExpression - } - RangeMethods interface { - //Creates a Range expression for between comparisons - // I("col").Between(RangeVal{Start:1, End:10}) //("col" BETWEEN 1 AND 10) - Between(RangeVal) RangeExpression - //Creates a Range expression for between comparisons - // I("col").NotBetween(RangeVal{Start:1, End:10}) //("col" NOT BETWEEN 1 AND 10) - NotBetween(RangeVal) RangeExpression - } - //Interface that an expression should implement if it can be used in an IN expression - InMethods interface { - //Creates a Boolean expression for IN clauses - // I("col").In([]string{"a", "b", "c"}) //("col" IN ('a', 'b', 'c')) - In(...interface{}) BooleanExpression - //Creates a Boolean expression for NOT IN clauses - // I("col").NotIn([]string{"a", "b", "c"}) //("col" NOT IN ('a', 'b', 'c')) - NotIn(...interface{}) BooleanExpression - } - //Interface that an expression should implement if it can be ORDERED. - OrderedMethods interface { - //Creates an Ordered Expression for sql ASC order - // ds.Order(I("a").Asc()) //ORDER BY "a" ASC - Asc() OrderedExpression - //Creates an Ordered Expression for sql DESC order - // ds.Order(I("a").Desc()) //ORDER BY "a" DESC - Desc() OrderedExpression - } - //Interface that an expression should implement if it can be used in string operations (e.g. LIKE, NOT LIKE...). - StringMethods interface { - //Creates an Boolean expression for LIKE clauses - // ds.Where(I("a").Like("a%")) //("a" LIKE 'a%') - Like(interface{}) BooleanExpression - //Creates an Boolean expression for NOT LIKE clauses - // ds.Where(I("a").NotLike("a%")) //("a" NOT LIKE 'a%') - NotLike(interface{}) BooleanExpression - //Creates an Boolean expression for case insensitive LIKE clauses - // ds.Where(I("a").ILike("a%")) //("a" ILIKE 'a%') - ILike(interface{}) BooleanExpression - //Creates an Boolean expression for case insensitive NOT LIKE clauses - // ds.Where(I("a").NotILike("a%")) //("a" NOT ILIKE 'a%') - NotILike(interface{}) BooleanExpression - } - //Interface that an expression should implement if it can be used in simple boolean operations (e.g IS, IS NOT). - BooleanMethods interface { - //Creates an Boolean expression IS clauses - // ds.Where(I("a").Is(nil)) //("a" IS NULL) - // ds.Where(I("a").Is(true)) //("a" IS TRUE) - // ds.Where(I("a").Is(false)) //("a" IS FALSE) - Is(interface{}) BooleanExpression - //Creates an Boolean expression IS NOT clauses - // ds.Where(I("a").IsNot(nil)) //("a" IS NOT NULL) - // ds.Where(I("a").IsNot(true)) //("a" IS NOT TRUE) - // ds.Where(I("a").IsNot(false)) //("a" IS NOT FALSE) - IsNot(interface{}) BooleanExpression - //Shortcut for Is(nil) - IsNull() BooleanExpression - //Shortcut for IsNot(nil) - IsNotNull() BooleanExpression - //Shortcut for Is(true) - IsTrue() BooleanExpression - //Shortcut for IsNot(true) - IsNotTrue() BooleanExpression - //Shortcut for Is(false) - IsFalse() BooleanExpression - //Shortcut for IsNot(false) - IsNotFalse() BooleanExpression - } - //Interface that an expression should implement if it can be casted to another SQL type . - CastMethods interface { - //Casts an expression to the specified type - // I("a").Cast("numeric")//CAST("a" AS numeric) - Cast(val string) CastExpression - } - updateMethods interface { - //Used internally by update sql - Set(interface{}) UpdateExpression - } - //Interface that an expression should implement if it can be used in a DISTINCT epxression. - DistinctMethods interface { - //Creates a DISTINCT clause - // I("a").Distinct() //DISTINCT("a") - Distinct() SqlFunctionExpression - } -) - -type ( - //An Identifier that can contain schema, table and column identifiers - IdentifierExpression interface { - Expression - AliasMethods - ComparisonMethods - RangeMethods - InMethods - StringMethods - BooleanMethods - OrderedMethods - updateMethods - DistinctMethods - CastMethods - //Returns a new IdentifierExpression with the specified schema - Schema(string) IdentifierExpression - //Returns the current schema - GetSchema() string - //Returns a new IdentifierExpression with the specified table - Table(string) IdentifierExpression - //Returns the current table - GetTable() string - //Returns a new IdentifierExpression with the specified column - Col(interface{}) IdentifierExpression - //Returns the current column - GetCol() interface{} - //Returns a new IdentifierExpression with the column set to * - // I("my_table").All() //"my_table".* - All() IdentifierExpression - } - identifier struct { - schema string - table string - col interface{} - } -) - -//Creates a new Identifier, the generated sql will use adapter specific quoting or '"' by default, this ensures case sensitivity and in certain databases allows for special characters, (e.g. "curr-table", "my table"). -//An Identifier can represent a one or a combination of schema, table, and/or column. -// I("column") -> "column" //A Column -// I("table.column") -> "table"."column" //A Column and table -// I("schema.table.column") //Schema table and column -// I("table.*") //Also handles the * operator -func I(ident string) IdentifierExpression { - parts := strings.Split(ident, ".") - switch len(parts) { - case 2: - return identifier{}.Table(parts[0]).Col(parts[1]) - case 3: - return identifier{}.Schema(parts[0]).Table(parts[1]).Col(parts[2]) - } - return identifier{}.Col(ident) -} - -func (me identifier) clone() identifier { - return identifier{schema: me.schema, table: me.table, col: me.col} -} - -func (me identifier) Clone() Expression { - return me.clone() -} - -//Sets the table on the current identifier -// I("col").Table("table") -> "table"."col" //postgres -// I("col").Table("table") -> `table`.`col` //mysql -// I("col").Table("table") -> `table`.`col` //sqlite3 -func (me identifier) Table(table string) IdentifierExpression { - ret := me.clone() - if s, ok := me.col.(string); ok && s != "" && me.table == "" && me.schema == "" { - ret.schema = s - ret.col = nil - } - ret.table = table - return ret -} - -func (me identifier) GetTable() string { - return me.table -} - -//Sets the table on the current identifier -// I("table").Schema("schema") -> "schema"."table" //postgres -// I("col").Schema("table") -> `schema`.`table` //mysql -// I("col").Schema("table") -> `schema`.`table` //sqlite3 -func (me identifier) Schema(schema string) IdentifierExpression { - ret := me.clone() - ret.schema = schema - return ret -} - -func (me identifier) GetSchema() string { - return me.schema -} - -//Sets the table on the current identifier -// I("table").Col("col") -> "table"."col" //postgres -// I("table").Schema("col") -> `table`.`col` //mysql -// I("table").Schema("col") -> `table`.`col` //sqlite3 -func (me identifier) Col(col interface{}) IdentifierExpression { - ret := me.clone() - if s, ok := me.col.(string); ok && s != "" && me.table == "" { - ret.table = s - } - if col == "*" { - ret.col = Star() - } else { - ret.col = col - } - - return ret -} - -func (me identifier) Expression() Expression { return me } - -//Qualifies the epression with a * literal (e.g. "table".*) -func (me identifier) All() IdentifierExpression { return me.Col("*") } - -//Gets the column identifier -func (me identifier) GetCol() interface{} { return me.col } - -//Used within updates to set a column value -func (me identifier) Set(val interface{}) UpdateExpression { return set(me, val) } - -//Alias an identifer (e.g "my_col" AS "other_col") -func (me identifier) As(val interface{}) AliasedExpression { return aliased(me, val) } - -//Returns a BooleanExpression for equality (e.g "my_col" = 1) -func (me identifier) Eq(val interface{}) BooleanExpression { return eq(me, val) } - -//Returns a BooleanExpression for in equality (e.g "my_col" != 1) -func (me identifier) Neq(val interface{}) BooleanExpression { return neq(me, val) } - -//Returns a BooleanExpression for checking that a identifier is greater than another value (e.g "my_col" > 1) -func (me identifier) Gt(val interface{}) BooleanExpression { return gt(me, val) } - -//Returns a BooleanExpression for checking that a identifier is greater than or equal to another value (e.g "my_col" >= 1) -func (me identifier) Gte(val interface{}) BooleanExpression { return gte(me, val) } - -//Returns a BooleanExpression for checking that a identifier is less than another value (e.g "my_col" < 1) -func (me identifier) Lt(val interface{}) BooleanExpression { return lt(me, val) } - -//Returns a BooleanExpression for checking that a identifier is less than or equal to another value (e.g "my_col" <= 1) -func (me identifier) Lte(val interface{}) BooleanExpression { return lte(me, val) } - -//Returns a BooleanExpression for checking that a identifier is in a list of values or (e.g "my_col" > 1) -func (me identifier) In(vals ...interface{}) BooleanExpression { return in(me, vals...) } -func (me identifier) NotIn(vals ...interface{}) BooleanExpression { return notIn(me, vals...) } -func (me identifier) Like(val interface{}) BooleanExpression { return like(me, val) } -func (me identifier) NotLike(val interface{}) BooleanExpression { return notLike(me, val) } -func (me identifier) ILike(val interface{}) BooleanExpression { return iLike(me, val) } -func (me identifier) NotILike(val interface{}) BooleanExpression { return notILike(me, val) } -func (me identifier) Is(val interface{}) BooleanExpression { return is(me, val) } -func (me identifier) IsNot(val interface{}) BooleanExpression { return isNot(me, val) } -func (me identifier) IsNull() BooleanExpression { return is(me, nil) } -func (me identifier) IsNotNull() BooleanExpression { return isNot(me, nil) } -func (me identifier) IsTrue() BooleanExpression { return is(me, true) } -func (me identifier) IsNotTrue() BooleanExpression { return isNot(me, true) } -func (me identifier) IsFalse() BooleanExpression { return is(me, false) } -func (me identifier) IsNotFalse() BooleanExpression { return isNot(me, false) } -func (me identifier) Asc() OrderedExpression { return asc(me) } -func (me identifier) Desc() OrderedExpression { return desc(me) } -func (me identifier) Distinct() SqlFunctionExpression { return DISTINCT(me) } -func (me identifier) Cast(t string) CastExpression { return Cast(me, t) } - -//Returns a RangeExpression for checking that a identifier is between two values (e.g "my_col" BETWEEN 1 AND 10) -func (me identifier) Between(val RangeVal) RangeExpression { return between(me, val) } - -//Returns a RangeExpression for checking that a identifier is between two values (e.g "my_col" BETWEEN 1 AND 10) -func (me identifier) NotBetween(val RangeVal) RangeExpression { return notBetween(me, val) } - -type ( - //Expression for representing "literal" sql. - // L("col = 1") -> col = 1) - // L("? = ?", I("col"), 1) -> "col" = 1 - LiteralExpression interface { - Expression - AliasMethods - ComparisonMethods - RangeMethods - OrderedMethods - //Returns the literal sql - Literal() string - //Arguments to be replaced within the sql - Args() []interface{} - } - literal struct { - literal string - args []interface{} - } -) - -//Alias for L -func Literal(val string, args ...interface{}) LiteralExpression { - return L(val, args...) -} - -//Creates a new SQL literal with the provided arguments. -// L("a = 1") -> a = 1 -//You can also you placeholders. All placeholders within a Literal are represented by '?' -// L("a = ?", "b") -> a = 'b' -//Literals can also contain placeholders for other expressions -// L("(? AND ?) OR (?)", I("a").Eq(1), I("b").Eq("b"), I("c").In([]string{"a", "b", "c"})) - -func L(val string, args ...interface{}) LiteralExpression { - return literal{literal: val, args: args} -} - -//Returns a literal for DEFAULT sql keyword -func Default() LiteralExpression { - return literal{literal: "DEFAULT"} -} - -//Returns a literal for the '*' operator -func Star() LiteralExpression { - return literal{literal: "*"} -} - -func (me literal) Clone() Expression { - return Literal(me.literal) -} - -func (me literal) Literal() string { - return me.literal -} - -func (me literal) Args() []interface{} { - return me.args -} - -func (me literal) Expression() Expression { return me } -func (me literal) As(val interface{}) AliasedExpression { return aliased(me, val) } -func (me literal) Eq(val interface{}) BooleanExpression { return eq(me, val) } -func (me literal) Neq(val interface{}) BooleanExpression { return neq(me, val) } -func (me literal) Gt(val interface{}) BooleanExpression { return gt(me, val) } -func (me literal) Gte(val interface{}) BooleanExpression { return gte(me, val) } -func (me literal) Lt(val interface{}) BooleanExpression { return lt(me, val) } -func (me literal) Lte(val interface{}) BooleanExpression { return lte(me, val) } -func (me literal) Asc() OrderedExpression { return asc(me) } -func (me literal) Desc() OrderedExpression { return desc(me) } -func (me literal) Between(val RangeVal) RangeExpression { return between(me, val) } -func (me literal) NotBetween(val RangeVal) RangeExpression { return notBetween(me, val) } - -type ( - UpdateExpression interface { - Col() IdentifierExpression - Val() interface{} - } - update struct { - col IdentifierExpression - val interface{} - } -) - -func set(col IdentifierExpression, val interface{}) UpdateExpression { - return update{col: col, val: val} -} - -func (me update) Expression() Expression { - return me -} - -func (me update) Clone() Expression { - return update{col: me.col.Clone().(IdentifierExpression), val: me.val} -} - -func (me update) Col() IdentifierExpression { - return me.col -} - -func (me update) Val() interface{} { - return me.val -} - -type ( - BooleanOperation int - BooleanExpression interface { - Expression - //Returns the operator for the expression - Op() BooleanOperation - //The left hand side of the expression (e.g. I("a") - Lhs() Expression - //The right hand side of the expression could be a primitive value, dataset, or expression - Rhs() interface{} - } - boolean struct { - lhs Expression - rhs interface{} - op BooleanOperation - } -) - -const ( - //= - EQ_OP BooleanOperation = iota - //!= or <> - NEQ_OP - //IS - IS_OP - //IS NOT - IS_NOT_OP - //> - GT_OP - //>= - GTE_OP - //< - LT_OP - //<= - LTE_OP - //IN - IN_OP - //NOT IN - NOT_IN_OP - //LIKE, LIKE BINARY... - LIKE_OP - //NOT LIKE, NOT LIKE BINARY... - NOT_LIKE_OP - //ILIKE, LIKE - I_LIKE_OP - //NOT ILIKE, NOT LIKE - NOT_I_LIKE_OP - //~, REGEXP BINARY - REGEXP_LIKE_OP - //!~, NOT REGEXP BINARY - REGEXP_NOT_LIKE_OP - //~*, REGEXP - REGEXP_I_LIKE_OP - //!~*, NOT REGEXP - REGEXP_NOT_I_LIKE_OP -) - -//used internally for inverting operators -var operator_inversions = map[BooleanOperation]BooleanOperation{ - IS_OP: IS_NOT_OP, - EQ_OP: NEQ_OP, - GT_OP: LTE_OP, - GTE_OP: LT_OP, - LT_OP: GTE_OP, - LTE_OP: GT_OP, - IN_OP: NOT_IN_OP, - LIKE_OP: NOT_LIKE_OP, - I_LIKE_OP: NOT_I_LIKE_OP, - REGEXP_LIKE_OP: REGEXP_NOT_LIKE_OP, - REGEXP_I_LIKE_OP: REGEXP_NOT_I_LIKE_OP, - IS_NOT_OP: IS_OP, - NEQ_OP: EQ_OP, - NOT_IN_OP: IN_OP, - NOT_LIKE_OP: LIKE_OP, - NOT_I_LIKE_OP: I_LIKE_OP, - REGEXP_NOT_LIKE_OP: REGEXP_LIKE_OP, - REGEXP_NOT_I_LIKE_OP: REGEXP_I_LIKE_OP, -} - -func (me boolean) Clone() Expression { - return boolean{op: me.op, lhs: me.lhs.Clone(), rhs: me.rhs} -} - -func (me boolean) Expression() Expression { - return me -} - -func (me boolean) Rhs() interface{} { - return me.rhs -} - -func (me boolean) Lhs() Expression { - return me.lhs -} - -func (me boolean) Op() BooleanOperation { - return me.op -} - -//used internally to create an equality BooleanExpression -func eq(lhs Expression, rhs interface{}) BooleanExpression { - return checkBoolExpType(EQ_OP, lhs, rhs, false) -} - -//used internally to create an in-equality BooleanExpression -func neq(lhs Expression, rhs interface{}) BooleanExpression { - return checkBoolExpType(EQ_OP, lhs, rhs, true) -} - -//used internally to create an gt comparison BooleanExpression -func gt(lhs Expression, rhs interface{}) BooleanExpression { - return boolean{op: GT_OP, lhs: lhs, rhs: rhs} -} - -//used internally to create an gte comparison BooleanExpression -func gte(lhs Expression, rhs interface{}) BooleanExpression { - return boolean{op: GTE_OP, lhs: lhs, rhs: rhs} -} - -//used internally to create an lt comparison BooleanExpression -func lt(lhs Expression, rhs interface{}) BooleanExpression { - return boolean{op: LT_OP, lhs: lhs, rhs: rhs} -} - -//used internally to create an lte comparison BooleanExpression -func lte(lhs Expression, rhs interface{}) BooleanExpression { - return boolean{op: LTE_OP, lhs: lhs, rhs: rhs} -} - -//used internally to create an IN BooleanExpression -func in(lhs Expression, vals ...interface{}) BooleanExpression { - if len(vals) == 1 && reflect.Indirect(reflect.ValueOf(vals[0])).Kind() == reflect.Slice { - return boolean{op: IN_OP, lhs: lhs, rhs: vals[0]} - } - return boolean{op: IN_OP, lhs: lhs, rhs: vals} -} - -//used internally to create a NOT IN BooleanExpression -func notIn(lhs Expression, vals ...interface{}) BooleanExpression { - if len(vals) == 1 && reflect.Indirect(reflect.ValueOf(vals[0])).Kind() == reflect.Slice { - return boolean{op: NOT_IN_OP, lhs: lhs, rhs: vals[0]} - } - return boolean{op: NOT_IN_OP, lhs: lhs, rhs: vals} -} - -//used internally to create an IS BooleanExpression -func is(lhs Expression, val interface{}) BooleanExpression { - return checkBoolExpType(IS_OP, lhs, val, false) -} - -//used internally to create an IS NOT BooleanExpression -func isNot(lhs Expression, val interface{}) BooleanExpression { - return checkBoolExpType(IS_OP, lhs, val, true) -} - -//used internally to create a LIKE BooleanExpression -func like(lhs Expression, val interface{}) BooleanExpression { - return checkLikeExp(LIKE_OP, lhs, val, false) -} - -//used internally to create an ILIKE BooleanExpression -func iLike(lhs Expression, val interface{}) BooleanExpression { - return checkLikeExp(I_LIKE_OP, lhs, val, false) -} - -//used internally to create a NOT LIKE BooleanExpression -func notLike(lhs Expression, val interface{}) BooleanExpression { - return checkLikeExp(LIKE_OP, lhs, val, true) -} - -//used internally to create a NOT ILIKE BooleanExpression -func notILike(lhs Expression, val interface{}) BooleanExpression { - return checkLikeExp(I_LIKE_OP, lhs, val, true) -} - -//checks an like rhs to create the proper like expression for strings or regexps -func checkLikeExp(op BooleanOperation, lhs Expression, val interface{}, invert bool) BooleanExpression { - rhs := val - switch val.(type) { - case *regexp.Regexp: - if op == LIKE_OP { - op = REGEXP_LIKE_OP - } else if op == I_LIKE_OP { - op = REGEXP_I_LIKE_OP - } - rhs = val.(*regexp.Regexp).String() - } - if invert { - op = operator_inversions[op] - } - return boolean{op: op, lhs: lhs, rhs: rhs} -} - -//checks a boolean operation normalizing the operation based on the RHS (e.g. "a" = true vs "a" IS TRUE -func checkBoolExpType(op BooleanOperation, lhs Expression, rhs interface{}, invert bool) BooleanExpression { - if rhs == nil { - op = IS_OP - } else { - switch reflect.Indirect(reflect.ValueOf(rhs)).Kind() { - case reflect.Bool: - op = IS_OP - case reflect.Slice: - //if its a slice of bytes dont treat as an IN - if _, ok := rhs.([]byte); !ok { - op = IN_OP - } - case reflect.Struct: - switch rhs.(type) { - case SqlExpression: - op = IN_OP - case *regexp.Regexp: - return checkLikeExp(LIKE_OP, lhs, rhs, invert) - } - } - } - if invert { - op = operator_inversions[op] - } - return boolean{op: op, lhs: lhs, rhs: rhs} -} - -type ( - RangeOperation int - RangeExpression interface { - Expression - //Returns the operator for the expression - Op() RangeOperation - //The left hand side of the expression (e.g. I("a") - Lhs() Expression - //The right hand side of the expression could be a primitive value, dataset, or expression - Rhs() RangeVal - } - ranged struct { - lhs Expression - rhs RangeVal - op RangeOperation - } - RangeVal struct { - Start interface{} - End interface{} - } -) - -const ( - //BETWEEN - BETWEEN_OP RangeOperation = iota - //NOT BETWEEN - NBETWEEN_OP -) - -func (me ranged) Clone() Expression { - return ranged{op: me.op, lhs: me.lhs.Clone(), rhs: me.rhs} -} - -func (me ranged) Expression() Expression { - return me -} - -func (me ranged) Rhs() RangeVal { - return me.rhs -} - -func (me ranged) Lhs() Expression { - return me.lhs -} - -func (me ranged) Op() RangeOperation { - return me.op -} - -//used internally to create an BETWEEN comparison RangeExpression -func between(lhs Expression, rhs RangeVal) RangeExpression { - return ranged{op: BETWEEN_OP, lhs: lhs, rhs: rhs} -} - -//used internally to create an NOT BETWEEN comparison RangeExpression -func notBetween(lhs Expression, rhs RangeVal) RangeExpression { - return ranged{op: NBETWEEN_OP, lhs: lhs, rhs: rhs} -} - -type ( - //Expression for Aliased expressions - // I("a").As("b") -> "a" AS "b" - // SUM("a").As(I("a_sum")) -> SUM("a") AS "a_sum" - AliasedExpression interface { - Expression - //Returns the Epxression being aliased - Aliased() Expression - //Returns the alias value as an identiier expression - GetAs() IdentifierExpression - } - aliasExpression struct { - aliased Expression - alias IdentifierExpression - } -) - -//used internally by other expressions to create a new aliased expression -func aliased(exp Expression, alias interface{}) AliasedExpression { - switch v := alias.(type) { - case string: - return aliasExpression{aliased: exp, alias: I(v)} - case IdentifierExpression: - return aliasExpression{aliased: exp, alias: v} - default: - panic(fmt.Sprintf("Cannot create alias from %+v", v)) - } -} - -func (me aliasExpression) Clone() Expression { - return aliasExpression{aliased: me.aliased, alias: me.alias.Clone().(IdentifierExpression)} -} - -func (me aliasExpression) Expression() Expression { - return me -} - -func (me aliasExpression) Aliased() Expression { - return me.aliased -} - -func (me aliasExpression) GetAs() IdentifierExpression { - return me.alias -} - -type ( - null_sort_type int - sort_direction int - //An expression for specifying sort order and options - OrderedExpression interface { - Expression - //The expression being sorted - SortExpression() Expression - //Sort direction (e.g. ASC, DESC) - Direction() sort_direction - //If the adapter supports it null sort type (e.g. NULLS FIRST, NULLS LAST) - NullSortType() null_sort_type - //Returns a new OrderedExpression with NullSortType set to NULLS_FIRST - NullsFirst() OrderedExpression - //Returns a new OrderedExpression with NullSortType set to NULLS_LAST - NullsLast() OrderedExpression - } - orderedExpression struct { - sortExpression Expression - direction sort_direction - nullSortType null_sort_type - } -) - -const ( - //Default null sort type with no null sort order - NO_NULLS null_sort_type = iota - //NULLS FIRST - NULLS_FIRST - //NULLS LAST - NULLS_LAST - - //ASC - SORT_ASC sort_direction = iota - //DESC - SORT_DESC -) - -//used internally to create a new SORT_ASC OrderedExpression -func asc(exp Expression) OrderedExpression { - return orderedExpression{sortExpression: exp, direction: SORT_ASC, nullSortType: NO_NULLS} -} - -//used internally to create a new SORT_DESC OrderedExpression -func desc(exp Expression) OrderedExpression { - return orderedExpression{sortExpression: exp, direction: SORT_DESC, nullSortType: NO_NULLS} -} - -func (me orderedExpression) Clone() Expression { - return orderedExpression{sortExpression: me.sortExpression, direction: me.direction, nullSortType: me.nullSortType} -} - -func (me orderedExpression) Expression() Expression { - return me -} - -func (me orderedExpression) SortExpression() Expression { - return me.sortExpression -} - -func (me orderedExpression) Direction() sort_direction { - return me.direction -} - -func (me orderedExpression) NullSortType() null_sort_type { - return me.nullSortType -} - -func (me orderedExpression) NullsFirst() OrderedExpression { - return orderedExpression{sortExpression: me.sortExpression, direction: me.direction, nullSortType: NULLS_FIRST} -} - -func (me orderedExpression) NullsLast() OrderedExpression { - return orderedExpression{sortExpression: me.sortExpression, direction: me.direction, nullSortType: NULLS_LAST} -} - -type ( - //Expression for representing a SqlFunction(e.g. COUNT, SUM, MIN, MAX...) - SqlFunctionExpression interface { - Expression - AliasMethods - RangeMethods - ComparisonMethods - //The function name - Name() string - //Arguments to be passed to the function - Args() []interface{} - } - sqlFunctionExpression struct { - name string - args []interface{} - } -) - -//Creates a new SqlFunctionExpression with the given name and arguments -func Func(name string, args ...interface{}) SqlFunctionExpression { - return sqlFunctionExpression{name: name, args: args} -} - -//used internally to normalize the column name if passed in as a string it should be turned into an identifier -func colFunc(name string, col interface{}) SqlFunctionExpression { +// used internally to normalize the column name if passed in as a string it should be turned into an identifier +func newIdentifierFunc(name string, col interface{}) exp.SQLFunctionExpression { if s, ok := col.(string); ok { col = I(s) } return Func(name, col) } -//Creates a new DISTINCT sql function +// Creates a new DISTINCT sql function // DISTINCT("a") -> DISTINCT("a") // DISTINCT(I("a")) -> DISTINCT("a") -func DISTINCT(col interface{}) SqlFunctionExpression { return colFunc("DISTINCT", col) } +func DISTINCT(col interface{}) exp.SQLFunctionExpression { return newIdentifierFunc("DISTINCT", col) } -//Creates a new COUNT sql function +// Creates a new COUNT sql function // COUNT("a") -> COUNT("a") // COUNT("*") -> COUNT("*") // COUNT(I("a")) -> COUNT("a") -func COUNT(col interface{}) SqlFunctionExpression { return colFunc("COUNT", col) } +func COUNT(col interface{}) exp.SQLFunctionExpression { return newIdentifierFunc("COUNT", col) } -//Creates a new MIN sql function +// Creates a new MIN sql function // MIN("a") -> MIN("a") // MIN(I("a")) -> MIN("a") -func MIN(col interface{}) SqlFunctionExpression { return colFunc("MIN", col) } +func MIN(col interface{}) exp.SQLFunctionExpression { return newIdentifierFunc("MIN", col) } -//Creates a new MAX sql function +// Creates a new MAX sql function // MAX("a") -> MAX("a") // MAX(I("a")) -> MAX("a") -func MAX(col interface{}) SqlFunctionExpression { return colFunc("MAX", col) } +func MAX(col interface{}) exp.SQLFunctionExpression { return newIdentifierFunc("MAX", col) } -//Creates a new AVG sql function +// Creates a new AVG sql function // AVG("a") -> AVG("a") // AVG(I("a")) -> AVG("a") -func AVG(col interface{}) SqlFunctionExpression { return colFunc("AVG", col) } +func AVG(col interface{}) exp.SQLFunctionExpression { return newIdentifierFunc("AVG", col) } -//Creates a new FIRST sql function +// Creates a new FIRST sql function // FIRST("a") -> FIRST("a") // FIRST(I("a")) -> FIRST("a") -func FIRST(col interface{}) SqlFunctionExpression { return colFunc("FIRST", col) } +func FIRST(col interface{}) exp.SQLFunctionExpression { return newIdentifierFunc("FIRST", col) } -//Creates a new LAST sql function +// Creates a new LAST sql function // LAST("a") -> LAST("a") // LAST(I("a")) -> LAST("a") -func LAST(col interface{}) SqlFunctionExpression { return colFunc("LAST", col) } +func LAST(col interface{}) exp.SQLFunctionExpression { return newIdentifierFunc("LAST", col) } -//Creates a new SUM sql function +// Creates a new SUM sql function // SUM("a") -> SUM("a") // SUM(I("a")) -> SUM("a") -func SUM(col interface{}) SqlFunctionExpression { return colFunc("SUM", col) } +func SUM(col interface{}) exp.SQLFunctionExpression { return newIdentifierFunc("SUM", col) } -//Creates a new COALESCE sql function +// Creates a new COALESCE sql function // COALESCE(I("a"), "a") -> COALESCE("a", 'a') // COALESCE(I("a"), I("b"), nil) -> COALESCE("a", "b", NULL) -func COALESCE(vals ...interface{}) SqlFunctionExpression { - return Func("COALESCE", vals...) -} - -func (me sqlFunctionExpression) Clone() Expression { - return sqlFunctionExpression{name: me.name, args: me.args} -} - -func (me sqlFunctionExpression) Expression() Expression { return me } -func (me sqlFunctionExpression) Args() []interface{} { return me.args } -func (me sqlFunctionExpression) Name() string { return me.name } -func (me sqlFunctionExpression) As(val interface{}) AliasedExpression { return aliased(me, val) } -func (me sqlFunctionExpression) Eq(val interface{}) BooleanExpression { return eq(me, val) } -func (me sqlFunctionExpression) Neq(val interface{}) BooleanExpression { return neq(me, val) } -func (me sqlFunctionExpression) Gt(val interface{}) BooleanExpression { return gt(me, val) } -func (me sqlFunctionExpression) Gte(val interface{}) BooleanExpression { return gte(me, val) } -func (me sqlFunctionExpression) Lt(val interface{}) BooleanExpression { return lt(me, val) } -func (me sqlFunctionExpression) Lte(val interface{}) BooleanExpression { return lte(me, val) } -func (me sqlFunctionExpression) Between(val RangeVal) RangeExpression { return between(me, val) } -func (me sqlFunctionExpression) NotBetween(val RangeVal) RangeExpression { return notBetween(me, val) } - -type ( - //An Expression that represents another Expression casted to a SQL type - CastExpression interface { - Expression - AliasMethods - ComparisonMethods - InMethods - StringMethods - BooleanMethods - OrderedMethods - DistinctMethods - RangeMethods - //The exression being casted - Casted() Expression - //The the SQL type to cast the expression to - Type() LiteralExpression - } - cast struct { - casted Expression - t LiteralExpression - } -) - -//Creates a new Casted expression -// Cast(I("a"), "NUMERIC") -> CAST("a" AS NUMERIC) -func Cast(e Expression, t string) CastExpression { - return cast{casted: e, t: Literal(t)} -} - -func (me cast) Casted() Expression { - return me.casted +func COALESCE(vals ...interface{}) exp.SQLFunctionExpression { + return exp.NewSQLFunctionExpression("COALESCE", vals...) } -func (me cast) Type() LiteralExpression { - return me.t -} - -func (me cast) Clone() Expression { - return cast{casted: me.casted.Clone(), t: me.t} -} - -func (me cast) Expression() Expression { return me } -func (me cast) As(val interface{}) AliasedExpression { return aliased(me, val) } -func (me cast) Eq(val interface{}) BooleanExpression { return eq(me, val) } -func (me cast) Neq(val interface{}) BooleanExpression { return neq(me, val) } -func (me cast) Gt(val interface{}) BooleanExpression { return gt(me, val) } -func (me cast) Gte(val interface{}) BooleanExpression { return gte(me, val) } -func (me cast) Lt(val interface{}) BooleanExpression { return lt(me, val) } -func (me cast) Lte(val interface{}) BooleanExpression { return lte(me, val) } -func (me cast) Asc() OrderedExpression { return asc(me) } -func (me cast) Desc() OrderedExpression { return desc(me) } -func (me cast) Like(i interface{}) BooleanExpression { return like(me, i) } -func (me cast) NotLike(i interface{}) BooleanExpression { return notLike(me, i) } -func (me cast) ILike(i interface{}) BooleanExpression { return iLike(me, i) } -func (me cast) NotILike(i interface{}) BooleanExpression { return notILike(me, i) } -func (me cast) In(i ...interface{}) BooleanExpression { return in(me, i...) } -func (me cast) NotIn(i ...interface{}) BooleanExpression { return notIn(me, i...) } -func (me cast) Is(i interface{}) BooleanExpression { return is(me, i) } -func (me cast) IsNot(i interface{}) BooleanExpression { return isNot(me, i) } -func (me cast) IsNull() BooleanExpression { return is(me, nil) } -func (me cast) IsNotNull() BooleanExpression { return isNot(me, nil) } -func (me cast) IsTrue() BooleanExpression { return is(me, true) } -func (me cast) IsNotTrue() BooleanExpression { return isNot(me, true) } -func (me cast) IsFalse() BooleanExpression { return is(me, false) } -func (me cast) IsNotFalse() BooleanExpression { return isNot(me, nil) } -func (me cast) Distinct() SqlFunctionExpression { return DISTINCT(me) } -func (me cast) Between(val RangeVal) RangeExpression { return between(me, val) } -func (me cast) NotBetween(val RangeVal) RangeExpression { return notBetween(me, val) } - -type ( - compoundType int - CompoundExpression interface { - Expression - Type() compoundType - Rhs() SqlExpression - } - compound struct { - t compoundType - rhs SqlExpression - } -) - -const ( - UNION compoundType = iota - UNION_ALL - INTERSECT - INTERSECT_ALL -) - -//Creates a new UNION compound expression between SqlExpression, typically Datasets'. This function is used internally by Dataset when compounded with another Dataset -func Union(rhs SqlExpression) CompoundExpression { - return compound{t: UNION, rhs: rhs} -} - -//Creates a new UNION ALL compound expression between SqlExpression, typically Datasets'. This function is used internally by Dataset when compounded with another Dataset -func UnionAll(rhs SqlExpression) CompoundExpression { - return compound{t: UNION_ALL, rhs: rhs} -} - -//Creates a new INTERSECT compound expression between SqlExpression, typically Datasets'. This function is used internally by Dataset when compounded with another Dataset -func Intersect(rhs SqlExpression) CompoundExpression { - return compound{t: INTERSECT, rhs: rhs} -} - -//Creates a new INTERSECT ALL compound expression between SqlExpression, typically Datasets'. This function is used internally by Dataset when compounded with another Dataset -func IntersectAll(rhs SqlExpression) CompoundExpression { - return compound{t: INTERSECT_ALL, rhs: rhs} -} - -func (me compound) Expression() Expression { return me } - -func (me compound) Clone() Expression { - return compound{t: me.t, rhs: me.rhs.Clone().(SqlExpression)} -} - -func (me compound) Type() compoundType { return me.t } -func (me compound) Rhs() SqlExpression { return me.rhs } - -type ( - CommonTableExpression interface { - Expression - IsRecursive() bool - //Returns the alias name for the extracted expression - Name() LiteralExpression - //Returns the Expression being extracted - SubQuery() SqlExpression - } - commonExpr struct { - recursive bool - name LiteralExpression - subQuery SqlExpression - } -) - -//Creates a new WITH common table expression for a SqlExpression, typically Datasets'. This function is used internally by Dataset when a CTE is added to another Dataset -func With(recursive bool, name string, subQuery SqlExpression) CommonTableExpression { - return commonExpr{recursive: recursive, name: Literal(name), subQuery: subQuery} -} - -func (me commonExpr) Expression() Expression { return me } - -func (me commonExpr) Clone() Expression { - return commonExpr{recursive: me.recursive, name: me.name, subQuery: me.subQuery.Clone().(SqlExpression)} -} - -func (me commonExpr) IsRecursive() bool { return me.recursive } -func (me commonExpr) Name() LiteralExpression { return me.name } -func (me commonExpr) SubQuery() SqlExpression { return me.subQuery } - -type ( - //An Expression that the ON CONFLICT/ON DUPLICATE KEY portion of an INSERT statement - ConflictExpression interface { - Updates() *ConflictUpdate - } - Conflict struct{} - //ConflictUpdate is the struct that represents the UPDATE fragment of an INSERT ... ON CONFLICT/ON DUPLICATE KEY DO UPDATE statement - ConflictUpdate struct { - Target string - Update interface{} - WhereClause ExpressionList - } -) - -//Updates returns the struct that represents the UPDATE fragment of an INSERT ... ON CONFLICT/ON DUPLICATE KEY DO UPDATE statement -//If nil, no update is preformed. -func (c Conflict) Updates() *ConflictUpdate { - return nil -} - -//Returns the target conflict column. Only necessary for Postgres. -//Will return an error for mysql/sqlite. Will also return an error if missing from a postgres ConflictUpdate. -func (c ConflictUpdate) TargetColumn() string { - return c.Target +// Creates a new Identifier, the generated sql will use adapter specific quoting or '"' by default, this ensures case +// sensitivity and in certain databases allows for special characters, (e.g. "curr-table", "my table"). +// +// The identifier will be split by '.' +// +// Table and Column example +// I("table.column") -> "table"."column" //A Column and table +// Schema table and column +// I("schema.table.column") -> "schema"."table"."column" +// Table with star +// I("table.*") -> "table".* +func I(ident string) exp.IdentifierExpression { + return exp.ParseIdentifier(ident) +} + +// Creates a new Column Identifier, the generated sql will use adapter specific quoting or '"' by default, this ensures case +// sensitivity and in certain databases allows for special characters, (e.g. "curr-table", "my table"). +// An Identifier can represent a one or a combination of schema, table, and/or column. +// C("column") -> "column" //A Column +// C("column").Table("table") -> "table"."column" //A Column and table +// C("column").Table("table").Schema("schema") //Schema table and column +// C("*") //Also handles the * operator +func C(col string) exp.IdentifierExpression { + return exp.NewIdentifierExpression("", "", col) +} + +// Creates a new Schema Identifier, the generated sql will use adapter specific quoting or '"' by default, this ensures case +// sensitivity and in certain databases allows for special characters, (e.g. "curr-schema", "my schema"). +// S("schema") -> "schema" //A Schema +// S("schema").Table("table") -> "schema"."table" //A Schema and table +// S("schema").Table("table").Col("col") //Schema table and column +// S("schema").Table("table").Col("*") //Schema table and all columns +func S(schema string) exp.IdentifierExpression { + return exp.NewIdentifierExpression(schema, "", "") +} + +// Creates a new Table Identifier, the generated sql will use adapter specific quoting or '"' by default, this ensures case +// sensitivity and in certain databases allows for special characters, (e.g. "curr-table", "my table"). +// T("table") -> "table" //A Column +// T("table").Col("col") -> "table"."column" //A Column and table +// T("table").Schema("schema").Col("col) -> "schema"."table"."column" //Schema table and column +// T("table").Schema("schema").Col("*") -> "schema"."table".* //Also handles the * operator +func T(table string) exp.IdentifierExpression { + return exp.NewIdentifierExpression("", table, "") +} + +// Creates a new ON clause to be used within a join +// ds.Join(goqu.T("my_table"), goqu.On( +// goqu.I("my_table.fkey").Eq(goqu.I("other_table.id")), +// )) +func On(expressions ...exp.Expression) exp.JoinCondition { + return exp.NewJoinOnCondition(expressions...) +} + +// Creates a new USING clause to be used within a join +// ds.Join(goqu.T("my_table"), goqu.Using("fkey")) +func Using(columns ...interface{}) exp.JoinCondition { + return exp.NewJoinUsingCondition(columns...) +} + +// Creates a new SQL literal with the provided arguments. +// L("a = 1") -> a = 1 +// You can also you placeholders. All placeholders within a Literal are represented by '?' +// L("a = ?", "b") -> a = 'b' +// Literals can also contain placeholders for other expressions +// L("(? AND ?) OR (?)", I("a").Eq(1), I("b").Eq("b"), I("c").In([]string{"a", "b", "c"})) +func L(sql string, args ...interface{}) exp.LiteralExpression { + return exp.NewLiteralExpression(sql, args...) } -//Returns the Updates which represent the ON CONFLICT DO UPDATE portion of an insert statement. If nil, there are no updates. -func (c ConflictUpdate) Updates() *ConflictUpdate { - return &c +// Alias for goqu.L +func Literal(sql string, args ...interface{}) exp.LiteralExpression { + return L(sql, args...) } -//Append to the existing Where clause for an ON CONFLICT DO UPDATE ... WHERE ... -// InsertConflict(DoNothing(),...) -> INSERT INTO ... ON CONFLICT DO NOTHING -func (c *ConflictUpdate) Where(expressions ...Expression) *ConflictUpdate { - if c.WhereClause == nil { - c.WhereClause = And(expressions...) - } else { - c.WhereClause = c.WhereClause.Append(expressions...) - } - return c +// Creates a new Range to be used with a Between expression +// exp.C("col").Between(exp.Range(1, 10)) +func Range(start, end interface{}) exp.RangeVal { + return exp.NewRangeVal(start, end) } -//Creates a Conflict struct to be passed to InsertConflict to ignore constraint errors -// InsertConflict(DoNothing(),...) -> INSERT INTO ... ON CONFLICT DO NOTHING -func DoNothing() *Conflict { - return &Conflict{} -} +// Creates a literal * +func Star() exp.LiteralExpression { return exp.Star() } -//Creates a ConflictUpdate struct to be passed to InsertConflict -//Represents a ON CONFLICT DO UPDATE portion of an INSERT statement (ON DUPLICATE KEY UPDATE for mysql) -// InsertConflict(DoUpdate("target_column", update),...) -> INSERT INTO ... ON CONFLICT DO UPDATE SET a=b -// InsertConflict(DoUpdate("target_column", update).Where(Ex{"a": 1},...) -> INSERT INTO ... ON CONFLICT DO UPDATE SET a=b WHERE a=1 -func DoUpdate(target string, update interface{}) *ConflictUpdate { - return &ConflictUpdate{Target: target, Update: update} +// Returns a literal for DEFAULT sql keyword +func Default() exp.LiteralExpression { + return L("DEFAULT") } diff --git a/expressions_example_test.go b/expressions_example_test.go new file mode 100644 index 00000000..3d136b85 --- /dev/null +++ b/expressions_example_test.go @@ -0,0 +1,1650 @@ +package goqu_test + +import ( + "fmt" + "regexp" + + "github.com/doug-martin/goqu/v7" + "github.com/doug-martin/goqu/v7/exp" +) + +func ExampleAVG() { + ds := goqu.From("test").Select(goqu.AVG("col")) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT AVG("col") FROM "test" [] + // SELECT AVG("col") FROM "test" [] +} + +func ExampleAVG_as() { + sql, _, _ := goqu.From("test").Select(goqu.AVG("a").As("a")).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT AVG("a") AS "a" FROM "test" +} + +func ExampleAVG_havingClause() { + ds := goqu. + From("test"). + Select(goqu.AVG("a").As("avg")). + GroupBy("a"). + Having(goqu.AVG("a").Gt(10)) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT AVG("a") AS "avg" FROM "test" GROUP BY "a" HAVING (AVG("a") > 10) [] + // SELECT AVG("a") AS "avg" FROM "test" GROUP BY "a" HAVING (AVG("a") > ?) [10] +} + +func ExampleAnd() { + ds := goqu.From("test").Where( + goqu.And( + goqu.C("col").Gt(10), + goqu.C("col").Lt(20), + ), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE (("col" > 10) AND ("col" < 20)) [] + // SELECT * FROM "test" WHERE (("col" > ?) AND ("col" < ?)) [10 20] +} + +// You can use And with Or to create more complex queries +func ExampleAnd_withOr() { + ds := goqu.From("test").Where( + goqu.And( + goqu.C("col1").IsTrue(), + goqu.Or( + goqu.C("col2").Gt(10), + goqu.C("col2").Lt(20), + ), + ), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // by default expressions are anded together + ds = goqu.From("test").Where( + goqu.C("col1").IsTrue(), + goqu.Or( + goqu.C("col2").Gt(10), + goqu.C("col2").Lt(20), + ), + ) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE (("col1" IS TRUE) AND (("col2" > 10) OR ("col2" < 20))) [] + // SELECT * FROM "test" WHERE (("col1" IS TRUE) AND (("col2" > ?) OR ("col2" < ?))) [10 20] + // SELECT * FROM "test" WHERE (("col1" IS TRUE) AND (("col2" > 10) OR ("col2" < 20))) [] + // SELECT * FROM "test" WHERE (("col1" IS TRUE) AND (("col2" > ?) OR ("col2" < ?))) [10 20] +} + +// You can use ExOr inside of And expression lists. +func ExampleAnd_withExOr() { + // by default expressions are anded together + ds := goqu.From("test").Where( + goqu.C("col1").IsTrue(), + goqu.ExOr{ + "col2": goqu.Op{"gt": 10}, + "col3": goqu.Op{"lt": 20}, + }, + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE (("col1" IS TRUE) AND (("col2" > 10) OR ("col3" < 20))) [] + // SELECT * FROM "test" WHERE (("col1" IS TRUE) AND (("col2" > ?) OR ("col3" < ?))) [10 20] +} + +func ExampleC() { + + sql, args, _ := goqu.From("test"). + Select(goqu.C("*")). + ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test"). + Select(goqu.C("col1")). + ToSQL() + fmt.Println(sql, args) + + ds := goqu.From("test").Where( + goqu.C("col1").Eq(10), + goqu.C("col2").In([]int64{1, 2, 3, 4}), + goqu.C("col3").Like(regexp.MustCompile("^(a|b)")), + goqu.C("col4").IsNull(), + ) + + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" [] + // SELECT "col1" FROM "test" [] + // SELECT * FROM "test" WHERE (("col1" = 10) AND ("col2" IN (1, 2, 3, 4)) AND ("col3" ~ '^(a|b)') AND ("col4" IS NULL)) [] + // SELECT * FROM "test" WHERE (("col1" = ?) AND ("col2" IN (?, ?, ?, ?)) AND ("col3" ~ ?) AND ("col4" IS NULL)) [10 1 2 3 4 ^(a|b)] +} + +func ExampleC_as() { + sql, _, _ := goqu.From("test").Select(goqu.C("a").As("as_a")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Select(goqu.C("a").As(goqu.C("as_a"))).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT "a" AS "as_a" FROM "test" + // SELECT "a" AS "as_a" FROM "test" +} + +func ExampleC_ordering() { + sql, args, _ := goqu.From("test").Order(goqu.C("a").Asc()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Order(goqu.C("a").Asc().NullsFirst()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Order(goqu.C("a").Asc().NullsLast()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Order(goqu.C("a").Desc()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Order(goqu.C("a").Desc().NullsFirst()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Order(goqu.C("a").Desc().NullsLast()).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" ORDER BY "a" ASC [] + // SELECT * FROM "test" ORDER BY "a" ASC NULLS FIRST [] + // SELECT * FROM "test" ORDER BY "a" ASC NULLS LAST [] + // SELECT * FROM "test" ORDER BY "a" DESC [] + // SELECT * FROM "test" ORDER BY "a" DESC NULLS FIRST [] + // SELECT * FROM "test" ORDER BY "a" DESC NULLS LAST [] +} + +func ExampleC_cast() { + sql, _, _ := goqu.From("test"). + Select(goqu.C("json1").Cast("TEXT").As("json_text")). + ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where( + goqu.C("json1").Cast("TEXT").Neq( + goqu.C("json2").Cast("TEXT"), + ), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT CAST("json1" AS TEXT) AS "json_text" FROM "test" + // SELECT * FROM "test" WHERE (CAST("json1" AS TEXT) != CAST("json2" AS TEXT)) +} + +func ExampleC_comparisons() { + // used from an identifier + sql, _, _ := goqu.From("test").Where(goqu.C("a").Eq(10)).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").Neq(10)).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").Gt(10)).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").Gte(10)).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").Lt(10)).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").Lte(10)).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT * FROM "test" WHERE ("a" = 10) + // SELECT * FROM "test" WHERE ("a" != 10) + // SELECT * FROM "test" WHERE ("a" > 10) + // SELECT * FROM "test" WHERE ("a" >= 10) + // SELECT * FROM "test" WHERE ("a" < 10) + // SELECT * FROM "test" WHERE ("a" <= 10) +} + +func ExampleC_inOperators() { + // using identifiers + sql, _, _ := goqu.From("test").Where(goqu.C("a").In("a", "b", "c")).ToSQL() + fmt.Println(sql) + // with a slice + sql, _, _ = goqu.From("test").Where(goqu.C("a").In([]string{"a", "b", "c"})).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").NotIn("a", "b", "c")).ToSQL() + fmt.Println(sql) + // with a slice + sql, _, _ = goqu.From("test").Where(goqu.C("a").NotIn([]string{"a", "b", "c"})).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT * FROM "test" WHERE ("a" IN ('a', 'b', 'c')) + // SELECT * FROM "test" WHERE ("a" IN ('a', 'b', 'c')) + // SELECT * FROM "test" WHERE ("a" NOT IN ('a', 'b', 'c')) + // SELECT * FROM "test" WHERE ("a" NOT IN ('a', 'b', 'c')) +} + +func ExampleC_likeComparisons() { + // using identifiers + sql, _, _ := goqu.From("test").Where(goqu.C("a").Like("%a%")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").Like(regexp.MustCompile("(a|b)"))).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").ILike("%a%")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").ILike(regexp.MustCompile("(a|b)"))).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").NotLike("%a%")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").NotLike(regexp.MustCompile("(a|b)"))).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").NotILike("%a%")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.C("a").NotILike(regexp.MustCompile("(a|b)"))).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT * FROM "test" WHERE ("a" LIKE '%a%') + // SELECT * FROM "test" WHERE ("a" ~ '(a|b)') + // SELECT * FROM "test" WHERE ("a" ILIKE '%a%') + // SELECT * FROM "test" WHERE ("a" ~* '(a|b)') + // SELECT * FROM "test" WHERE ("a" NOT LIKE '%a%') + // SELECT * FROM "test" WHERE ("a" !~ '(a|b)') + // SELECT * FROM "test" WHERE ("a" NOT ILIKE '%a%') + // SELECT * FROM "test" WHERE ("a" !~* '(a|b)') +} + +func ExampleC_isComparisons() { + sql, args, _ := goqu.From("test").Where(goqu.C("a").Is(nil)).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.C("a").Is(true)).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.C("a").Is(false)).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.C("a").IsNull()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.C("a").IsTrue()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.C("a").IsFalse()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.C("a").IsNot(nil)).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.C("a").IsNot(true)).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.C("a").IsNot(false)).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.C("a").IsNotNull()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.C("a").IsNotTrue()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.C("a").IsNotFalse()).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE ("a" IS NULL) [] + // SELECT * FROM "test" WHERE ("a" IS TRUE) [] + // SELECT * FROM "test" WHERE ("a" IS FALSE) [] + // SELECT * FROM "test" WHERE ("a" IS NULL) [] + // SELECT * FROM "test" WHERE ("a" IS TRUE) [] + // SELECT * FROM "test" WHERE ("a" IS FALSE) [] + // SELECT * FROM "test" WHERE ("a" IS NOT NULL) [] + // SELECT * FROM "test" WHERE ("a" IS NOT TRUE) [] + // SELECT * FROM "test" WHERE ("a" IS NOT FALSE) [] + // SELECT * FROM "test" WHERE ("a" IS NOT NULL) [] + // SELECT * FROM "test" WHERE ("a" IS NOT TRUE) [] + // SELECT * FROM "test" WHERE ("a" IS NOT FALSE) [] +} + +func ExampleC_betweenComparisons() { + ds := goqu.From("test").Where( + goqu.C("a").Between(goqu.Range(1, 10)), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where( + goqu.C("a").NotBetween(goqu.Range(1, 10)), + ) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE ("a" BETWEEN 1 AND 10) [] + // SELECT * FROM "test" WHERE ("a" BETWEEN ? AND ?) [1 10] + // SELECT * FROM "test" WHERE ("a" NOT BETWEEN 1 AND 10) [] + // SELECT * FROM "test" WHERE ("a" NOT BETWEEN ? AND ?) [1 10] +} + +func ExampleCOALESCE() { + ds := goqu.From("test").Select( + goqu.COALESCE(goqu.C("a"), "a"), + goqu.COALESCE(goqu.C("a"), goqu.C("b"), nil), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT COALESCE("a", 'a'), COALESCE("a", "b", NULL) FROM "test" [] + // SELECT COALESCE("a", ?), COALESCE("a", "b", NULL) FROM "test" [a] +} + +func ExampleCOALESCE_as() { + sql, _, _ := goqu.From("test").Select(goqu.COALESCE(goqu.C("a"), "a").As("a")).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT COALESCE("a", 'a') AS "a" FROM "test" +} + +func ExampleCOUNT() { + ds := goqu.From("test").Select(goqu.COUNT("*")) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT COUNT(*) FROM "test" [] + // SELECT COUNT(*) FROM "test" [] +} + +func ExampleCOUNT_as() { + sql, _, _ := goqu.From("test").Select(goqu.COUNT("*").As("count")).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT COUNT(*) AS "count" FROM "test" +} + +func ExampleCOUNT_havingClause() { + ds := goqu. + From("test"). + Select(goqu.COUNT("a").As("COUNT")). + GroupBy("a"). + Having(goqu.COUNT("a").Gt(10)) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT COUNT("a") AS "COUNT" FROM "test" GROUP BY "a" HAVING (COUNT("a") > 10) [] + // SELECT COUNT("a") AS "COUNT" FROM "test" GROUP BY "a" HAVING (COUNT("a") > ?) [10] +} + +func ExampleCast() { + sql, _, _ := goqu.From("test"). + Select(goqu.Cast(goqu.C("json1"), "TEXT").As("json_text")). + ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where( + goqu.Cast(goqu.C("json1"), "TEXT").Neq( + goqu.Cast(goqu.C("json2"), "TEXT"), + ), + ).ToSQL() + fmt.Println(sql) + // Output: + // SELECT CAST("json1" AS TEXT) AS "json_text" FROM "test" + // SELECT * FROM "test" WHERE (CAST("json1" AS TEXT) != CAST("json2" AS TEXT)) +} + +func ExampleDISTINCT() { + ds := goqu.From("test").Select(goqu.DISTINCT("col")) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT DISTINCT("col") FROM "test" [] + // SELECT DISTINCT("col") FROM "test" [] +} + +func ExampleDISTINCT_as() { + sql, _, _ := goqu.From("test").Select(goqu.DISTINCT("a").As("distinct_a")).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT DISTINCT("a") AS "distinct_a" FROM "test" +} + +func ExampleDefault() { + ds := goqu.From("items") + + sql, args, _ := ds.ToInsertSQL(goqu.Record{ + "name": goqu.Default(), + "address": goqu.Default(), + }) + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToInsertSQL(goqu.Record{ + "name": goqu.Default(), + "address": goqu.Default(), + }) + fmt.Println(sql, args) + + // Output: + // INSERT INTO "items" ("address", "name") VALUES (DEFAULT, DEFAULT) [] + // INSERT INTO "items" ("address", "name") VALUES (DEFAULT, DEFAULT) [] + +} + +func ExampleDoNothing() { + ds := goqu.From("items") + + sql, args, _ := ds.ToInsertConflictSQL(goqu.DoNothing(), goqu.Record{ + "address": "111 Address", + "name": "bob", + }) + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToInsertConflictSQL(goqu.DoNothing(), goqu.Record{ + "address": "111 Address", + "name": "bob", + }) + fmt.Println(sql, args) + + // Output: + // INSERT INTO "items" ("address", "name") VALUES ('111 Address', 'bob') ON CONFLICT DO NOTHING [] + // INSERT INTO "items" ("address", "name") VALUES (?, ?) ON CONFLICT DO NOTHING [111 Address bob] + +} + +func ExampleDoUpdate() { + ds := goqu.From("items") + + sql, args, _ := ds.ToInsertConflictSQL( + goqu.DoUpdate("address", goqu.C("address").Set(goqu.I("EXCLUDED.address"))), + goqu.Record{"address": "111 Address"}, + ) + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToInsertConflictSQL( + goqu.DoUpdate("address", goqu.C("address").Set(goqu.I("EXCLUDED.address"))), + goqu.Record{"address": "111 Address"}, + ) + fmt.Println(sql, args) + + // Output: + // INSERT INTO "items" ("address") VALUES ('111 Address') ON CONFLICT (address) DO UPDATE SET "address"="EXCLUDED"."address" [] + // INSERT INTO "items" ("address") VALUES (?) ON CONFLICT (address) DO UPDATE SET "address"="EXCLUDED"."address" [111 Address] +} + +func ExampleDoUpdate_where() { + ds := goqu.From("items") + + sql, args, _ := ds.ToInsertConflictSQL( + goqu.DoUpdate("address", goqu.C("address").Set(goqu.I("EXCLUDED.address"))).Where(goqu.I("items.updated").IsNull()), + goqu.Record{"address": "111 Address"}, + ) + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToInsertConflictSQL( + goqu.DoUpdate("address", goqu.C("address").Set(goqu.I("EXCLUDED.address"))).Where(goqu.I("items.updated").IsNull()), + goqu.Record{"address": "111 Address"}, + ) + fmt.Println(sql, args) + + // nolint:lll + // Output: + // INSERT INTO "items" ("address") VALUES ('111 Address') ON CONFLICT (address) DO UPDATE SET "address"="EXCLUDED"."address" WHERE ("items"."updated" IS NULL) [] + // INSERT INTO "items" ("address") VALUES (?) ON CONFLICT (address) DO UPDATE SET "address"="EXCLUDED"."address" WHERE ("items"."updated" IS NULL) [111 Address] +} + +func ExampleFIRST() { + ds := goqu.From("test").Select(goqu.FIRST("col")) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT FIRST("col") FROM "test" [] + // SELECT FIRST("col") FROM "test" [] +} + +func ExampleFIRST_as() { + sql, _, _ := goqu.From("test").Select(goqu.FIRST("a").As("a")).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT FIRST("a") AS "a" FROM "test" +} + +// This example shows how to create custom SQL Functions +func ExampleFunc() { + stragg := func(expression exp.Expression, delimiter string) exp.SQLFunctionExpression { + return goqu.Func("str_agg", expression, goqu.L(delimiter)) + } + sql, _, _ := goqu.From("test").Select(stragg(goqu.C("col"), "|")).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT str_agg("col", |) FROM "test" +} + +func ExampleI() { + ds := goqu.From("test"). + Select( + goqu.I("my_schema.table.col1"), + goqu.I("table.col2"), + goqu.I("col3"), + ) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Select(goqu.I("test.*")) + + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT "my_schema"."table"."col1", "table"."col2", "col3" FROM "test" [] + // SELECT "my_schema"."table"."col1", "table"."col2", "col3" FROM "test" [] + // SELECT "test".* FROM "test" [] + // SELECT "test".* FROM "test" [] +} + +func ExampleL() { + ds := goqu.From("test").Where( + // literal with no args + goqu.L(`"col"::TEXT = ""other_col"::text`), + // literal with args they will be interpolated into the sql by default + goqu.L("col IN (?, ?, ?)", "a", "b", "c"), + ) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT * FROM "test" WHERE ("col"::TEXT = ""other_col"::text AND col IN ('a', 'b', 'c')) [] + // SELECT * FROM "test" WHERE ("col"::TEXT = ""other_col"::text AND col IN (?, ?, ?)) [a b c] +} + +func ExampleL_withArgs() { + ds := goqu.From("test").Where( + goqu.L( + "(? AND ?) OR ?", + goqu.C("a").Eq(1), + goqu.C("b").Eq("b"), + goqu.C("c").In([]string{"a", "b", "c"}), + ), + ) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT * FROM "test" WHERE (("a" = 1) AND ("b" = 'b')) OR ("c" IN ('a', 'b', 'c')) [] + // SELECT * FROM "test" WHERE (("a" = ?) AND ("b" = ?)) OR ("c" IN (?, ?, ?)) [1 b a b c] +} + +func ExampleL_as() { + sql, _, _ := goqu.From("test").Select(goqu.L("json_col->>'totalAmount'").As("total_amount")).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT json_col->>'totalAmount' AS "total_amount" FROM "test" +} + +func ExampleL_comparisons() { + // used from a literal expression + sql, _, _ := goqu.From("test").Where(goqu.L("(a + b)").Eq(10)).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.L("(a + b)").Neq(10)).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.L("(a + b)").Gt(10)).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.L("(a + b)").Gte(10)).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.L("(a + b)").Lt(10)).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.L("(a + b)").Lte(10)).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT * FROM "test" WHERE ((a + b) = 10) + // SELECT * FROM "test" WHERE ((a + b) != 10) + // SELECT * FROM "test" WHERE ((a + b) > 10) + // SELECT * FROM "test" WHERE ((a + b) >= 10) + // SELECT * FROM "test" WHERE ((a + b) < 10) + // SELECT * FROM "test" WHERE ((a + b) <= 10) +} + +func ExampleL_inOperators() { + // using identifiers + sql, _, _ := goqu.From("test").Where(goqu.L("json_col->>'val'").In("a", "b", "c")).ToSQL() + fmt.Println(sql) + // with a slice + sql, _, _ = goqu.From("test").Where(goqu.L("json_col->>'val'").In([]string{"a", "b", "c"})).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.L("json_col->>'val'").NotIn("a", "b", "c")).ToSQL() + fmt.Println(sql) + // with a slice + sql, _, _ = goqu.From("test").Where(goqu.L("json_col->>'val'").NotIn([]string{"a", "b", "c"})).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT * FROM "test" WHERE (json_col->>'val' IN ('a', 'b', 'c')) + // SELECT * FROM "test" WHERE (json_col->>'val' IN ('a', 'b', 'c')) + // SELECT * FROM "test" WHERE (json_col->>'val' NOT IN ('a', 'b', 'c')) + // SELECT * FROM "test" WHERE (json_col->>'val' NOT IN ('a', 'b', 'c')) +} + +func ExampleL_likeComparisons() { + // using identifiers + sql, _, _ := goqu.From("test").Where(goqu.L("(a::text || 'bar')").Like("%a%")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where( + goqu.L("(a::text || 'bar')").Like(regexp.MustCompile("(a|b)")), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.L("(a::text || 'bar')").ILike("%a%")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where( + goqu.L("(a::text || 'bar')").ILike(regexp.MustCompile("(a|b)")), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.L("(a::text || 'bar')").NotLike("%a%")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where( + goqu.L("(a::text || 'bar')").NotLike(regexp.MustCompile("(a|b)")), + ).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where(goqu.L("(a::text || 'bar')").NotILike("%a%")).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("test").Where( + goqu.L("(a::text || 'bar')").NotILike(regexp.MustCompile("(a|b)")), + ).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT * FROM "test" WHERE ((a::text || 'bar') LIKE '%a%') + // SELECT * FROM "test" WHERE ((a::text || 'bar') ~ '(a|b)') + // SELECT * FROM "test" WHERE ((a::text || 'bar') ILIKE '%a%') + // SELECT * FROM "test" WHERE ((a::text || 'bar') ~* '(a|b)') + // SELECT * FROM "test" WHERE ((a::text || 'bar') NOT LIKE '%a%') + // SELECT * FROM "test" WHERE ((a::text || 'bar') !~ '(a|b)') + // SELECT * FROM "test" WHERE ((a::text || 'bar') NOT ILIKE '%a%') + // SELECT * FROM "test" WHERE ((a::text || 'bar') !~* '(a|b)') +} + +func ExampleL_isComparisons() { + sql, args, _ := goqu.From("test").Where(goqu.L("a").Is(nil)).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.L("a").Is(true)).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.L("a").Is(false)).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.L("a").IsNull()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.L("a").IsTrue()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.L("a").IsFalse()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.L("a").IsNot(nil)).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.L("a").IsNot(true)).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.L("a").IsNot(false)).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.L("a").IsNotNull()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.L("a").IsNotTrue()).ToSQL() + fmt.Println(sql, args) + + sql, args, _ = goqu.From("test").Where(goqu.L("a").IsNotFalse()).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE (a IS NULL) [] + // SELECT * FROM "test" WHERE (a IS TRUE) [] + // SELECT * FROM "test" WHERE (a IS FALSE) [] + // SELECT * FROM "test" WHERE (a IS NULL) [] + // SELECT * FROM "test" WHERE (a IS TRUE) [] + // SELECT * FROM "test" WHERE (a IS FALSE) [] + // SELECT * FROM "test" WHERE (a IS NOT NULL) [] + // SELECT * FROM "test" WHERE (a IS NOT TRUE) [] + // SELECT * FROM "test" WHERE (a IS NOT FALSE) [] + // SELECT * FROM "test" WHERE (a IS NOT NULL) [] + // SELECT * FROM "test" WHERE (a IS NOT TRUE) [] + // SELECT * FROM "test" WHERE (a IS NOT FALSE) [] +} + +func ExampleL_betweenComparisons() { + ds := goqu.From("test").Where( + goqu.L("(a + b)").Between(goqu.Range(1, 10)), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where( + goqu.L("(a + b)").NotBetween(goqu.Range(1, 10)), + ) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE ((a + b) BETWEEN 1 AND 10) [] + // SELECT * FROM "test" WHERE ((a + b) BETWEEN ? AND ?) [1 10] + // SELECT * FROM "test" WHERE ((a + b) NOT BETWEEN 1 AND 10) [] + // SELECT * FROM "test" WHERE ((a + b) NOT BETWEEN ? AND ?) [1 10] +} + +func ExampleLAST() { + ds := goqu.From("test").Select(goqu.LAST("col")) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT LAST("col") FROM "test" [] + // SELECT LAST("col") FROM "test" [] +} + +func ExampleLAST_as() { + sql, _, _ := goqu.From("test").Select(goqu.LAST("a").As("a")).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT LAST("a") AS "a" FROM "test" +} + +func ExampleMAX() { + ds := goqu.From("test").Select(goqu.MAX("col")) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT MAX("col") FROM "test" [] + // SELECT MAX("col") FROM "test" [] +} + +func ExampleMAX_as() { + sql, _, _ := goqu.From("test").Select(goqu.MAX("a").As("a")).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT MAX("a") AS "a" FROM "test" +} + +func ExampleMAX_havingClause() { + ds := goqu. + From("test"). + Select(goqu.MAX("a").As("MAX")). + GroupBy("a"). + Having(goqu.MAX("a").Gt(10)) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT MAX("a") AS "MAX" FROM "test" GROUP BY "a" HAVING (MAX("a") > 10) [] + // SELECT MAX("a") AS "MAX" FROM "test" GROUP BY "a" HAVING (MAX("a") > ?) [10] +} + +func ExampleMIN() { + ds := goqu.From("test").Select(goqu.MIN("col")) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT MIN("col") FROM "test" [] + // SELECT MIN("col") FROM "test" [] +} + +func ExampleMIN_as() { + sql, _, _ := goqu.From("test").Select(goqu.MIN("a").As("a")).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT MIN("a") AS "a" FROM "test" +} + +func ExampleMIN_havingClause() { + ds := goqu. + From("test"). + Select(goqu.MIN("a").As("MIN")). + GroupBy("a"). + Having(goqu.MIN("a").Gt(10)) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT MIN("a") AS "MIN" FROM "test" GROUP BY "a" HAVING (MIN("a") > 10) [] + // SELECT MIN("a") AS "MIN" FROM "test" GROUP BY "a" HAVING (MIN("a") > ?) [10] +} + +func ExampleOn() { + ds := goqu.From("test").Join( + goqu.T("my_table"), + goqu.On(goqu.I("my_table.fkey").Eq(goqu.I("other_table.id"))), + ) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT * FROM "test" INNER JOIN "my_table" ON ("my_table"."fkey" = "other_table"."id") [] + // SELECT * FROM "test" INNER JOIN "my_table" ON ("my_table"."fkey" = "other_table"."id") [] +} + +func ExampleOn_withEx() { + ds := goqu.From("test").Join( + goqu.T("my_table"), + goqu.On(goqu.Ex{"my_table.fkey": goqu.I("other_table.id")}), + ) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT * FROM "test" INNER JOIN "my_table" ON ("my_table"."fkey" = "other_table"."id") [] + // SELECT * FROM "test" INNER JOIN "my_table" ON ("my_table"."fkey" = "other_table"."id") [] +} + +func ExampleOr() { + ds := goqu.From("test").Where( + goqu.Or( + goqu.C("col").Eq(10), + goqu.C("col").Eq(20), + ), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE (("col" = 10) OR ("col" = 20)) [] + // SELECT * FROM "test" WHERE (("col" = ?) OR ("col" = ?)) [10 20] +} + +func ExampleOr_withAnd() { + ds := goqu.From("items").Where( + goqu.Or( + goqu.C("a").Gt(10), + goqu.And( + goqu.C("b").Eq(100), + goqu.C("c").Neq("test"), + ), + ), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT * FROM "items" WHERE (("a" > 10) OR (("b" = 100) AND ("c" != 'test'))) [] + // SELECT * FROM "items" WHERE (("a" > ?) OR (("b" = ?) AND ("c" != ?))) [10 100 test] +} + +func ExampleOr_withExMap() { + ds := goqu.From("test").Where( + goqu.Or( + // Ex will be anded together + goqu.Ex{ + "col1": 1, + "col2": true, + }, + goqu.Ex{ + "col3": nil, + "col4": "foo", + }, + ), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE ((("col1" = 1) AND ("col2" IS TRUE)) OR (("col3" IS NULL) AND ("col4" = 'foo'))) [] + // SELECT * FROM "test" WHERE ((("col1" = ?) AND ("col2" IS TRUE)) OR (("col3" IS NULL) AND ("col4" = ?))) [1 foo] +} + +func ExampleRange_numbers() { + ds := goqu.From("test").Where( + goqu.C("col").Between(goqu.Range(1, 10)), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where( + goqu.C("col").NotBetween(goqu.Range(1, 10)), + ) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE ("col" BETWEEN 1 AND 10) [] + // SELECT * FROM "test" WHERE ("col" BETWEEN ? AND ?) [1 10] + // SELECT * FROM "test" WHERE ("col" NOT BETWEEN 1 AND 10) [] + // SELECT * FROM "test" WHERE ("col" NOT BETWEEN ? AND ?) [1 10] +} + +func ExampleRange_strings() { + ds := goqu.From("test").Where( + goqu.C("col").Between(goqu.Range("a", "z")), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where( + goqu.C("col").NotBetween(goqu.Range("a", "z")), + ) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE ("col" BETWEEN 'a' AND 'z') [] + // SELECT * FROM "test" WHERE ("col" BETWEEN ? AND ?) [a z] + // SELECT * FROM "test" WHERE ("col" NOT BETWEEN 'a' AND 'z') [] + // SELECT * FROM "test" WHERE ("col" NOT BETWEEN ? AND ?) [a z] +} + +func ExampleRange_identifiers() { + ds := goqu.From("test").Where( + goqu.C("col1").Between(goqu.Range(goqu.C("col2"), goqu.C("col3"))), + ) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where( + goqu.C("col1").NotBetween(goqu.Range(goqu.C("col2"), goqu.C("col3"))), + ) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE ("col1" BETWEEN "col2" AND "col3") [] + // SELECT * FROM "test" WHERE ("col1" BETWEEN "col2" AND "col3") [] + // SELECT * FROM "test" WHERE ("col1" NOT BETWEEN "col2" AND "col3") [] + // SELECT * FROM "test" WHERE ("col1" NOT BETWEEN "col2" AND "col3") [] +} + +func ExampleS() { + s := goqu.S("test_schema") + t := s.Table("test") + sql, args, _ := goqu. + From(t). + Select( + t.Col("col1"), + t.Col("col2"), + t.Col("col3"), + ). + ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT "test_schema"."test"."col1", "test_schema"."test"."col2", "test_schema"."test"."col3" FROM "test_schema"."test" [] +} + +func ExampleSUM() { + ds := goqu.From("test").Select(goqu.SUM("col")) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT SUM("col") FROM "test" [] + // SELECT SUM("col") FROM "test" [] +} + +func ExampleSUM_as() { + sql, _, _ := goqu.From("test").Select(goqu.SUM("a").As("a")).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT SUM("a") AS "a" FROM "test" +} + +func ExampleSUM_havingClause() { + ds := goqu. + From("test"). + Select(goqu.SUM("a").As("SUM")). + GroupBy("a"). + Having(goqu.SUM("a").Gt(10)) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT SUM("a") AS "SUM" FROM "test" GROUP BY "a" HAVING (SUM("a") > 10) [] + // SELECT SUM("a") AS "SUM" FROM "test" GROUP BY "a" HAVING (SUM("a") > ?) [10] +} + +func ExampleStar() { + ds := goqu.From("test").Select(goqu.Star()) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" [] + // SELECT * FROM "test" [] +} + +func ExampleT() { + t := goqu.T("test") + sql, args, _ := goqu. + From(t). + Select( + t.Col("col1"), + t.Col("col2"), + t.Col("col3"), + ). + ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT "test"."col1", "test"."col2", "test"."col3" FROM "test" [] +} + +func ExampleUsing() { + ds := goqu.From("test").Join( + goqu.T("my_table"), + goqu.Using("fkey"), + ) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT * FROM "test" INNER JOIN "my_table" USING ("fkey") [] + // SELECT * FROM "test" INNER JOIN "my_table" USING ("fkey") [] +} + +func ExampleUsing_withIdentifier() { + ds := goqu.From("test").Join( + goqu.T("my_table"), + goqu.Using(goqu.C("fkey")), + ) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT * FROM "test" INNER JOIN "my_table" USING ("fkey") [] + // SELECT * FROM "test" INNER JOIN "my_table" USING ("fkey") [] +} + +func ExampleEx() { + ds := goqu.From("items").Where( + goqu.Ex{ + "col1": "a", + "col2": 1, + "col3": true, + "col4": false, + "col5": nil, + "col6": []string{"a", "b", "c"}, + }, + ) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // nolint:lll + // Output: + // SELECT * FROM "items" WHERE (("col1" = 'a') AND ("col2" = 1) AND ("col3" IS TRUE) AND ("col4" IS FALSE) AND ("col5" IS NULL) AND ("col6" IN ('a', 'b', 'c'))) [] + // SELECT * FROM "items" WHERE (("col1" = ?) AND ("col2" = ?) AND ("col3" IS TRUE) AND ("col4" IS FALSE) AND ("col5" IS NULL) AND ("col6" IN (?, ?, ?))) [a 1 a b c] +} + +func ExampleEx_withOp() { + sql, args, _ := goqu.From("items").Where( + goqu.Ex{ + "col1": goqu.Op{"neq": "a"}, + "col3": goqu.Op{"isNot": true}, + "col6": goqu.Op{"notIn": []string{"a", "b", "c"}}, + }, + ).ToSQL() + fmt.Println(sql, args) + // Output: + // SELECT * FROM "items" WHERE (("col1" != 'a') AND ("col3" IS NOT TRUE) AND ("col6" NOT IN ('a', 'b', 'c'))) [] +} + +func ExampleEx_in() { + // using an Ex expression map + sql, _, _ := goqu.From("test").Where(goqu.Ex{ + "a": []string{"a", "b", "c"}, + }).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT * FROM "test" WHERE ("a" IN ('a', 'b', 'c')) +} + +func ExampleExOr() { + sql, args, _ := goqu.From("items").Where( + goqu.ExOr{ + "col1": "a", + "col2": 1, + "col3": true, + "col4": false, + "col5": nil, + "col6": []string{"a", "b", "c"}, + }, + ).ToSQL() + fmt.Println(sql, args) + + // nolint:lll + // Output: + // SELECT * FROM "items" WHERE (("col1" = 'a') OR ("col2" = 1) OR ("col3" IS TRUE) OR ("col4" IS FALSE) OR ("col5" IS NULL) OR ("col6" IN ('a', 'b', 'c'))) [] +} + +func ExampleExOr_withOp() { + sql, _, _ := goqu.From("items").Where(goqu.ExOr{ + "col1": goqu.Op{"neq": "a"}, + "col3": goqu.Op{"isNot": true}, + "col6": goqu.Op{"notIn": []string{"a", "b", "c"}}, + }).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("items").Where(goqu.ExOr{ + "col1": goqu.Op{"gt": 1}, + "col2": goqu.Op{"gte": 1}, + "col3": goqu.Op{"lt": 1}, + "col4": goqu.Op{"lte": 1}, + }).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("items").Where(goqu.ExOr{ + "col1": goqu.Op{"like": "a%"}, + "col2": goqu.Op{"notLike": "a%"}, + "col3": goqu.Op{"iLike": "a%"}, + "col4": goqu.Op{"notILike": "a%"}, + }).ToSQL() + fmt.Println(sql) + + sql, _, _ = goqu.From("items").Where(goqu.ExOr{ + "col1": goqu.Op{"like": regexp.MustCompile("^(a|b)")}, + "col2": goqu.Op{"notLike": regexp.MustCompile("^(a|b)")}, + "col3": goqu.Op{"iLike": regexp.MustCompile("^(a|b)")}, + "col4": goqu.Op{"notILike": regexp.MustCompile("^(a|b)")}, + }).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT * FROM "items" WHERE (("col1" != 'a') OR ("col3" IS NOT TRUE) OR ("col6" NOT IN ('a', 'b', 'c'))) + // SELECT * FROM "items" WHERE (("col1" > 1) OR ("col2" >= 1) OR ("col3" < 1) OR ("col4" <= 1)) + // SELECT * FROM "items" WHERE (("col1" LIKE 'a%') OR ("col2" NOT LIKE 'a%') OR ("col3" ILIKE 'a%') OR ("col4" NOT ILIKE 'a%')) + // SELECT * FROM "items" WHERE (("col1" ~ '^(a|b)') OR ("col2" !~ '^(a|b)') OR ("col3" ~* '^(a|b)') OR ("col4" !~* '^(a|b)')) + +} + +func ExampleOp_comparisons() { + + ds := goqu.From("test").Where(goqu.Ex{ + "a": 10, + "b": goqu.Op{"neq": 10}, + "c": goqu.Op{"gte": 10}, + "d": goqu.Op{"lt": 10}, + "e": goqu.Op{"lte": 10}, + }) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE (("a" = 10) AND ("b" != 10) AND ("c" >= 10) AND ("d" < 10) AND ("e" <= 10)) [] + // SELECT * FROM "test" WHERE (("a" = ?) AND ("b" != ?) AND ("c" >= ?) AND ("d" < ?) AND ("e" <= ?)) [10 10 10 10 10] +} + +func ExampleOp_inComparisons() { + // using an Ex expression map + ds := goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"in": []string{"a", "b", "c"}}, + }) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"notIn": []string{"a", "b", "c"}}, + }) + + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE ("a" IN ('a', 'b', 'c')) [] + // SELECT * FROM "test" WHERE ("a" IN (?, ?, ?)) [a b c] + // SELECT * FROM "test" WHERE ("a" NOT IN ('a', 'b', 'c')) [] + // SELECT * FROM "test" WHERE ("a" NOT IN (?, ?, ?)) [a b c] +} + +func ExampleOp_likeComparisons() { + // using an Ex expression map + ds := goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"like": "%a%"}, + }) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"like": regexp.MustCompile("(a|b)")}, + }) + + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"iLike": "%a%"}, + }) + + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"iLike": regexp.MustCompile("(a|b)")}, + }) + + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"notLike": "%a%"}, + }) + + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"notLike": regexp.MustCompile("(a|b)")}, + }) + + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"notILike": "%a%"}, + }) + + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"notILike": regexp.MustCompile("(a|b)")}, + }) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE ("a" LIKE '%a%') [] + // SELECT * FROM "test" WHERE ("a" LIKE ?) [%a%] + // SELECT * FROM "test" WHERE ("a" ~ '(a|b)') [] + // SELECT * FROM "test" WHERE ("a" ~ ?) [(a|b)] + // SELECT * FROM "test" WHERE ("a" ILIKE '%a%') [] + // SELECT * FROM "test" WHERE ("a" ILIKE ?) [%a%] + // SELECT * FROM "test" WHERE ("a" ~* '(a|b)') [] + // SELECT * FROM "test" WHERE ("a" ~* ?) [(a|b)] + // SELECT * FROM "test" WHERE ("a" NOT LIKE '%a%') [] + // SELECT * FROM "test" WHERE ("a" NOT LIKE ?) [%a%] + // SELECT * FROM "test" WHERE ("a" !~ '(a|b)') [] + // SELECT * FROM "test" WHERE ("a" !~ ?) [(a|b)] + // SELECT * FROM "test" WHERE ("a" NOT ILIKE '%a%') [] + // SELECT * FROM "test" WHERE ("a" NOT ILIKE ?) [%a%] + // SELECT * FROM "test" WHERE ("a" !~* '(a|b)') [] + // SELECT * FROM "test" WHERE ("a" !~* ?) [(a|b)] +} + +func ExampleOp_isComparisons() { + // using an Ex expression map + ds := goqu.From("test").Where(goqu.Ex{ + "a": true, + }) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"is": true}, + }) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": false, + }) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"is": false}, + }) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": nil, + }) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"is": nil}, + }) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"isNot": true}, + }) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"isNot": false}, + }) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"isNot": nil}, + }) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE ("a" IS TRUE) [] + // SELECT * FROM "test" WHERE ("a" IS TRUE) [] + // SELECT * FROM "test" WHERE ("a" IS TRUE) [] + // SELECT * FROM "test" WHERE ("a" IS TRUE) [] + // SELECT * FROM "test" WHERE ("a" IS FALSE) [] + // SELECT * FROM "test" WHERE ("a" IS FALSE) [] + // SELECT * FROM "test" WHERE ("a" IS FALSE) [] + // SELECT * FROM "test" WHERE ("a" IS FALSE) [] + // SELECT * FROM "test" WHERE ("a" IS NULL) [] + // SELECT * FROM "test" WHERE ("a" IS NULL) [] + // SELECT * FROM "test" WHERE ("a" IS NULL) [] + // SELECT * FROM "test" WHERE ("a" IS NULL) [] + // SELECT * FROM "test" WHERE ("a" IS NOT TRUE) [] + // SELECT * FROM "test" WHERE ("a" IS NOT TRUE) [] + // SELECT * FROM "test" WHERE ("a" IS NOT FALSE) [] + // SELECT * FROM "test" WHERE ("a" IS NOT FALSE) [] + // SELECT * FROM "test" WHERE ("a" IS NOT NULL) [] + // SELECT * FROM "test" WHERE ("a" IS NOT NULL) [] +} + +func ExampleOp_betweenComparisons() { + ds := goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"between": goqu.Range(1, 10)}, + }) + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + ds = goqu.From("test").Where(goqu.Ex{ + "a": goqu.Op{"notBetween": goqu.Range(1, 10)}, + }) + sql, args, _ = ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE ("a" BETWEEN 1 AND 10) [] + // SELECT * FROM "test" WHERE ("a" BETWEEN ? AND ?) [1 10] + // SELECT * FROM "test" WHERE ("a" NOT BETWEEN 1 AND 10) [] + // SELECT * FROM "test" WHERE ("a" NOT BETWEEN ? AND ?) [1 10] +} + +// When using a single op with multiple keys they are ORed together +func ExampleOp_withMultipleKeys() { + ds := goqu.From("items").Where(goqu.Ex{ + "col1": goqu.Op{"is": nil, "eq": 10}, + }) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "items" WHERE (("col1" = 10) OR ("col1" IS NULL)) [] + // SELECT * FROM "items" WHERE (("col1" = ?) OR ("col1" IS NULL)) [10] +} + +func ExampleRecord_insert() { + ds := goqu.From("test") + + records := []goqu.Record{ + {"col1": 1, "col2": "foo"}, + {"col1": 2, "col2": "bar"}, + } + + sql, args, _ := ds.ToInsertSQL(records) + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToInsertSQL(records) + fmt.Println(sql, args) + // Output: + // INSERT INTO "test" ("col1", "col2") VALUES (1, 'foo'), (2, 'bar') [] + // INSERT INTO "test" ("col1", "col2") VALUES (?, ?), (?, ?) [1 foo 2 bar] +} + +func ExampleRecord_update() { + ds := goqu.From("test") + update := goqu.Record{"col1": 1, "col2": "foo"} + + sql, args, _ := ds.ToUpdateSQL(update) + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToUpdateSQL(update) + fmt.Println(sql, args) + // Output: + // UPDATE "test" SET "col1"=1,"col2"='foo' [] + // UPDATE "test" SET "col1"=?,"col2"=? [1 foo] +} diff --git a/go.mod b/go.mod index 9cb4ba4a..c847088e 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ -module github.com/doug-martin/goqu/v6 +module github.com/doug-martin/goqu/v7 go 1.12 require ( github.com/DATA-DOG/go-sqlmock v1.3.3 + github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-sql-driver/mysql v1.4.1 github.com/lib/pq v1.1.1 github.com/mattn/go-sqlite3 v1.10.0 diff --git a/go.sum b/go.sum index 97bd117c..9c262efd 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFD github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -11,6 +13,7 @@ github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK86 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -26,6 +29,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b h1:mSUCVIwDx4hfXJfWsOPfdzEHxzb2Xjl6BQ8YgPnazQA= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= diff --git a/go.test.sh b/go.test.sh index 34dbbfb3..ffd833a0 100755 --- a/go.test.sh +++ b/go.test.sh @@ -3,10 +3,4 @@ set -e echo "" > coverage.txt -for d in $(go list ./... | grep -v vendor); do - go test -race -coverprofile=profile.out -covermode=atomic $d - if [ -f profile.out ]; then - cat profile.out >> coverage.txt - rm profile.out - fi -done +go test -coverprofile=coverage.txt -coverpkg=./... ./... \ No newline at end of file diff --git a/goqu.go b/goqu.go index 2b55b1d2..eb3b46e5 100644 --- a/goqu.go +++ b/goqu.go @@ -11,3 +11,36 @@ goqu an idiomatch SQL builder, and query package. Please see https://github.com/doug-martin/goqu for an introduction to goqu. */ package goqu + +import ( + "database/sql" + + "github.com/doug-martin/goqu/v7/internal/util" +) + +type DialectWrapper struct { + dialect string +} + +// Creates a new DialectWrapper to create goqu.Datasets or goqu.Databases with the specified dialect. +func Dialect(dialect string) DialectWrapper { + return DialectWrapper{dialect: dialect} +} + +func (dw DialectWrapper) From(table ...interface{}) *Dataset { + return From(table...).WithDialect(dw.dialect) +} + +func (dw DialectWrapper) DB(db *sql.DB) *Database { + return newDatabase(dw.dialect, db) +} + +func New(dialect string, db *sql.DB) *Database { + return newDatabase(dialect, db) +} + +// Set the column rename function. This is used for struct fields that do not have a db tag to specify the column name +// By default all struct fields that do not have a db tag will be converted lowercase +func SetColumnRenameFunction(renameFunc func(string) string) { + util.SetColumnRenameFunction(renameFunc) +} diff --git a/goqu_example_test.go b/goqu_example_test.go new file mode 100644 index 00000000..4c02f5c2 --- /dev/null +++ b/goqu_example_test.go @@ -0,0 +1,232 @@ +package goqu_test + +import ( + "fmt" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/doug-martin/goqu/v7" + _ "github.com/doug-martin/goqu/v7/dialect/mysql" + _ "github.com/doug-martin/goqu/v7/dialect/postgres" + _ "github.com/doug-martin/goqu/v7/dialect/sqlite3" +) + +// Creating a mysql dataset. Be sure to import the mysql adapter +func ExampleDialect_datasetMysql() { + // import _ "github.com/doug-martin/goqu/v7/adapters/mysql" + + d := goqu.Dialect("mysql") + ds := d.From("test").Where(goqu.Ex{ + "foo": "bar", + "baz": []int64{1, 2, 3}, + }).Limit(10) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM `test` WHERE ((`baz` IN (1, 2, 3)) AND (`foo` = 'bar')) LIMIT 10 [] + // SELECT * FROM `test` WHERE ((`baz` IN (?, ?, ?)) AND (`foo` = ?)) LIMIT ? [1 2 3 bar 10] +} + +// Creating a mysql database. Be sure to import the mysql adapter +func ExampleDialect_dbMysql() { + // import _ "github.com/doug-martin/goqu/v7/adapters/mysql" + + type item struct { + ID int64 `db:"id"` + Address string `db:"address"` + Name string `db:"name"` + } + + // set up a mock db this would normally be + // db, err := sql.Open("mysql", dbURI) + // if err != nil { + // panic(err.Error()) + // } + mDb, mock, _ := sqlmock.New() + + d := goqu.Dialect("mysql") + + db := d.DB(mDb) + + // use the db.From to get a dataset to execute queries + ds := db.From("items").Where(goqu.C("id").Eq(1)) + + // set up mock for example purposes + mock.ExpectQuery("SELECT `address`, `id`, `name` FROM `items` WHERE \\(`id` = 1\\) LIMIT 1"). + WillReturnRows( + sqlmock.NewRows([]string{"id", "address", "name"}). + FromCSVString("1, 111 Test Addr,Test1"), + ) + var it item + found, err := ds.ScanStruct(&it) + fmt.Println(it, found, err) + + // set up mock for example purposes + mock.ExpectQuery("SELECT `address`, `id`, `name` FROM `items` WHERE \\(`id` = \\?\\) LIMIT \\?"). + WithArgs(1, 1). + WillReturnRows( + sqlmock.NewRows([]string{"id", "address", "name"}). + FromCSVString("1, 111 Test Addr,Test1"), + ) + + found, err = ds.Prepared(true).ScanStruct(&it) + fmt.Println(it, found, err) + + // Output: + // {1 111 Test Addr Test1} true + // {1 111 Test Addr Test1} true +} + +// Creating a mysql dataset. Be sure to import the postgres adapter +func ExampleDialect_datasetPostgres() { + // import _ "github.com/doug-martin/goqu/v7/adapters/postgres" + + d := goqu.Dialect("postgres") + ds := d.From("test").Where(goqu.Ex{ + "foo": "bar", + "baz": []int64{1, 2, 3}, + }).Limit(10) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" WHERE (("baz" IN (1, 2, 3)) AND ("foo" = 'bar')) LIMIT 10 [] + // SELECT * FROM "test" WHERE (("baz" IN ($1, $2, $3)) AND ("foo" = $4)) LIMIT $5 [1 2 3 bar 10] +} + +// Creating a postgres dataset. Be sure to import the postgres adapter +func ExampleDialect_dbPostgres() { + // import _ "github.com/doug-martin/goqu/v7/adapters/postgres" + + type item struct { + ID int64 `db:"id"` + Address string `db:"address"` + Name string `db:"name"` + } + + // set up a mock db this would normally be + // db, err := sql.Open("postgres", dbURI) + // if err != nil { + // panic(err.Error()) + // } + mDb, mock, _ := sqlmock.New() + + d := goqu.Dialect("postgres") + + db := d.DB(mDb) + + // use the db.From to get a dataset to execute queries + ds := db.From("items").Where(goqu.C("id").Eq(1)) + + // set up mock for example purposes + mock.ExpectQuery(`SELECT "address", "id", "name" FROM "items" WHERE \("id" = 1\) LIMIT 1`). + WillReturnRows( + sqlmock.NewRows([]string{"id", "address", "name"}). + FromCSVString("1, 111 Test Addr,Test1"), + ) + var it item + found, err := ds.ScanStruct(&it) + fmt.Println(it, found, err) + + // set up mock for example purposes + mock.ExpectQuery(`SELECT "address", "id", "name" FROM "items" WHERE \("id" = \$1\) LIMIT \$2`). + WithArgs(1, 1). + WillReturnRows( + sqlmock.NewRows([]string{"id", "address", "name"}). + FromCSVString("1, 111 Test Addr,Test1"), + ) + + found, err = ds.Prepared(true).ScanStruct(&it) + fmt.Println(it, found, err) + + // Output: + // {1 111 Test Addr Test1} true + // {1 111 Test Addr Test1} true +} + +// Creating a mysql dataset. Be sure to import the sqlite3 adapter +func ExampleDialect_datasetSqlite3() { + // import _ "github.com/doug-martin/goqu/v7/adapters/sqlite3" + + d := goqu.Dialect("sqlite3") + ds := d.From("test").Where(goqu.Ex{ + "foo": "bar", + "baz": []int64{1, 2, 3}, + }).Limit(10) + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + sql, args, _ = ds.Prepared(true).ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM `test` WHERE ((`baz` IN (1, 2, 3)) AND (`foo` = 'bar')) LIMIT 10 [] + // SELECT * FROM `test` WHERE ((`baz` IN (?, ?, ?)) AND (`foo` = ?)) LIMIT ? [1 2 3 bar 10] +} + +// Creating a sqlite3 database. Be sure to import the sqlite3 adapter +func ExampleDialect_dbSqlite3() { + // import _ "github.com/doug-martin/goqu/v7/adapters/sqlite3" + + type item struct { + ID int64 `db:"id"` + Address string `db:"address"` + Name string `db:"name"` + } + + // set up a mock db this would normally be + // db, err := sql.Open("sqlite3", dbURI) + // if err != nil { + // panic(err.Error()) + // } + mDb, mock, _ := sqlmock.New() + + d := goqu.Dialect("sqlite3") + + db := d.DB(mDb) + + // use the db.From to get a dataset to execute queries + ds := db.From("items").Where(goqu.C("id").Eq(1)) + + // set up mock for example purposes + mock.ExpectQuery("SELECT `address`, `id`, `name` FROM `items` WHERE \\(`id` = 1\\) LIMIT 1"). + WillReturnRows( + sqlmock.NewRows([]string{"id", "address", "name"}). + FromCSVString("1, 111 Test Addr,Test1"), + ) + var it item + found, err := ds.ScanStruct(&it) + fmt.Println(it, found, err) + + // set up mock for example purposes + mock.ExpectQuery("SELECT `address`, `id`, `name` FROM `items` WHERE \\(`id` = \\?\\) LIMIT \\?"). + WithArgs(1, 1). + WillReturnRows( + sqlmock.NewRows([]string{"id", "address", "name"}). + FromCSVString("1, 111 Test Addr,Test1"), + ) + + found, err = ds.Prepared(true).ScanStruct(&it) + fmt.Println(it, found, err) + + // Output: + // {1 111 Test Addr Test1} true + // {1 111 Test Addr Test1} true +} + +func ExampleFrom() { + sql, args, _ := goqu.From("test").ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM "test" [] +} diff --git a/goqu_test.go b/goqu_test.go deleted file mode 100644 index 5d637cd5..00000000 --- a/goqu_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package goqu - -type testNoReturnAdapter struct { - Adapter -} - -func (me *testNoReturnAdapter) SupportsReturn() bool { - return false -} - -type testLimitAdapter struct { - Adapter -} - -func (me *testLimitAdapter) SupportsLimitOnDelete() bool { - return true -} - -func (me *testLimitAdapter) SupportsLimitOnUpdate() bool { - return true -} - -type testOrderAdapter struct { - Adapter -} - -func (me *testOrderAdapter) SupportsOrderByOnDelete() bool { - return true -} - -func (me *testOrderAdapter) SupportsOrderByOnUpdate() bool { - return true -} - -func init() { - RegisterAdapter("mock", func(ds *Dataset) Adapter { - return NewDefaultAdapter(ds) - }) - RegisterAdapter("no-return", func(ds *Dataset) Adapter { - adapter := NewDefaultAdapter(ds) - return &testNoReturnAdapter{adapter} - }) - RegisterAdapter("limit", func(ds *Dataset) Adapter { - adapter := NewDefaultAdapter(ds) - return &testLimitAdapter{adapter} - }) - RegisterAdapter("order", func(ds *Dataset) Adapter { - adapter := NewDefaultAdapter(ds) - return &testOrderAdapter{adapter} - }) - -} diff --git a/internal/errors/error.go b/internal/errors/error.go new file mode 100644 index 00000000..6aa74333 --- /dev/null +++ b/internal/errors/error.go @@ -0,0 +1,28 @@ +package errors + +import "fmt" + +type Error struct { + err string +} + +func New(message string, args ...interface{}) error { + return Error{err: "goqu: " + fmt.Sprintf(message, args...)} +} + +func (e Error) Error() string { + return e.err +} + +type EncodeError struct { + error + err string +} + +func NewEncodeError(t interface{}) error { + return Error{err: "goqu_encode_error: " + fmt.Sprintf("Unable to encode value %+v", t)} +} + +func (e EncodeError) Error() string { + return e.err +} diff --git a/internal/sb/sql_builder.go b/internal/sb/sql_builder.go new file mode 100644 index 00000000..8dbf660d --- /dev/null +++ b/internal/sb/sql_builder.go @@ -0,0 +1,107 @@ +package sb + +import ( + "bytes" +) + +// Builder that is composed of a bytes.Buffer. It is used internally and by adapters to build SQL statements +type ( + SQLBuilder interface { + Error() error + SetError(err error) SQLBuilder + Clear() SQLBuilder + WriteArg(i ...interface{}) SQLBuilder + Write(p []byte) SQLBuilder + WriteStrings(ss ...string) SQLBuilder + WriteRunes(r ...rune) SQLBuilder + IsPrepared() bool + CurrentArgPosition() int + ToSQL() (sql string, args []interface{}, err error) + } + sqlBuilder struct { + buf *bytes.Buffer + // True if the sql should not be interpolated + isPrepared bool + // Current Number of arguments, used by adapters that need positional placeholders + currentArgPosition int + args []interface{} + err error + } +) + +func NewSQLBuilder(isPrepared bool) SQLBuilder { + return &sqlBuilder{ + buf: &bytes.Buffer{}, + isPrepared: isPrepared, + args: make([]interface{}, 0), + currentArgPosition: 1, + } +} + +func (b *sqlBuilder) Error() error { + return b.err +} + +func (b *sqlBuilder) SetError(err error) SQLBuilder { + if b.err == nil { + b.err = err + } + return b +} + +func (b *sqlBuilder) Clear() SQLBuilder { + b.buf.Truncate(0) + b.args = make([]interface{}, 0) + b.err = nil + return b +} + +func (b *sqlBuilder) Write(bs []byte) SQLBuilder { + if b.err == nil { + b.buf.Write(bs) + } + return b +} +func (b *sqlBuilder) WriteStrings(ss ...string) SQLBuilder { + if b.err == nil { + for _, s := range ss { + b.buf.WriteString(s) + } + } + return b +} +func (b *sqlBuilder) WriteRunes(rs ...rune) SQLBuilder { + if b.err == nil { + for _, r := range rs { + b.buf.WriteRune(r) + } + } + return b +} + +// Returns true if the sql is a prepared statement +func (b *sqlBuilder) IsPrepared() bool { + return b.isPrepared +} + +// Returns true if the sql is a prepared statement +func (b *sqlBuilder) CurrentArgPosition() int { + return b.currentArgPosition +} + +// Adds an argument to the builder, used when IsPrepared is false +func (b *sqlBuilder) WriteArg(i ...interface{}) SQLBuilder { + if b.err == nil { + b.currentArgPosition += len(i) + b.args = append(b.args, i...) + } + return b +} + +// Returns the sql string, and arguments. +func (b *sqlBuilder) ToSQL() (sql string, args []interface{}, err error) { + if b.err != nil { + return sql, args, b.err + } + return b.buf.String(), b.args, nil +} diff --git a/tags.go b/internal/tag/tags.go similarity index 51% rename from tags.go rename to internal/tag/tags.go index 3fab30d8..843aee01 100644 --- a/tags.go +++ b/internal/tag/tags.go @@ -1,15 +1,22 @@ -package goqu +package tag -import "strings" +import ( + "reflect" + "strings" +) // tagOptions is the string following a comma in a struct field's "json" // tag, or the empty string. It does not include the leading comma. -type tagOptions string +type Options string + +func New(tagName string, st reflect.StructTag) Options { + return Options(st.Get(tagName)) +} // Contains reports whether a comma-separated list of options // contains a particular substr flag. substr must be surrounded by a // string boundary or commas. -func (o tagOptions) Contains(optionName string) bool { +func (o Options) Contains(optionName string) bool { if len(o) == 0 { return false } @@ -29,3 +36,17 @@ func (o tagOptions) Contains(optionName string) bool { } return ret } + +// Contains reports whether a comma-separated list of options +// contains a particular substr flag. substr must be surrounded by a +// string boundary or commas. +func (o Options) Equals(val string) bool { + if len(o) == 0 { + return false + } + return string(o) == val +} + +func (o Options) IsEmpty() bool { + return len(o) == 0 +} diff --git a/internal/util/reflect.go b/internal/util/reflect.go new file mode 100644 index 00000000..6bee7bc9 --- /dev/null +++ b/internal/util/reflect.go @@ -0,0 +1,200 @@ +package util + +import ( + "reflect" + "strings" + "sync" + + "github.com/doug-martin/goqu/v7/internal/errors" +) + +type ( + ColumnData struct { + ColumnName string + Transient bool + FieldName string + GoType reflect.Type + } + ColumnMap map[string]ColumnData +) + +func IsUint(k reflect.Kind) bool { + return (k == reflect.Uint) || + (k == reflect.Uint8) || + (k == reflect.Uint16) || + (k == reflect.Uint32) || + (k == reflect.Uint64) +} + +func IsInt(k reflect.Kind) bool { + return (k == reflect.Int) || + (k == reflect.Int8) || + (k == reflect.Int16) || + (k == reflect.Int32) || + (k == reflect.Int64) +} + +func IsFloat(k reflect.Kind) bool { + return (k == reflect.Float32) || + (k == reflect.Float64) +} + +func IsString(k reflect.Kind) bool { + return k == reflect.String +} + +func IsBool(k reflect.Kind) bool { + return k == reflect.Bool +} + +func IsSlice(k reflect.Kind) bool { + return k == reflect.Slice +} + +func IsStruct(k reflect.Kind) bool { + return k == reflect.Struct +} + +func IsInvalid(k reflect.Kind) bool { + return k == reflect.Invalid +} + +func IsPointer(k reflect.Kind) bool { + return k == reflect.Ptr +} + +var structMapCache = make(map[interface{}]ColumnMap) +var structMapCacheLock = sync.Mutex{} + +var defaultColumnRenameFunction = strings.ToLower +var columnRenameFunction = defaultColumnRenameFunction + +func SetColumnRenameFunction(newFunction func(string) string) { + columnRenameFunction = newFunction +} + +func GetTypeInfo(i interface{}, val reflect.Value) (reflect.Type, reflect.Kind, bool) { + var t reflect.Type + isSliceOfPointers := false + valKind := val.Kind() + if valKind == reflect.Slice { + if reflect.ValueOf(i).Kind() == reflect.Ptr { + t = reflect.TypeOf(i).Elem().Elem() + } else { + t = reflect.TypeOf(i).Elem() + } + if t.Kind() == reflect.Ptr { + isSliceOfPointers = true + t = t.Elem() + } + valKind = t.Kind() + } else { + t = val.Type() + } + return t, valKind, isSliceOfPointers +} + +func AssignStructVals(i interface{}, results []map[string]interface{}, cm ColumnMap) { + val := reflect.Indirect(reflect.ValueOf(i)) + t, _, isSliceOfPointers := GetTypeInfo(i, val) + switch val.Kind() { + case reflect.Struct: + result := results[0] + initEmbeddedPtr(val) + for name, data := range cm { + src, ok := result[name] + if ok { + srcVal := reflect.ValueOf(src) + f := val.FieldByName(data.FieldName) + if f.Kind() == reflect.Ptr { + f.Set(reflect.ValueOf(srcVal)) + } else { + f.Set(reflect.Indirect(srcVal)) + } + } + } + case reflect.Slice: + for _, result := range results { + row := reflect.Indirect(reflect.New(t)) + initEmbeddedPtr(row) + for name, data := range cm { + src, ok := result[name] + if ok { + srcVal := reflect.ValueOf(src) + f := row.FieldByName(data.FieldName) + if f.Kind() == reflect.Ptr { + f.Set(reflect.ValueOf(srcVal)) + } else { + f.Set(reflect.Indirect(srcVal)) + } + } + } + if isSliceOfPointers { + val.Set(reflect.Append(val, row.Addr())) + } else { + val.Set(reflect.Append(val, row)) + } + } + } +} + +func initEmbeddedPtr(value reflect.Value) { + for i := 0; i < value.NumField(); i++ { + v := value.Field(i) + kind := v.Kind() + t := value.Type().Field(i) + if t.Anonymous && kind == reflect.Ptr { + z := reflect.New(t.Type.Elem()) + v.Set(z) + } + } +} + +func GetColumnMap(i interface{}) (ColumnMap, error) { + val := reflect.Indirect(reflect.ValueOf(i)) + t, valKind, _ := GetTypeInfo(i, val) + if valKind != reflect.Struct { + return nil, errors.New("cannot scan into this type: %v", t) // #nosec + } + + structMapCacheLock.Lock() + defer structMapCacheLock.Unlock() + if _, ok := structMapCache[t]; !ok { + structMapCache[t] = createColumnMap(t) + } + return structMapCache[t], nil +} + +func createColumnMap(t reflect.Type) ColumnMap { + cm, n := ColumnMap{}, t.NumField() + var subColMaps []ColumnMap + for i := 0; i < n; i++ { + f := t.Field(i) + if f.Anonymous && (f.Type.Kind() == reflect.Struct || f.Type.Kind() == reflect.Ptr) { + if f.Type.Kind() == reflect.Ptr { + subColMaps = append(subColMaps, createColumnMap(f.Type.Elem())) + } else { + subColMaps = append(subColMaps, createColumnMap(f.Type)) + } + } else { + columnName := f.Tag.Get("db") + if columnName == "" { + columnName = columnRenameFunction(f.Name) + } + cm[columnName] = ColumnData{ + ColumnName: columnName, + Transient: columnName == "-", + FieldName: f.Name, + GoType: f.Type, + } + } + } + for _, subCm := range subColMaps { + for key, val := range subCm { + if _, ok := cm[key]; !ok { + cm[key] = val + } + } + } + return cm +} diff --git a/internal/util/reflect_test.go b/internal/util/reflect_test.go new file mode 100644 index 00000000..fdb94838 --- /dev/null +++ b/internal/util/reflect_test.go @@ -0,0 +1,89 @@ +package util + +import ( + "strings" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type reflectTest struct { + suite.Suite +} + +func (rt *reflectTest) TestColumnRename() { + t := rt.T() + // different key names are used each time to circumvent the caching that happens + // it seems like a solid assumption that when people use this feature, + // they would simply set a renaming function once at startup, + // and not change between requests like this + lowerAnon := struct { + FirstLower string + LastLower string + }{} + lowerColumnMap, lowerErr := GetColumnMap(&lowerAnon) + assert.NoError(t, lowerErr) + + var lowerKeys []string + for key := range lowerColumnMap { + lowerKeys = append(lowerKeys, key) + } + assert.Contains(t, lowerKeys, "firstlower") + assert.Contains(t, lowerKeys, "lastlower") + + // changing rename function + SetColumnRenameFunction(strings.ToUpper) + + upperAnon := struct { + FirstUpper string + LastUpper string + }{} + upperColumnMap, upperErr := GetColumnMap(&upperAnon) + assert.NoError(t, upperErr) + + var upperKeys []string + for key := range upperColumnMap { + upperKeys = append(upperKeys, key) + } + assert.Contains(t, upperKeys, "FIRSTUPPER") + assert.Contains(t, upperKeys, "LASTUPPER") + + SetColumnRenameFunction(defaultColumnRenameFunction) +} + +func (rt *reflectTest) TestParallelGetColumnMap() { + t := rt.T() + + type item struct { + id uint + name string + } + + wg := sync.WaitGroup{} + + wg.Add(1) + go func() { + i := item{id: 1, name: "bob"} + m, err := GetColumnMap(i) + assert.NoError(t, err) + assert.NotNil(t, m) + wg.Done() + }() + + wg.Add(1) + go func() { + i := item{id: 2, name: "sally"} + m, err := GetColumnMap(i) + assert.NoError(t, err) + assert.NotNil(t, m) + wg.Done() + }() + + wg.Wait() +} + +func TestReflectSuite(t *testing.T) { + suite.Run(t, new(reflectTest)) +} diff --git a/internal/util/value_slice.go b/internal/util/value_slice.go new file mode 100644 index 00000000..b013ed5e --- /dev/null +++ b/internal/util/value_slice.go @@ -0,0 +1,33 @@ +package util + +import ( + "fmt" + "reflect" + "sort" + "strings" +) + +type ValueSlice []reflect.Value + +func (vs ValueSlice) Len() int { return len(vs) } +func (vs ValueSlice) Less(i, j int) bool { return vs[i].String() < vs[j].String() } +func (vs ValueSlice) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } + +func (vs ValueSlice) Equal(other ValueSlice) bool { + sort.Sort(other) + for i, key := range vs { + if other[i].String() != key.String() { + return false + } + } + return true +} + +func (vs ValueSlice) String() string { + vals := make([]string, vs.Len()) + for i, key := range vs { + vals[i] = fmt.Sprintf(`"%s"`, key.String()) + } + sort.Strings(vals) + return fmt.Sprintf("[%s]", strings.Join(vals, ",")) +} diff --git a/mocks/SQLDialect.go b/mocks/SQLDialect.go new file mode 100644 index 00000000..31e227c6 --- /dev/null +++ b/mocks/SQLDialect.go @@ -0,0 +1,38 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import exp "github.com/doug-martin/goqu/v7/exp" + +import mock "github.com/stretchr/testify/mock" +import sb "github.com/doug-martin/goqu/v7/internal/sb" + +// SQLDialect is an autogenerated mock type for the SQLDialect type +type SQLDialect struct { + mock.Mock +} + +// ToDeleteSQL provides a mock function with given fields: b, clauses +func (_m *SQLDialect) ToDeleteSQL(b sb.SQLBuilder, clauses exp.Clauses) { + _m.Called(b, clauses) +} + +// ToInsertSQL provides a mock function with given fields: b, clauses, ie +func (_m *SQLDialect) ToInsertSQL(b sb.SQLBuilder, clauses exp.Clauses, ie exp.InsertExpression) { + _m.Called(b, clauses, ie) +} + +// ToSelectSQL provides a mock function with given fields: b, clauses +func (_m *SQLDialect) ToSelectSQL(b sb.SQLBuilder, clauses exp.Clauses) { + _m.Called(b, clauses) +} + +// ToTruncateSQL provides a mock function with given fields: b, clauses, options +func (_m *SQLDialect) ToTruncateSQL(b sb.SQLBuilder, clauses exp.Clauses, options exp.TruncateOptions) { + _m.Called(b, clauses, options) +} + +// ToUpdateSQL provides a mock function with given fields: b, clauses, update +func (_m *SQLDialect) ToUpdateSQL(b sb.SQLBuilder, clauses exp.Clauses, update interface{}) { + _m.Called(b, clauses, update) +} diff --git a/sql_builder.go b/sql_builder.go deleted file mode 100644 index 44a96276..00000000 --- a/sql_builder.go +++ /dev/null @@ -1,28 +0,0 @@ -package goqu - -import "bytes" - -//Builder that is composed of a bytes.Buffer. It is used internally and by adapters to build SQL statements -type SqlBuilder struct { - bytes.Buffer - //True if the sql should not be interpolated - IsPrepared bool - //Current Number of arguments, used by adapters that need positional placeholders - CurrentArgPosition int - args []interface{} -} - -func NewSqlBuilder(isPrepared bool) *SqlBuilder { - return &SqlBuilder{IsPrepared: isPrepared, args: make([]interface{}, 0), CurrentArgPosition: 1} -} - -//Adds an argument to the builder, used when IsPrepared is false -func (me *SqlBuilder) WriteArg(i interface{}) { - me.CurrentArgPosition++ - me.args = append(me.args, i) -} - -//Returns the sql string, and arguments. -func (me *SqlBuilder) ToSql() (string, []interface{}) { - return me.String(), me.args -} diff --git a/sql_dialect.go b/sql_dialect.go new file mode 100644 index 00000000..937d3c51 --- /dev/null +++ b/sql_dialect.go @@ -0,0 +1,1164 @@ +package goqu + +import ( + "database/sql/driver" + "reflect" + "strconv" + "strings" + "time" + "unicode/utf8" + + "github.com/doug-martin/goqu/v7/exp" + "github.com/doug-martin/goqu/v7/internal/errors" + "github.com/doug-martin/goqu/v7/internal/sb" + "github.com/doug-martin/goqu/v7/internal/util" +) + +type ( + // An adapter interface to be used by a Dataset to generate SQL for a specific dialect. + // See DefaultAdapter for a concrete implementation and examples. + SQLDialect interface { + ToSelectSQL(b sb.SQLBuilder, clauses exp.Clauses) + ToUpdateSQL(b sb.SQLBuilder, clauses exp.Clauses, update interface{}) + ToInsertSQL(b sb.SQLBuilder, clauses exp.Clauses, ie exp.InsertExpression) + ToDeleteSQL(b sb.SQLBuilder, clauses exp.Clauses) + ToTruncateSQL(b sb.SQLBuilder, clauses exp.Clauses, options exp.TruncateOptions) + } + // The default adapter. This class should be used when building a new adapter. When creating a new adapter you can + // either override methods, or more typically update default values. + // See (github.com/doug-martin/goqu/adapters/postgres) + sqlDialect struct { + dialectOptions *SQLDialectOptions + } +) + +var ( + replacementRune = '?' + dialects = make(map[string]SQLDialect) + TrueLiteral = exp.NewLiteralExpression("TRUE") + FalseLiteral = exp.NewLiteralExpression("FALSE") + + errCTENotSupported = errors.New("adapter does not support CTE with clause") + errRecursiveCTENotSupported = errors.New("adapter does not support CTE with recursive clause") + errNoUpdatedValuesProvided = errors.New("no update values provided") + errConflictUpdateValuesRequired = errors.New("values are required for on conflict update expression") + errUpsertWithWhereNotSupported = errors.New("adapter does not support upsert with where clause") + errNoSourceForUpdate = errors.New("no source found when generating update sql") + errNoSourceForInsert = errors.New("no source found when generating insert sql") + errNoSourceForDelete = errors.New("no source found when generating delete sql") + errNoSourceForTruncate = errors.New("no source found when generating truncate sql") + errReturnNotSupported = errors.New("adapter does not support RETURNING clause") +) + +func notSupportedFragmentErr(sqlType string, f SQLFragmentType) error { + return errors.New("unsupported %s SQL fragment %s", sqlType, f) +} + +func notSupportedJoinTypeErr(j exp.JoinExpression) error { + return errors.New("dialect does not support %v", j.JoinType()) +} + +func joinConditionRequiredErr(j exp.JoinExpression) error { + return errors.New("join condition required for conditioned join %v", j.JoinType()) +} + +func misMatchedRowLengthErr(expectedL, actualL int) error { + return errors.New("rows with different value length expected %d got %d", expectedL, actualL) +} + +func unsupportedExpressionTypeErr(e exp.Expression) error { + return errors.New("unsupported expression type %T", e) +} + +func unsupportedIdentifierExpressionErr(t interface{}) error { + return errors.New("unexpected col type must be string or LiteralExpression %+v", t) +} + +func unsupportedBooleanExpressionOperator(op exp.BooleanOperation) error { + return errors.New("boolean operator %+v not supported", op) +} + +func unsupportedRangeExpressionOperator(op exp.RangeOperation) error { + return errors.New("range operator %+v not supported", op) +} + +func init() { + RegisterDialect("default", DefaultDialectOptions()) +} + +func RegisterDialect(name string, do *SQLDialectOptions) { + dialects[strings.ToLower(name)] = newDialect(do) +} + +func DeregisterDialect(name string) { + delete(dialects, strings.ToLower(name)) +} + +func GetDialect(name string) SQLDialect { + name = strings.ToLower(name) + if d, ok := dialects[name]; ok { + return d + } + return newDialect(DefaultDialectOptions()) +} + +func newDialect(do *SQLDialectOptions) SQLDialect { + return &sqlDialect{dialectOptions: do} +} + +func (d *sqlDialect) SupportsReturn() bool { + return d.dialectOptions.SupportsReturn +} + +func (d *sqlDialect) SupportsOrderByOnUpdate() bool { + return d.dialectOptions.SupportsOrderByOnUpdate +} + +func (d *sqlDialect) SupportsLimitOnUpdate() bool { + return d.dialectOptions.SupportsLimitOnUpdate +} + +func (d *sqlDialect) SupportsOrderByOnDelete() bool { + return d.dialectOptions.SupportsOrderByOnDelete +} +func (d *sqlDialect) SupportsLimitOnDelete() bool { + return d.dialectOptions.SupportsLimitOnDelete +} + +func (d *sqlDialect) ToSelectSQL(b sb.SQLBuilder, clauses exp.Clauses) { + for _, f := range d.dialectOptions.SelectSQLOrder { + if b.Error() != nil { + return + } + switch f { + case CommonTableSQLFragment: + d.CommonTablesSQL(b, clauses.CommonTables()) + case SelectSQLFragment: + if clauses.HasSelectDistinct() { + d.SelectDistinctSQL(b, clauses.SelectDistinct()) + } else { + d.SelectSQL(b, clauses.Select()) + } + case FromSQLFragment: + d.FromSQL(b, clauses.From()) + case JoinSQLFragment: + d.JoinSQL(b, clauses.Joins()) + case WhereSQLFragment: + d.WhereSQL(b, clauses.Where()) + case GroupBySQLFragment: + d.GroupBySQL(b, clauses.GroupBy()) + case HavingSQLFragment: + d.HavingSQL(b, clauses.Having()) + case CompoundsSQLFragment: + d.CompoundsSQL(b, clauses.Compounds()) + case OrderSQLFragment: + d.OrderSQL(b, clauses.Order()) + case LimitSQLFragment: + d.LimitSQL(b, clauses.Limit()) + case OffsetSQLFragment: + d.OffsetSQL(b, clauses.Offset()) + case ForSQLFragment: + d.ForSQL(b, clauses.Lock()) + default: + b.SetError(notSupportedFragmentErr("SELECT", f)) + } + } +} + +func (d *sqlDialect) ToUpdateSQL(b sb.SQLBuilder, clauses exp.Clauses, update interface{}) { + updates, err := exp.NewUpdateExpressions(update) + if err != nil { + b.SetError(err) + return + } + if !clauses.HasSources() { + b.SetError(errNoSourceForUpdate) + return + } + for _, f := range d.dialectOptions.UpdateSQLOrder { + if b.Error() != nil { + return + } + switch f { + case CommonTableSQLFragment: + d.CommonTablesSQL(b, clauses.CommonTables()) + case UpdateBeginSQLFragment: + d.UpdateBeginSQL(b) + case SourcesSQLFragment: + d.SourcesSQL(b, clauses.From()) + case UpdateSQLFragment: + d.UpdateExpressionsSQL(b, updates...) + case WhereSQLFragment: + d.WhereSQL(b, clauses.Where()) + case OrderSQLFragment: + if d.dialectOptions.SupportsOrderByOnUpdate { + d.OrderSQL(b, clauses.Order()) + } + case LimitSQLFragment: + if d.dialectOptions.SupportsLimitOnUpdate { + d.LimitSQL(b, clauses.Limit()) + } + case ReturningSQLFragment: + d.ReturningSQL(b, clauses.Returning()) + default: + b.SetError(notSupportedFragmentErr("UPDATE", f)) + } + } +} + +func (d *sqlDialect) ToInsertSQL( + b sb.SQLBuilder, + clauses exp.Clauses, + ie exp.InsertExpression, +) { + if !clauses.HasSources() { + b.SetError(errNoSourceForInsert) + return + } + for _, f := range d.dialectOptions.InsertSQLOrder { + if b.Error() != nil { + return + } + switch f { + case CommonTableSQLFragment: + d.CommonTablesSQL(b, clauses.CommonTables()) + case InsertBeingSQLFragment: + d.InsertBeginSQL(b, ie.OnConflict()) + case SourcesSQLFragment: + d.SourcesSQL(b, clauses.From()) + case InsertSQLFragment: + d.InsertSQL(b, ie) + case ReturningSQLFragment: + d.ReturningSQL(b, clauses.Returning()) + default: + b.SetError(notSupportedFragmentErr("INSERT", f)) + } + } + +} + +func (d *sqlDialect) ToDeleteSQL(b sb.SQLBuilder, clauses exp.Clauses) { + if !clauses.HasSources() { + b.SetError(errNoSourceForDelete) + return + } + for _, f := range d.dialectOptions.DeleteSQLOrder { + if b.Error() != nil { + return + } + switch f { + case CommonTableSQLFragment: + d.CommonTablesSQL(b, clauses.CommonTables()) + case DeleteBeginSQLFragment: + d.DeleteBeginSQL(b) + case FromSQLFragment: + d.FromSQL(b, clauses.From()) + case WhereSQLFragment: + d.WhereSQL(b, clauses.Where()) + case OrderSQLFragment: + if d.dialectOptions.SupportsOrderByOnDelete { + d.OrderSQL(b, clauses.Order()) + } + case LimitSQLFragment: + if d.dialectOptions.SupportsLimitOnDelete { + d.LimitSQL(b, clauses.Limit()) + } + case ReturningSQLFragment: + d.ReturningSQL(b, clauses.Returning()) + default: + b.SetError(notSupportedFragmentErr("DELETE", f)) + } + } +} + +func (d *sqlDialect) ToTruncateSQL(b sb.SQLBuilder, clauses exp.Clauses, opts exp.TruncateOptions) { + if !clauses.HasSources() { + b.SetError(errNoSourceForTruncate) + return + } + for _, f := range d.dialectOptions.TruncateSQLOrder { + if b.Error() != nil { + return + } + switch f { + case TruncateSQLFragment: + d.TruncateSQL(b, clauses.From(), opts) + default: + b.SetError(notSupportedFragmentErr("TRUNCATE", f)) + } + } +} + +// Adds the correct fragment to being an UPDATE statement +func (d *sqlDialect) UpdateBeginSQL(b sb.SQLBuilder) { + b.Write(d.dialectOptions.UpdateClause) +} + +// Adds the correct fragment to being an INSERT statement +func (d *sqlDialect) InsertBeginSQL(b sb.SQLBuilder, o exp.ConflictExpression) { + if d.dialectOptions.SupportsInsertIgnoreSyntax && o != nil { + b.Write(d.dialectOptions.InsertIgnoreClause) + } else { + b.Write(d.dialectOptions.InsertClause) + } +} + +// Adds the correct fragment to being an DELETE statement +func (d *sqlDialect) DeleteBeginSQL(b sb.SQLBuilder) { + b.Write(d.dialectOptions.DeleteClause) +} + +// Generates a TRUNCATE statement +func (d *sqlDialect) TruncateSQL(b sb.SQLBuilder, from exp.ColumnListExpression, opts exp.TruncateOptions) { + if b.Error() != nil { + return + } + b.Write(d.dialectOptions.TruncateClause) + d.SourcesSQL(b, from) + if opts.Identity != d.dialectOptions.EmptyString { + b.WriteRunes(d.dialectOptions.SpaceRune). + WriteStrings(strings.ToUpper(opts.Identity)). + Write(d.dialectOptions.IdentityFragment) + } + if opts.Cascade { + b.Write(d.dialectOptions.CascadeFragment) + } else if opts.Restrict { + b.Write(d.dialectOptions.RestrictFragment) + } +} + +// Adds the columns list to an insert statement +func (d *sqlDialect) InsertSQL(b sb.SQLBuilder, ie exp.InsertExpression) { + switch { + case b.Error() != nil: + return + case ie.IsInsertFrom(): + d.insertFromSQL(b, ie.From()) + case ie.IsEmpty(): + d.defaultValuesSQL(b) + default: + d.insertColumnsSQL(b, ie.Cols()) + d.insertValuesSQL(b, ie.Vals()) + } + d.onConflictSQL(b, ie.OnConflict()) +} + +// Adds column setters in an update SET clause +func (d *sqlDialect) UpdateExpressionsSQL(b sb.SQLBuilder, updates ...exp.UpdateExpression) { + if b.Error() != nil { + return + } + b.Write(d.dialectOptions.SetFragment) + d.updateValuesSQL(b, updates...) + +} + +// Adds the SELECT clause and columns to a sql statement +func (d *sqlDialect) SelectSQL(b sb.SQLBuilder, cols exp.ColumnListExpression) { + if b.Error() != nil { + return + } + b.Write(d.dialectOptions.SelectClause). + WriteRunes(d.dialectOptions.SpaceRune) + if len(cols.Columns()) == 0 { + b.WriteRunes(d.dialectOptions.StarRune) + } else { + d.Literal(b, cols) + } +} + +// Adds the SELECT DISTINCT clause and columns to a sql statement +func (d *sqlDialect) SelectDistinctSQL(b sb.SQLBuilder, cols exp.ColumnListExpression) { + if b.Error() != nil { + return + } + b.Write(d.dialectOptions.SelectClause).Write(d.dialectOptions.DistinctFragment) + d.Literal(b, cols) +} + +func (d *sqlDialect) ReturningSQL(b sb.SQLBuilder, returns exp.ColumnListExpression) { + if b.Error() != nil { + return + } + if returns != nil && len(returns.Columns()) > 0 { + if d.SupportsReturn() { + b.Write(d.dialectOptions.ReturningFragment) + d.Literal(b, returns) + } else { + b.SetError(errReturnNotSupported) + } + } + +} + +// Adds the FROM clause and tables to an sql statement +func (d *sqlDialect) FromSQL(b sb.SQLBuilder, from exp.ColumnListExpression) { + if b.Error() != nil { + return + } + if from != nil && len(from.Columns()) > 0 { + b.Write(d.dialectOptions.FromFragment) + d.SourcesSQL(b, from) + } +} + +// Adds the generates the SQL for a column list +func (d *sqlDialect) SourcesSQL(b sb.SQLBuilder, from exp.ColumnListExpression) { + if b.Error() != nil { + return + } + b.WriteRunes(d.dialectOptions.SpaceRune) + d.Literal(b, from) +} + +// Generates the JOIN clauses for an SQL statement +func (d *sqlDialect) JoinSQL(b sb.SQLBuilder, joins exp.JoinExpressions) { + if b.Error() != nil { + return + } + if len(joins) > 0 { + for _, j := range joins { + joinType, ok := d.dialectOptions.JoinTypeLookup[j.JoinType()] + if !ok { + b.SetError(notSupportedJoinTypeErr(j)) + return + } + b.Write(joinType) + d.Literal(b, j.Table()) + if t, ok := j.(exp.ConditionedJoinExpression); ok { + if t.IsConditionEmpty() { + b.SetError(joinConditionRequiredErr(j)) + return + } + d.joinConditionSQL(b, t.Condition()) + } + } + } +} + +// Generates the WHERE clause for an SQL statement +func (d *sqlDialect) WhereSQL(b sb.SQLBuilder, where exp.ExpressionList) { + if b.Error() != nil { + return + } + if where != nil && len(where.Expressions()) > 0 { + b.Write(d.dialectOptions.WhereFragment) + d.Literal(b, where) + } +} + +// Generates the GROUP BY clause for an SQL statement +func (d *sqlDialect) GroupBySQL(b sb.SQLBuilder, groupBy exp.ColumnListExpression) { + if b.Error() != nil { + return + } + if groupBy != nil && len(groupBy.Columns()) > 0 { + b.Write(d.dialectOptions.GroupByFragment) + d.Literal(b, groupBy) + } +} + +// Generates the HAVING clause for an SQL statement +func (d *sqlDialect) HavingSQL(b sb.SQLBuilder, having exp.ExpressionList) { + if b.Error() != nil { + return + } + if having != nil && len(having.Expressions()) > 0 { + b.Write(d.dialectOptions.HavingFragment) + d.Literal(b, having) + } +} + +// Generates the ORDER BY clause for an SQL statement +func (d *sqlDialect) OrderSQL(b sb.SQLBuilder, order exp.ColumnListExpression) { + if b.Error() != nil { + return + } + if order != nil && len(order.Columns()) > 0 { + b.Write(d.dialectOptions.OrderByFragment) + d.Literal(b, order) + } +} + +// Generates the LIMIT clause for an SQL statement +func (d *sqlDialect) LimitSQL(b sb.SQLBuilder, limit interface{}) { + if b.Error() != nil { + return + } + if limit != nil { + b.Write(d.dialectOptions.LimitFragment) + d.Literal(b, limit) + } +} + +// Generates the OFFSET clause for an SQL statement +func (d *sqlDialect) OffsetSQL(b sb.SQLBuilder, offset uint) { + if b.Error() != nil { + return + } + if offset > 0 { + b.Write(d.dialectOptions.OffsetFragment) + d.Literal(b, offset) + } +} + +// Generates the sql for the WITH clauses for common table expressions (CTE) +func (d *sqlDialect) CommonTablesSQL(b sb.SQLBuilder, ctes []exp.CommonTableExpression) { + if b.Error() != nil { + return + } + if l := len(ctes); l > 0 { + if !d.dialectOptions.SupportsWithCTE { + b.SetError(errCTENotSupported) + return + } + b.Write(d.dialectOptions.WithFragment) + anyRecursive := false + for _, cte := range ctes { + anyRecursive = anyRecursive || cte.IsRecursive() + } + if anyRecursive { + if !d.dialectOptions.SupportsWithCTERecursive { + b.SetError(errRecursiveCTENotSupported) + return + } + b.Write(d.dialectOptions.RecursiveFragment) + } + for i, cte := range ctes { + d.Literal(b, cte) + if i < l-1 { + b.WriteRunes(d.dialectOptions.CommaRune, d.dialectOptions.SpaceRune) + } + } + b.WriteRunes(d.dialectOptions.SpaceRune) + } +} + +// Generates the compound sql clause for an SQL statement (e.g. UNION, INTERSECT) +func (d *sqlDialect) CompoundsSQL(b sb.SQLBuilder, compounds []exp.CompoundExpression) { + if b.Error() != nil { + return + } + for _, compound := range compounds { + d.Literal(b, compound) + } +} + +// Generates the FOR (aka "locking") clause for an SQL statement +func (d *sqlDialect) ForSQL(b sb.SQLBuilder, lockingClause exp.Lock) { + if b.Error() != nil { + return + } + if lockingClause == nil { + return + } + switch lockingClause.Strength() { + case exp.ForNolock: + return + case exp.ForUpdate: + b.Write(d.dialectOptions.ForUpdateFragment) + case exp.ForNoKeyUpdate: + b.Write(d.dialectOptions.ForNoKeyUpdateFragment) + case exp.ForShare: + b.Write(d.dialectOptions.ForShareFragment) + case exp.ForKeyShare: + b.Write(d.dialectOptions.ForKeyShareFragment) + } + // the WAIT case is the default in Postgres, and is what you get if you don't specify NOWAIT or + // SKIP LOCKED. There's no special syntax for it in PG, so we don't do anything for it here + switch lockingClause.WaitOption() { + case exp.NoWait: + b.Write(d.dialectOptions.NowaitFragment) + case exp.SkipLocked: + b.Write(d.dialectOptions.SkipLockedFragment) + } +} + +func (d *sqlDialect) Literal(b sb.SQLBuilder, val interface{}) { + if b.Error() != nil { + return + } + if val == nil { + d.literalNil(b) + return + } + switch v := val.(type) { + case exp.Expression: + d.expressionSQL(b, v) + case int: + d.literalInt(b, int64(v)) + case int32: + d.literalInt(b, int64(v)) + case int64: + d.literalInt(b, v) + case float32: + d.literalFloat(b, float64(v)) + case float64: + d.literalFloat(b, v) + case string: + d.literalString(b, v) + case []byte: + d.literalBytes(b, v) + case bool: + d.literalBool(b, v) + case time.Time: + d.literalTime(b, v) + case *time.Time: + if v == nil { + d.literalNil(b) + return + } + d.literalTime(b, *v) + case driver.Valuer: + dVal, err := v.Value() + if err != nil { + b.SetError(errors.New(err.Error())) + return + } + d.Literal(b, dVal) + default: + d.reflectSQL(b, val) + } +} + +// Adds the DefaultValuesFragment to an SQL statement +func (d *sqlDialect) defaultValuesSQL(b sb.SQLBuilder) { + b.Write(d.dialectOptions.DefaultValuesFragment) +} + +func (d *sqlDialect) insertFromSQL(b sb.SQLBuilder, ae exp.AppendableExpression) { + b.WriteRunes(d.dialectOptions.SpaceRune) + ae.AppendSQL(b) +} + +// Adds the columns list to an insert statement +func (d *sqlDialect) insertColumnsSQL(b sb.SQLBuilder, cols exp.ColumnListExpression) { + b.WriteRunes(d.dialectOptions.SpaceRune, d.dialectOptions.LeftParenRune) + d.Literal(b, cols) + b.WriteRunes(d.dialectOptions.RightParenRune) +} + +// Adds the values clause to an SQL statement +func (d *sqlDialect) insertValuesSQL(b sb.SQLBuilder, values [][]interface{}) { + b.Write(d.dialectOptions.ValuesFragment) + rowLen := len(values[0]) + valueLen := len(values) + for i, row := range values { + if len(row) != rowLen { + b.SetError(misMatchedRowLengthErr(rowLen, len(row))) + return + } + d.Literal(b, row) + if i < valueLen-1 { + b.WriteRunes(d.dialectOptions.CommaRune, d.dialectOptions.SpaceRune) + } + } +} + +// Adds the DefaultValuesFragment to an SQL statement +func (d *sqlDialect) onConflictSQL(b sb.SQLBuilder, o exp.ConflictExpression) { + if o == nil { + return + } + b.Write(d.dialectOptions.ConflictFragment) + switch t := o.(type) { + case exp.ConflictUpdateExpression: + target := t.TargetColumn() + if d.dialectOptions.SupportsConflictTarget && target != "" { + wrapParens := !strings.HasPrefix(strings.ToLower(target), "on constraint") + + b.WriteRunes(d.dialectOptions.SpaceRune) + if wrapParens { + b.WriteRunes(d.dialectOptions.LeftParenRune). + WriteStrings(target). + WriteRunes(d.dialectOptions.RightParenRune) + } else { + b.Write([]byte(target)) + } + } + d.onConflictDoUpdateSQL(b, t) + default: + b.Write(d.dialectOptions.ConflictDoNothingFragment) + } +} + +// Adds column setters in an update SET clause +func (d *sqlDialect) updateValuesSQL(b sb.SQLBuilder, updates ...exp.UpdateExpression) { + if len(updates) == 0 { + b.SetError(errNoUpdatedValuesProvided) + return + } + updateLen := len(updates) + for i, update := range updates { + d.Literal(b, update) + if i < updateLen-1 { + b.WriteRunes(d.dialectOptions.CommaRune) + } + } +} + +func (d *sqlDialect) onConflictDoUpdateSQL(b sb.SQLBuilder, o exp.ConflictUpdateExpression) { + b.Write(d.dialectOptions.ConflictDoUpdateFragment) + update := o.Update() + if update == nil { + b.SetError(errConflictUpdateValuesRequired) + return + } + ue, err := exp.NewUpdateExpressions(update) + if err != nil { + b.SetError(err) + return + } + d.updateValuesSQL(b, ue...) + if o.WhereClause() != nil { + if !d.dialectOptions.SupportsConflictUpdateWhere { + b.SetError(errUpsertWithWhereNotSupported) + return + } + d.WhereSQL(b, o.WhereClause()) + } +} + +func (d *sqlDialect) joinConditionSQL(b sb.SQLBuilder, jc exp.JoinCondition) { + switch t := jc.(type) { + case exp.JoinOnCondition: + d.joinOnConditionSQL(b, t) + case exp.JoinUsingCondition: + d.joinUsingConditionSQL(b, t) + } +} + +func (d *sqlDialect) joinUsingConditionSQL(b sb.SQLBuilder, jc exp.JoinUsingCondition) { + b.Write(d.dialectOptions.UsingFragment). + WriteRunes(d.dialectOptions.LeftParenRune) + d.Literal(b, jc.Using()) + b.WriteRunes(d.dialectOptions.RightParenRune) +} + +func (d *sqlDialect) joinOnConditionSQL(b sb.SQLBuilder, jc exp.JoinOnCondition) { + b.Write(d.dialectOptions.OnFragment) + d.Literal(b, jc.On()) +} + +func (d *sqlDialect) reflectSQL(b sb.SQLBuilder, val interface{}) { + v := reflect.Indirect(reflect.ValueOf(val)) + valKind := v.Kind() + switch { + case util.IsInvalid(valKind): + d.literalNil(b) + case util.IsSlice(valKind): + if bs, ok := val.([]byte); ok { + d.Literal(b, bs) + return + } + d.sliceValueSQL(b, v) + case util.IsInt(valKind): + d.Literal(b, v.Int()) + case util.IsUint(valKind): + d.Literal(b, int64(v.Uint())) + case util.IsFloat(valKind): + d.Literal(b, v.Float()) + case util.IsString(valKind): + d.Literal(b, v.String()) + case util.IsBool(valKind): + d.Literal(b, v.Bool()) + default: + b.SetError(errors.NewEncodeError(val)) + } +} + +func (d *sqlDialect) expressionSQL(b sb.SQLBuilder, expression exp.Expression) { + switch e := expression.(type) { + case exp.ColumnListExpression: + d.columnListSQL(b, e) + case exp.ExpressionList: + d.expressionListSQL(b, e) + case exp.LiteralExpression: + d.literalExpressionSQL(b, e) + case exp.IdentifierExpression: + d.quoteIdentifier(b, e) + case exp.AliasedExpression: + d.aliasedExpressionSQL(b, e) + case exp.BooleanExpression: + d.booleanExpressionSQL(b, e) + case exp.RangeExpression: + d.rangeExpressionSQL(b, e) + case exp.OrderedExpression: + d.orderedExpressionSQL(b, e) + case exp.UpdateExpression: + d.updateExpressionSQL(b, e) + case exp.SQLFunctionExpression: + d.sqlFunctionExpressionSQL(b, e) + case exp.CastExpression: + d.castExpressionSQL(b, e) + case exp.AppendableExpression: + d.appendableExpressionSQL(b, e) + case exp.CommonTableExpression: + d.commonTableExpressionSQL(b, e) + case exp.CompoundExpression: + d.compoundExpressionSQL(b, e) + case exp.Ex: + d.expressionMapSQL(b, e) + case exp.ExOr: + d.expressionOrMapSQL(b, e) + default: + b.SetError(unsupportedExpressionTypeErr(e)) + } +} + +// Generates a placeholder (e.g. ?, $1) +func (d *sqlDialect) placeHolderSQL(b sb.SQLBuilder, i interface{}) { + b.WriteRunes(d.dialectOptions.PlaceHolderRune) + if d.dialectOptions.IncludePlaceholderNum { + b.WriteStrings(strconv.FormatInt(int64(b.CurrentArgPosition()), 10)) + } + b.WriteArg(i) +} + +// Generates creates the sql for a sub select on a Dataset +func (d *sqlDialect) appendableExpressionSQL(b sb.SQLBuilder, a exp.AppendableExpression) { + b.WriteRunes(d.dialectOptions.LeftParenRune) + a.AppendSQL(b) + b.WriteRunes(d.dialectOptions.RightParenRune) + c := a.GetClauses() + if c != nil { + alias := c.Alias() + if alias != nil { + b.Write(d.dialectOptions.AsFragment) + d.Literal(b, alias) + } + } +} + +// Quotes an identifier (e.g. "col", "table"."col" +func (d *sqlDialect) quoteIdentifier(b sb.SQLBuilder, ident exp.IdentifierExpression) { + schema, table, col := ident.GetSchema(), ident.GetTable(), ident.GetCol() + if schema != d.dialectOptions.EmptyString { + b.WriteRunes(d.dialectOptions.QuoteRune). + WriteStrings(schema). + WriteRunes(d.dialectOptions.QuoteRune) + } + if table != d.dialectOptions.EmptyString { + if schema != d.dialectOptions.EmptyString { + b.WriteRunes(d.dialectOptions.PeriodRune) + } + b.WriteRunes(d.dialectOptions.QuoteRune). + WriteStrings(table). + WriteRunes(d.dialectOptions.QuoteRune) + } + switch t := col.(type) { + case nil: + case string: + if col != d.dialectOptions.EmptyString { + if table != d.dialectOptions.EmptyString || schema != d.dialectOptions.EmptyString { + b.WriteRunes(d.dialectOptions.PeriodRune) + } + b.WriteRunes(d.dialectOptions.QuoteRune). + WriteStrings(t). + WriteRunes(d.dialectOptions.QuoteRune) + } + case exp.LiteralExpression: + if table != d.dialectOptions.EmptyString || schema != d.dialectOptions.EmptyString { + b.WriteRunes(d.dialectOptions.PeriodRune) + } + d.Literal(b, t) + default: + b.SetError(unsupportedIdentifierExpressionErr(col)) + } +} + +// Generates SQL NULL value +func (d *sqlDialect) literalNil(b sb.SQLBuilder) { + b.Write(d.dialectOptions.Null) +} + +// Generates SQL bool literal, (e.g. TRUE, FALSE, mysql 1, 0, sqlite3 1, 0) +func (d *sqlDialect) literalBool(b sb.SQLBuilder, bl bool) { + if b.IsPrepared() { + d.placeHolderSQL(b, bl) + return + } + if bl { + b.Write(d.dialectOptions.True) + } else { + b.Write(d.dialectOptions.False) + } +} + +// Generates SQL for a time.Time value +func (d *sqlDialect) literalTime(b sb.SQLBuilder, t time.Time) { + if b.IsPrepared() { + d.placeHolderSQL(b, t) + return + } + d.Literal(b, t.UTC().Format(d.dialectOptions.TimeFormat)) +} + +// Generates SQL for a Float Value +func (d *sqlDialect) literalFloat(b sb.SQLBuilder, f float64) { + if b.IsPrepared() { + d.placeHolderSQL(b, f) + return + } + b.WriteStrings(strconv.FormatFloat(f, 'f', -1, 64)) +} + +// Generates SQL for an int value +func (d *sqlDialect) literalInt(b sb.SQLBuilder, i int64) { + if b.IsPrepared() { + d.placeHolderSQL(b, i) + return + } + b.WriteStrings(strconv.FormatInt(i, 10)) +} + +// Generates SQL for a string +func (d *sqlDialect) literalString(b sb.SQLBuilder, s string) { + if b.IsPrepared() { + d.placeHolderSQL(b, s) + return + } + b.WriteRunes(d.dialectOptions.StringQuote) + for _, char := range s { + if e, ok := d.dialectOptions.EscapedRunes[char]; ok { + b.Write(e) + } else { + b.WriteRunes(char) + } + } + + b.WriteRunes(d.dialectOptions.StringQuote) +} + +// Generates SQL for a slice of bytes +func (d *sqlDialect) literalBytes(b sb.SQLBuilder, bs []byte) { + if b.IsPrepared() { + d.placeHolderSQL(b, bs) + return + } + b.WriteRunes(d.dialectOptions.StringQuote) + i := 0 + for len(bs) > 0 { + char, l := utf8.DecodeRune(bs) + if e, ok := d.dialectOptions.EscapedRunes[char]; ok { + b.Write(e) + } else { + b.WriteRunes(char) + } + i++ + bs = bs[l:] + } + b.WriteRunes(d.dialectOptions.StringQuote) +} + +// Generates SQL for a slice of values (e.g. []int64{1,2,3,4} -> (1,2,3,4) +func (d *sqlDialect) sliceValueSQL(b sb.SQLBuilder, slice reflect.Value) { + b.WriteRunes(d.dialectOptions.LeftParenRune) + for i, l := 0, slice.Len(); i < l; i++ { + d.Literal(b, slice.Index(i).Interface()) + if i < l-1 { + b.WriteRunes(d.dialectOptions.CommaRune, d.dialectOptions.SpaceRune) + } + } + b.WriteRunes(d.dialectOptions.RightParenRune) +} + +// Generates SQL for an AliasedExpression (e.g. I("a").As("b") -> "a" AS "b") +func (d *sqlDialect) aliasedExpressionSQL(b sb.SQLBuilder, aliased exp.AliasedExpression) { + d.Literal(b, aliased.Aliased()) + b.Write(d.dialectOptions.AsFragment) + d.Literal(b, aliased.GetAs()) +} + +// Generates SQL for a BooleanExpresion (e.g. I("a").Eq(2) -> "a" = 2) +func (d *sqlDialect) booleanExpressionSQL(b sb.SQLBuilder, operator exp.BooleanExpression) { + b.WriteRunes(d.dialectOptions.LeftParenRune) + d.Literal(b, operator.LHS()) + b.WriteRunes(d.dialectOptions.SpaceRune) + operatorOp := operator.Op() + if operator.RHS() == nil { + switch operatorOp { + case exp.EqOp: + operatorOp = exp.IsOp + case exp.NeqOp: + operatorOp = exp.IsNotOp + } + } + if val, ok := d.dialectOptions.BooleanOperatorLookup[operatorOp]; ok { + b.Write(val) + } else { + b.SetError(unsupportedBooleanExpressionOperator(operatorOp)) + return + } + rhs := operator.RHS() + if (operatorOp == exp.IsOp || operatorOp == exp.IsNotOp) && d.dialectOptions.UseLiteralIsBools { + if rhs == true { + rhs = TrueLiteral + } else if rhs == false { + rhs = FalseLiteral + } + } + b.WriteRunes(d.dialectOptions.SpaceRune) + d.Literal(b, rhs) + b.WriteRunes(d.dialectOptions.RightParenRune) +} + +// Generates SQL for a RangeExpresion (e.g. I("a").Between(RangeVal{Start:2,End:5}) -> "a" BETWEEN 2 AND 5) +func (d *sqlDialect) rangeExpressionSQL(b sb.SQLBuilder, operator exp.RangeExpression) { + b.WriteRunes(d.dialectOptions.LeftParenRune) + d.Literal(b, operator.LHS()) + b.WriteRunes(d.dialectOptions.SpaceRune) + operatorOp := operator.Op() + if val, ok := d.dialectOptions.RangeOperatorLookup[operatorOp]; ok { + b.Write(val) + } else { + b.SetError(unsupportedRangeExpressionOperator(operatorOp)) + return + } + rhs := operator.RHS() + b.WriteRunes(d.dialectOptions.SpaceRune) + d.Literal(b, rhs.Start()) + b.Write(d.dialectOptions.AndFragment) + d.Literal(b, rhs.End()) + b.WriteRunes(d.dialectOptions.RightParenRune) +} + +// Generates SQL for an OrderedExpression (e.g. I("a").Asc() -> "a" ASC) +func (d *sqlDialect) orderedExpressionSQL(b sb.SQLBuilder, order exp.OrderedExpression) { + d.Literal(b, order.SortExpression()) + if order.IsAsc() { + b.Write(d.dialectOptions.AscFragment) + } else { + b.Write(d.dialectOptions.DescFragment) + } + switch order.NullSortType() { + case exp.NullsFirstSortType: + b.Write(d.dialectOptions.NullsFirstFragment) + case exp.NullsLastSortType: + b.Write(d.dialectOptions.NullsLastFragment) + } +} + +// Generates SQL for an ExpressionList (e.g. And(I("a").Eq("a"), I("b").Eq("b")) -> (("a" = 'a') AND ("b" = 'b'))) +func (d *sqlDialect) expressionListSQL(b sb.SQLBuilder, expressionList exp.ExpressionList) { + var op []byte + if expressionList.Type() == exp.AndType { + op = d.dialectOptions.AndFragment + } else { + op = d.dialectOptions.OrFragment + } + exps := expressionList.Expressions() + expLen := len(exps) - 1 + needsAppending := expLen > 0 + if needsAppending { + b.WriteRunes(d.dialectOptions.LeftParenRune) + } else { + d.Literal(b, exps[0]) + return + } + for i, e := range exps { + d.Literal(b, e) + if i < expLen { + b.Write(op) + } + } + b.WriteRunes(d.dialectOptions.RightParenRune) +} + +// Generates SQL for a ColumnListExpression +func (d *sqlDialect) columnListSQL(b sb.SQLBuilder, columnList exp.ColumnListExpression) { + cols := columnList.Columns() + colLen := len(cols) + for i, col := range cols { + d.Literal(b, col) + if i < colLen-1 { + b.WriteRunes(d.dialectOptions.CommaRune, d.dialectOptions.SpaceRune) + } + } +} + +// Generates SQL for an UpdateEpxresion +func (d *sqlDialect) updateExpressionSQL(b sb.SQLBuilder, update exp.UpdateExpression) { + d.Literal(b, update.Col()) + b.WriteRunes(d.dialectOptions.SetOperatorRune) + d.Literal(b, update.Val()) +} + +// Generates SQL for a LiteralExpression +// L("a + b") -> a + b +// L("a = ?", 1) -> a = 1 +func (d *sqlDialect) literalExpressionSQL(b sb.SQLBuilder, literal exp.LiteralExpression) { + lit := literal.Literal() + args := literal.Args() + argsLen := len(args) + if argsLen > 0 { + currIndex := 0 + for _, char := range lit { + if char == replacementRune && currIndex < argsLen { + d.Literal(b, args[currIndex]) + currIndex++ + } else { + b.WriteRunes(char) + } + } + } else { + b.WriteStrings(lit) + } +} + +// Generates SQL for a SQLFunctionExpression +// COUNT(I("a")) -> COUNT("a") +func (d *sqlDialect) sqlFunctionExpressionSQL(b sb.SQLBuilder, sqlFunc exp.SQLFunctionExpression) { + b.WriteStrings(sqlFunc.Name()) + d.Literal(b, sqlFunc.Args()) +} + +// Generates SQL for a CastExpression +// I("a").Cast("NUMERIC") -> CAST("a" AS NUMERIC) +func (d *sqlDialect) castExpressionSQL(b sb.SQLBuilder, cast exp.CastExpression) { + b.Write(d.dialectOptions.CastFragment).WriteRunes(d.dialectOptions.LeftParenRune) + d.Literal(b, cast.Casted()) + b.Write(d.dialectOptions.AsFragment) + d.Literal(b, cast.Type()) + b.WriteRunes(d.dialectOptions.RightParenRune) +} + +// Generates SQL for a CommonTableExpression +func (d *sqlDialect) commonTableExpressionSQL(b sb.SQLBuilder, cte exp.CommonTableExpression) { + d.Literal(b, cte.Name()) + b.Write(d.dialectOptions.AsFragment) + d.Literal(b, cte.SubQuery()) +} + +// Generates SQL for a CompoundExpression +func (d *sqlDialect) compoundExpressionSQL(b sb.SQLBuilder, compound exp.CompoundExpression) { + switch compound.Type() { + case exp.UnionCompoundType: + b.Write(d.dialectOptions.UnionFragment) + case exp.UnionAllCompoundType: + b.Write(d.dialectOptions.UnionAllFragment) + case exp.IntersectCompoundType: + b.Write(d.dialectOptions.IntersectFragment) + case exp.IntersectAllCompoundType: + b.Write(d.dialectOptions.IntersectAllFragment) + } + d.Literal(b, compound.RHS()) +} + +func (d *sqlDialect) expressionMapSQL(b sb.SQLBuilder, ex exp.Ex) { + expressionList, err := ex.ToExpressions() + if err != nil { + b.SetError(err) + return + } + d.Literal(b, expressionList) +} + +func (d *sqlDialect) expressionOrMapSQL(b sb.SQLBuilder, ex exp.ExOr) { + expressionList, err := ex.ToExpressions() + if err != nil { + b.SetError(err) + return + } + d.Literal(b, expressionList) +} diff --git a/sql_dialect_example_test.go b/sql_dialect_example_test.go new file mode 100644 index 00000000..6f95b029 --- /dev/null +++ b/sql_dialect_example_test.go @@ -0,0 +1,23 @@ +package goqu_test + +import ( + "fmt" + + "github.com/doug-martin/goqu/v7" +) + +func ExampleRegisterDialect() { + opts := goqu.DefaultDialectOptions() + opts.QuoteRune = '`' + goqu.RegisterDialect("custom-dialect", opts) + + dialect := goqu.Dialect("custom-dialect") + + ds := dialect.From("test") + + sql, args, _ := ds.ToSQL() + fmt.Println(sql, args) + + // Output: + // SELECT * FROM `test` [] +} diff --git a/sql_dialect_options.go b/sql_dialect_options.go new file mode 100644 index 00000000..6597b115 --- /dev/null +++ b/sql_dialect_options.go @@ -0,0 +1,507 @@ +package goqu + +import ( + "fmt" + "time" + + "github.com/doug-martin/goqu/v7/exp" +) + +type ( + SQLFragmentType int + SQLDialectOptions struct { + // Set to true if the dialect supports ORDER BY expressions in DELETE statements (DEFAULT=false) + SupportsOrderByOnDelete bool + // Set to true if the dialect supports ORDER BY expressions in UPDATE statements (DEFAULT=false) + SupportsOrderByOnUpdate bool + // Set to true if the dialect supports LIMIT expressions in DELETE statements (DEFAULT=false) + SupportsLimitOnDelete bool + // Set to true if the dialect supports LIMIT expressions in UPDATE statements (DEFAULT=false) + SupportsLimitOnUpdate bool + // Set to true if the dialect supports RETURN expressions (DEFAULT=true) + SupportsReturn bool + // Set to true if the dialect supports Conflict Target (DEFAULT=true) + SupportsConflictTarget bool + // Set to true if the dialect supports Conflict Target (DEFAULT=true) + SupportsConflictUpdateWhere bool + // Set to true if the dialect supports Insert Ignore syntax (DEFAULT=false) + SupportsInsertIgnoreSyntax bool + // Set to true if the dialect supports Common Table Expressions (DEFAULT=true) + SupportsWithCTE bool + // Set to true if the dialect supports recursive Common Table Expressions (DEFAULT=true) + SupportsWithCTERecursive bool + + // The UPDATE fragment to use when generating sql. (DEFAULT=[]byte("UPDATE")) + UpdateClause []byte + // The INSERT fragment to use when generating sql. (DEFAULT=[]byte("INSERT INTO")) + InsertClause []byte + // The INSERT IGNORE INTO fragment to use when generating sql. (DEFAULT=[]byte("INSERT IGNORE INTO")) + InsertIgnoreClause []byte + // The SELECT fragment to use when generating sql. (DEFAULT=[]byte("SELECT")) + SelectClause []byte + // The DELETE fragment to use when generating sql. (DEFAULT=[]byte("DELETE")) + DeleteClause []byte + // The TRUNCATE fragment to use when generating sql. (DEFAULT=[]byte("TRUNCATE")) + TruncateClause []byte + // The WITH fragment to use when generating sql. (DEFAULT=[]byte("WITH ")) + WithFragment []byte + // The RECURSIVE fragment to use when generating sql (after WITH). (DEFAULT=[]byte("RECURSIVE ")) + RecursiveFragment []byte + // The CASCADE fragment to use when generating sql. (DEFAULT=[]byte(" CASCADE")) + CascadeFragment []byte + // The RESTRICT fragment to use when generating sql. (DEFAULT=[]byte(" RESTRICT")) + RestrictFragment []byte + // The SQL fragment to use when generating insert sql and using + // DEFAULT VALUES (e.g. postgres="DEFAULT VALUES", mysql="", sqlite3=""). (DEFAULT=[]byte(" DEFAULT VALUES")) + DefaultValuesFragment []byte + // The SQL fragment to use when generating insert sql and listing columns using a VALUES clause + // (DEFAULT=[]byte(" VALUES ")) + ValuesFragment []byte + // The SQL fragment to use when generating truncate sql and using the IDENTITY clause + // (DEFAULT=[]byte(" IDENTITY")) + IdentityFragment []byte + // The SQL fragment to use when generating update sql and using the SET clause (DEFAULT=[]byte(" SET ")) + SetFragment []byte + // The SQL DISTINCT keyword (DEFAULT=[]byte(" DISTINCT ")) + DistinctFragment []byte + // The SQL RETURNING clause (DEFAULT=[]byte(" RETURNING ")) + ReturningFragment []byte + // The SQL FROM clause fragment (DEFAULT=[]byte(" FROM")) + FromFragment []byte + // The SQL USING join clause fragment (DEFAULT=[]byte(" USING ")) + UsingFragment []byte + // The SQL ON join clause fragment (DEFAULT=[]byte(" ON ")) + OnFragment []byte + // The SQL WHERE clause fragment (DEFAULT=[]byte(" WHERE ")) + WhereFragment []byte + // The SQL GROUP BY clause fragment(DEFAULT=[]byte(" GROUP BY ")) + GroupByFragment []byte + // The SQL HAVING clause fragment(DELiFAULT=[]byte(" HAVING ")) + HavingFragment []byte + // The SQL ORDER BY clause fragment(DEFAULT=[]byte(" ORDER BY ")) + OrderByFragment []byte + // The SQL LIMIT BY clause fragment(DEFAULT=[]byte(" LIMIT ")) + LimitFragment []byte + // The SQL OFFSET BY clause fragment(DEFAULT=[]byte(" OFFSET ")) + OffsetFragment []byte + // The SQL FOR UPDATE fragment(DEFAULT=[]byte(" FOR UPDATE ")) + ForUpdateFragment []byte + // The SQL FOR NO KEY UPDATE fragment(DEFAULT=[]byte(" FOR NO KEY UPDATE ")) + ForNoKeyUpdateFragment []byte + // The SQL FOR SHARE fragment(DEFAULT=[]byte(" FOR SHARE ")) + ForShareFragment []byte + // The SQL FOR KEY SHARE fragment(DEFAULT=[]byte(" FOR KEY SHARE ")) + ForKeyShareFragment []byte + // The SQL NOWAIT fragment(DEFAULT=[]byte("NOWAIT")) + NowaitFragment []byte + // The SQL SKIP LOCKED fragment(DEFAULT=[]byte("SKIP LOCKED")) + SkipLockedFragment []byte + // The SQL AS fragment when aliasing an Expression(DEFAULT=[]byte(" AS ")) + AsFragment []byte + // The quote rune to use when quoting identifiers(DEFAULT='"') + QuoteRune rune + // The NULL literal to use when interpolating nulls values (DEFAULT=[]byte("NULL")) + Null []byte + // The TRUE literal to use when interpolating bool true values (DEFAULT=[]byte("TRUE")) + True []byte + // The FALSE literal to use when interpolating bool false values (DEFAULT=[]byte("FALSE")) + False []byte + // The ASC fragment when specifying column order (DEFAULT=[]byte(" ASC")) + AscFragment []byte + // The DESC fragment when specifying column order (DEFAULT=[]byte(" DESC")) + DescFragment []byte + // The NULLS FIRST fragment when specifying column order (DEFAULT=[]byte(" NULLS FIRST")) + NullsFirstFragment []byte + // The NULLS LAST fragment when specifying column order (DEFAULT=[]byte(" NULLS LAST")) + NullsLastFragment []byte + // The AND keyword used when joining ExpressionLists (DEFAULT=[]byte(" AND ")) + AndFragment []byte + // The OR keyword used when joining ExpressionLists (DEFAULT=[]byte(" OR ")) + OrFragment []byte + // The UNION keyword used when creating compound statements (DEFAULT=[]byte(" UNION ")) + UnionFragment []byte + // The UNION ALL keyword used when creating compound statements (DEFAULT=[]byte(" UNION ALL ")) + UnionAllFragment []byte + // The INTERSECT keyword used when creating compound statements (DEFAULT=[]byte(" INTERSECT ")) + IntersectFragment []byte + // The INTERSECT ALL keyword used when creating compound statements (DEFAULT=[]byte(" INTERSECT ALL ")) + IntersectAllFragment []byte + // The CAST keyword to use when casting a value (DEFAULT=[]byte("CAST")) + CastFragment []byte + // The quote rune to use when quoting string literals (DEFAULT='\'') + StringQuote rune + // The operator to use when setting values in an update statement (DEFAULT='=') + SetOperatorRune rune + // The placeholder rune to use when generating a non interpolated statement (DEFAULT='?') + PlaceHolderRune rune + // Empty string (DEFAULT="") + EmptyString string + // Comma rune (DEFAULT=',') + CommaRune rune + // Space rune (DEFAULT=' ') + SpaceRune rune + // Left paren rune (DEFAULT='(') + LeftParenRune rune + // Right paren rune (DEFAULT=')') + RightParenRune rune + // Star rune (DEFAULT='*') + StarRune rune + // Period rune (DEFAULT='.') + PeriodRune rune + // Set to true to include positional argument numbers when creating a prepared statement (Default=false) + IncludePlaceholderNum bool + // The time format to use when serializing time.Time (DEFAULT=time.RFC3339Nano) + TimeFormat string + // A map used to look up BooleanOperations and their SQL equivalents + // (Default= map[exp.BooleanOperation][]byte{ + // exp.EqOp: []byte("="), + // exp.NeqOp: []byte("!="), + // exp.GtOp: []byte(">"), + // exp.GteOp: []byte(">="), + // exp.LtOp: []byte("<"), + // exp.LteOp: []byte("<="), + // exp.InOp: []byte("IN"), + // exp.NotInOp: []byte("NOT IN"), + // exp.IsOp: []byte("IS"), + // exp.IsNotOp: []byte("IS NOT"), + // exp.LikeOp: []byte("LIKE"), + // exp.NotLikeOp: []byte("NOT LIKE"), + // exp.ILikeOp: []byte("ILIKE"), + // exp.NotILikeOp: []byte("NOT ILIKE"), + // exp.RegexpLikeOp: []byte("~"), + // exp.RegexpNotLikeOp: []byte("!~"), + // exp.RegexpILikeOp: []byte("~*"), + // exp.RegexpNotILikeOp: []byte("!~*"), + // }) + BooleanOperatorLookup map[exp.BooleanOperation][]byte + // A map used to look up RangeOperations and their SQL equivalents + // (Default=map[exp.RangeOperation][]byte{ + // exp.BetweenOp: []byte("BETWEEN"), + // exp.NotBetweenOp: []byte("NOT BETWEEN"), + // }) + RangeOperatorLookup map[exp.RangeOperation][]byte + // A map used to look up JoinTypes and their SQL equivalents + // (Default= map[exp.JoinType][]byte{ + // exp.InnerJoinType: []byte(" INNER JOIN "), + // exp.FullOuterJoinType: []byte(" FULL OUTER JOIN "), + // exp.RightOuterJoinType: []byte(" RIGHT OUTER JOIN "), + // exp.LeftOuterJoinType: []byte(" LEFT OUTER JOIN "), + // exp.FullJoinType: []byte(" FULL JOIN "), + // exp.RightJoinType: []byte(" RIGHT JOIN "), + // exp.LeftJoinType: []byte(" LEFT JOIN "), + // exp.NaturalJoinType: []byte(" NATURAL JOIN "), + // exp.NaturalLeftJoinType: []byte(" NATURAL LEFT JOIN "), + // exp.NaturalRightJoinType: []byte(" NATURAL RIGHT JOIN "), + // exp.NaturalFullJoinType: []byte(" NATURAL FULL JOIN "), + // exp.CrossJoinType: []byte(" CROSS JOIN "), + // }) + JoinTypeLookup map[exp.JoinType][]byte + // Whether or not to use literal TRUE or FALSE for IS statements (e.g. IS TRUE or IS 0) + UseLiteralIsBools bool + // EscapedRunes is a map of a rune and the corresponding escape sequence in bytes. Used when escaping text + // types. + // (Default= map[rune][]byte{ + // '\'': []byte("''"), + // }) + EscapedRunes map[rune][]byte + + // The SQL fragment to use for CONFLICT (Default=[]byte(" ON CONFLICT")) + ConflictFragment []byte + // The SQL fragment to use for CONFLICT DO NOTHING (Default=[]byte(" DO NOTHING")) + ConflictDoNothingFragment []byte + // The SQL fragment to use for CONFLICT DO UPDATE (Default=[]byte(" DO UPDATE SET")) + ConflictDoUpdateFragment []byte + + // The order of SQL fragments when creating a SELECT statement + // (Default=[]SQLFragmentType{ + // CommonTableSQLFragment, + // SelectSQLFragment, + // FromSQLFragment, + // JoinSQLFragment, + // WhereSQLFragment, + // GroupBySQLFragment, + // HavingSQLFragment, + // CompoundsSQLFragment, + // OrderSQLFragment, + // LimitSQLFragment, + // OffsetSQLFragment, + // ForSQLFragment, + // }) + SelectSQLOrder []SQLFragmentType + + // The order of SQL fragments when creating an UPDATE statement + // (Default=[]SQLFragmentType{ + // CommonTableSQLFragment, + // UpdateBeginSQLFragment, + // SourcesSQLFragment, + // UpdateSQLFragment, + // WhereSQLFragment, + // OrderSQLFragment, + // LimitSQLFragment, + // ReturningSQLFragment, + // }) + UpdateSQLOrder []SQLFragmentType + + // The order of SQL fragments when creating an INSERT statement + // (Default=[]SQLFragmentType{ + // CommonTableSQLFragment, + // InsertBeingSQLFragment, + // SourcesSQLFragment, + // InsertSQLFragment, + // ReturningSQLFragment, + // }) + InsertSQLOrder []SQLFragmentType + + // The order of SQL fragments when creating a DELETE statement + // (Default=[]SQLFragmentType{ + // CommonTableSQLFragment, + // DeleteBeginSQLFragment, + // FromSQLFragment, + // WhereSQLFragment, + // OrderSQLFragment, + // LimitSQLFragment, + // ReturningSQLFragment, + // }) + DeleteSQLOrder []SQLFragmentType + + // The order of SQL fragments when creating a TRUNCATE statement + // (Default=[]SQLFragmentType{ + // TruncateSQLFragment, + // }) + TruncateSQLOrder []SQLFragmentType + } +) + +const ( + CommonTableSQLFragment = iota + SelectSQLFragment + FromSQLFragment + JoinSQLFragment + WhereSQLFragment + GroupBySQLFragment + HavingSQLFragment + CompoundsSQLFragment + OrderSQLFragment + LimitSQLFragment + OffsetSQLFragment + ForSQLFragment + UpdateBeginSQLFragment + SourcesSQLFragment + UpdateSQLFragment + ReturningSQLFragment + InsertBeingSQLFragment + InsertSQLFragment + DeleteBeginSQLFragment + TruncateSQLFragment +) + +func (sf SQLFragmentType) String() string { + switch sf { + case CommonTableSQLFragment: + return "CommonTableSQLFragment" + case SelectSQLFragment: + return "SelectSQLFragment" + case FromSQLFragment: + return "FromSQLFragment" + case JoinSQLFragment: + return "JoinSQLFragment" + case WhereSQLFragment: + return "WhereSQLFragment" + case GroupBySQLFragment: + return "GroupBySQLFragment" + case HavingSQLFragment: + return "HavingSQLFragment" + case CompoundsSQLFragment: + return "CompoundsSQLFragment" + case OrderSQLFragment: + return "OrderSQLFragment" + case LimitSQLFragment: + return "LimitSQLFragment" + case OffsetSQLFragment: + return "OffsetSQLFragment" + case ForSQLFragment: + return "ForSQLFragment" + case UpdateBeginSQLFragment: + return "UpdateBeginSQLFragment" + case SourcesSQLFragment: + return "SourcesSQLFragment" + case UpdateSQLFragment: + return "UpdateSQLFragment" + case ReturningSQLFragment: + return "ReturningSQLFragment" + case InsertBeingSQLFragment: + return "InsertBeingSQLFragment" + case DeleteBeginSQLFragment: + return "DeleteBeginSQLFragment" + case TruncateSQLFragment: + return "TruncateSQLFragment" + } + return fmt.Sprintf("%d", sf) +} + +func DefaultDialectOptions() *SQLDialectOptions { + return &SQLDialectOptions{ + SupportsOrderByOnDelete: false, + SupportsOrderByOnUpdate: false, + SupportsLimitOnDelete: false, + SupportsLimitOnUpdate: false, + SupportsReturn: true, + SupportsConflictUpdateWhere: true, + SupportsInsertIgnoreSyntax: false, + SupportsConflictTarget: true, + SupportsWithCTE: true, + SupportsWithCTERecursive: true, + + UpdateClause: []byte("UPDATE"), + InsertClause: []byte("INSERT INTO"), + InsertIgnoreClause: []byte("INSERT IGNORE INTO"), + SelectClause: []byte("SELECT"), + DeleteClause: []byte("DELETE"), + TruncateClause: []byte("TRUNCATE"), + WithFragment: []byte("WITH "), + RecursiveFragment: []byte("RECURSIVE "), + CascadeFragment: []byte(" CASCADE"), + RestrictFragment: []byte(" RESTRICT"), + DefaultValuesFragment: []byte(" DEFAULT VALUES"), + ValuesFragment: []byte(" VALUES "), + IdentityFragment: []byte(" IDENTITY"), + SetFragment: []byte(" SET "), + DistinctFragment: []byte(" DISTINCT "), + ReturningFragment: []byte(" RETURNING "), + FromFragment: []byte(" FROM"), + UsingFragment: []byte(" USING "), + OnFragment: []byte(" ON "), + WhereFragment: []byte(" WHERE "), + GroupByFragment: []byte(" GROUP BY "), + HavingFragment: []byte(" HAVING "), + OrderByFragment: []byte(" ORDER BY "), + LimitFragment: []byte(" LIMIT "), + OffsetFragment: []byte(" OFFSET "), + ForUpdateFragment: []byte(" FOR UPDATE "), + ForNoKeyUpdateFragment: []byte(" FOR NO KEY UPDATE "), + ForShareFragment: []byte(" FOR SHARE "), + ForKeyShareFragment: []byte(" FOR KEY SHARE "), + NowaitFragment: []byte("NOWAIT"), + SkipLockedFragment: []byte("SKIP LOCKED"), + AsFragment: []byte(" AS "), + AscFragment: []byte(" ASC"), + DescFragment: []byte(" DESC"), + NullsFirstFragment: []byte(" NULLS FIRST"), + NullsLastFragment: []byte(" NULLS LAST"), + AndFragment: []byte(" AND "), + OrFragment: []byte(" OR "), + UnionFragment: []byte(" UNION "), + UnionAllFragment: []byte(" UNION ALL "), + IntersectFragment: []byte(" INTERSECT "), + IntersectAllFragment: []byte(" INTERSECT ALL "), + ConflictFragment: []byte(" ON CONFLICT"), + ConflictDoUpdateFragment: []byte(" DO UPDATE SET "), + ConflictDoNothingFragment: []byte(" DO NOTHING"), + CastFragment: []byte("CAST"), + Null: []byte("NULL"), + True: []byte("TRUE"), + False: []byte("FALSE"), + + PlaceHolderRune: '?', + QuoteRune: '"', + StringQuote: '\'', + SetOperatorRune: '=', + CommaRune: ',', + SpaceRune: ' ', + LeftParenRune: '(', + RightParenRune: ')', + StarRune: '*', + PeriodRune: '.', + EmptyString: "", + + BooleanOperatorLookup: map[exp.BooleanOperation][]byte{ + exp.EqOp: []byte("="), + exp.NeqOp: []byte("!="), + exp.GtOp: []byte(">"), + exp.GteOp: []byte(">="), + exp.LtOp: []byte("<"), + exp.LteOp: []byte("<="), + exp.InOp: []byte("IN"), + exp.NotInOp: []byte("NOT IN"), + exp.IsOp: []byte("IS"), + exp.IsNotOp: []byte("IS NOT"), + exp.LikeOp: []byte("LIKE"), + exp.NotLikeOp: []byte("NOT LIKE"), + exp.ILikeOp: []byte("ILIKE"), + exp.NotILikeOp: []byte("NOT ILIKE"), + exp.RegexpLikeOp: []byte("~"), + exp.RegexpNotLikeOp: []byte("!~"), + exp.RegexpILikeOp: []byte("~*"), + exp.RegexpNotILikeOp: []byte("!~*"), + }, + RangeOperatorLookup: map[exp.RangeOperation][]byte{ + exp.BetweenOp: []byte("BETWEEN"), + exp.NotBetweenOp: []byte("NOT BETWEEN"), + }, + JoinTypeLookup: map[exp.JoinType][]byte{ + exp.InnerJoinType: []byte(" INNER JOIN "), + exp.FullOuterJoinType: []byte(" FULL OUTER JOIN "), + exp.RightOuterJoinType: []byte(" RIGHT OUTER JOIN "), + exp.LeftOuterJoinType: []byte(" LEFT OUTER JOIN "), + exp.FullJoinType: []byte(" FULL JOIN "), + exp.RightJoinType: []byte(" RIGHT JOIN "), + exp.LeftJoinType: []byte(" LEFT JOIN "), + exp.NaturalJoinType: []byte(" NATURAL JOIN "), + exp.NaturalLeftJoinType: []byte(" NATURAL LEFT JOIN "), + exp.NaturalRightJoinType: []byte(" NATURAL RIGHT JOIN "), + exp.NaturalFullJoinType: []byte(" NATURAL FULL JOIN "), + exp.CrossJoinType: []byte(" CROSS JOIN "), + }, + + TimeFormat: time.RFC3339Nano, + UseLiteralIsBools: true, + + EscapedRunes: map[rune][]byte{ + '\'': []byte("''"), + }, + + SelectSQLOrder: []SQLFragmentType{ + CommonTableSQLFragment, + SelectSQLFragment, + FromSQLFragment, + JoinSQLFragment, + WhereSQLFragment, + GroupBySQLFragment, + HavingSQLFragment, + CompoundsSQLFragment, + OrderSQLFragment, + LimitSQLFragment, + OffsetSQLFragment, + ForSQLFragment, + }, + UpdateSQLOrder: []SQLFragmentType{ + CommonTableSQLFragment, + UpdateBeginSQLFragment, + SourcesSQLFragment, + UpdateSQLFragment, + WhereSQLFragment, + OrderSQLFragment, + LimitSQLFragment, + ReturningSQLFragment, + }, + InsertSQLOrder: []SQLFragmentType{ + CommonTableSQLFragment, + InsertBeingSQLFragment, + SourcesSQLFragment, + InsertSQLFragment, + ReturningSQLFragment, + }, + DeleteSQLOrder: []SQLFragmentType{ + CommonTableSQLFragment, + DeleteBeginSQLFragment, + FromSQLFragment, + WhereSQLFragment, + OrderSQLFragment, + LimitSQLFragment, + ReturningSQLFragment, + }, + TruncateSQLOrder: []SQLFragmentType{ + TruncateSQLFragment, + }, + } +} diff --git a/sql_dialect_test.go b/sql_dialect_test.go new file mode 100644 index 00000000..a660f0e6 --- /dev/null +++ b/sql_dialect_test.go @@ -0,0 +1,1995 @@ +package goqu + +import ( + "database/sql/driver" + "fmt" + "regexp" + "testing" + "time" + + "github.com/doug-martin/goqu/v7/exp" + "github.com/doug-martin/goqu/v7/internal/errors" + "github.com/doug-martin/goqu/v7/internal/sb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +var emptyArgs = make([]interface{}, 0) + +type testAppendableExpression struct { + exp.AppendableExpression + sql string + args []interface{} + err error + clauses exp.Clauses +} + +func newTestAppendableExpression(sql string, args []interface{}, err error, clauses exp.Clauses) exp.AppendableExpression { + if clauses == nil { + clauses = exp.NewClauses() + } + return &testAppendableExpression{sql: sql, args: args, err: err, clauses: clauses} +} + +func (tae *testAppendableExpression) Expression() exp.Expression { + return tae +} + +func (tae *testAppendableExpression) GetClauses() exp.Clauses { + return tae.clauses +} + +func (tae *testAppendableExpression) Clone() exp.Expression { + return tae +} + +func (tae *testAppendableExpression) AppendSQL(b sb.SQLBuilder) { + if tae.err != nil { + b.SetError(tae.err) + return + } + b.WriteStrings(tae.sql) + if len(tae.args) > 0 { + b.WriteArg(tae.args...) + } +} + +type dialectTestSuite struct { + suite.Suite +} + +func (dts *dialectTestSuite) assertNotPreparedSQL(t *testing.T, b sb.SQLBuilder, expectedSQL string) { + actualSQL, actualArgs, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, expectedSQL, actualSQL) + assert.Empty(t, actualArgs) +} + +func (dts *dialectTestSuite) assertPreparedSQL( + t *testing.T, + b sb.SQLBuilder, + expectedSQL string, + expectedArgs []interface{}, +) { + actualSQL, actualArgs, err := b.ToSQL() + assert.NoError(t, err) + assert.Equal(t, expectedSQL, actualSQL) + assert.Equal(t, expectedArgs, actualArgs) +} + +func (dts *dialectTestSuite) assertErrorSQL(t *testing.T, b sb.SQLBuilder, errMsg string) { + actualSQL, actualArgs, err := b.ToSQL() + assert.EqualError(t, err, errMsg) + assert.Empty(t, actualSQL) + assert.Empty(t, actualArgs) +} + +func (dts *dialectTestSuite) TestSupportsReturn() { + t := dts.T() + opts := DefaultDialectOptions() + opts.SupportsReturn = true + d := sqlDialect{opts} + + opts2 := DefaultDialectOptions() + opts2.SupportsReturn = false + d2 := sqlDialect{opts2} + + assert.True(t, d.SupportsReturn()) + assert.False(t, d2.SupportsReturn()) +} + +func (dts *dialectTestSuite) TestSupportsOrderByOnUpdate() { + t := dts.T() + opts := DefaultDialectOptions() + opts.SupportsOrderByOnUpdate = true + d := sqlDialect{opts} + + opts2 := DefaultDialectOptions() + opts2.SupportsOrderByOnUpdate = false + d2 := sqlDialect{opts2} + + assert.True(t, d.SupportsOrderByOnUpdate()) + assert.False(t, d2.SupportsOrderByOnUpdate()) +} + +func (dts *dialectTestSuite) TestSupportsLimitOnUpdate() { + t := dts.T() + opts := DefaultDialectOptions() + opts.SupportsLimitOnUpdate = true + d := sqlDialect{opts} + + opts2 := DefaultDialectOptions() + opts2.SupportsLimitOnUpdate = false + d2 := sqlDialect{opts2} + + assert.True(t, d.SupportsLimitOnUpdate()) + assert.False(t, d2.SupportsLimitOnUpdate()) +} + +func (dts *dialectTestSuite) TestSupportsOrderByOnDelete() { + t := dts.T() + opts := DefaultDialectOptions() + opts.SupportsOrderByOnDelete = true + d := sqlDialect{opts} + + opts2 := DefaultDialectOptions() + opts2.SupportsOrderByOnDelete = false + d2 := sqlDialect{opts2} + + assert.True(t, d.SupportsOrderByOnDelete()) + assert.False(t, d2.SupportsOrderByOnDelete()) +} + +func (dts *dialectTestSuite) TestSupportsLimitOnDelete() { + t := dts.T() + opts := DefaultDialectOptions() + opts.SupportsLimitOnDelete = true + d := sqlDialect{opts} + + opts2 := DefaultDialectOptions() + opts2.SupportsLimitOnDelete = false + d2 := sqlDialect{opts2} + + assert.True(t, d.SupportsLimitOnDelete()) + assert.False(t, d2.SupportsLimitOnDelete()) +} + +func (dts *dialectTestSuite) TestUpdateBeginSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + d := sqlDialect{opts} + + opts2 := DefaultDialectOptions() + opts2.UpdateClause = []byte("update") + d2 := sqlDialect{opts2} + + b := sb.NewSQLBuilder(false) + d.UpdateBeginSQL(b) + dts.assertNotPreparedSQL(t, b, "UPDATE") + + d2.UpdateBeginSQL(b.Clear()) + dts.assertNotPreparedSQL(t, b, "update") + + b = sb.NewSQLBuilder(true) + d.UpdateBeginSQL(b) + dts.assertPreparedSQL(t, b, "UPDATE", emptyArgs) + + d2.UpdateBeginSQL(b.Clear()) + dts.assertPreparedSQL(t, b, "update", emptyArgs) +} + +func (dts *dialectTestSuite) TestInsertBeginSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + d := sqlDialect{opts} + + opts2 := DefaultDialectOptions() + opts2.InsertClause = []byte("insert into") + d2 := sqlDialect{opts2} + + b := sb.NewSQLBuilder(false) + d.InsertBeginSQL(b, nil) + dts.assertNotPreparedSQL(t, b, "INSERT INTO") + + d2.InsertBeginSQL(b.Clear(), nil) + dts.assertNotPreparedSQL(t, b, "insert into") + + b = sb.NewSQLBuilder(true) + d.InsertBeginSQL(b, nil) + dts.assertPreparedSQL(t, b, "INSERT INTO", emptyArgs) + + d2.InsertBeginSQL(b.Clear(), nil) + dts.assertPreparedSQL(t, b, "insert into", emptyArgs) +} + +func (dts *dialectTestSuite) TestInsertBeginSQL_WithConflictExpression() { + t := dts.T() + + opts := DefaultDialectOptions() + opts.SupportsInsertIgnoreSyntax = true + d := sqlDialect{opts} + + opts2 := DefaultDialectOptions() + opts2.SupportsInsertIgnoreSyntax = true + opts2.InsertIgnoreClause = []byte("insert ignore into") + d2 := sqlDialect{opts2} + ce := exp.NewDoNothingConflictExpression() + + b := sb.NewSQLBuilder(false) + d.InsertBeginSQL(b, ce) + dts.assertNotPreparedSQL(t, b, "INSERT IGNORE INTO") + + d2.InsertBeginSQL(b.Clear(), ce) + dts.assertNotPreparedSQL(t, b, "insert ignore into") + + b = sb.NewSQLBuilder(true) + d.InsertBeginSQL(b, ce) + dts.assertPreparedSQL(t, b, "INSERT IGNORE INTO", emptyArgs) + + d2.InsertBeginSQL(b.Clear(), ce) + dts.assertPreparedSQL(t, b, "insert ignore into", emptyArgs) +} + +func (dts *dialectTestSuite) TestDeleteBeginSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + d := sqlDialect{opts} + + opts2 := DefaultDialectOptions() + opts2.DeleteClause = []byte("delete") + d2 := sqlDialect{opts2} + + b := sb.NewSQLBuilder(false) + d.DeleteBeginSQL(b) + dts.assertNotPreparedSQL(t, b, "DELETE") + + d2.DeleteBeginSQL(b.Clear()) + dts.assertNotPreparedSQL(t, b, "delete") + + b = sb.NewSQLBuilder(true) + d.DeleteBeginSQL(b) + dts.assertPreparedSQL(t, b, "DELETE", emptyArgs) + + d2.DeleteBeginSQL(b.Clear()) + dts.assertPreparedSQL(t, b, "delete", emptyArgs) +} + +func (dts *dialectTestSuite) TestTruncateSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + d := sqlDialect{opts} + + opts2 := DefaultDialectOptions() + opts2.TruncateClause = []byte("truncate") + opts2.IdentityFragment = []byte(" identity") + opts2.CascadeFragment = []byte(" cascade") + opts2.RestrictFragment = []byte(" restrict") + d2 := sqlDialect{opts2} + + cols := exp.NewColumnListExpression("a") + b := sb.NewSQLBuilder(false) + d.TruncateSQL(b, cols, exp.TruncateOptions{}) + dts.assertNotPreparedSQL(t, b, `TRUNCATE "a"`) + + d2.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{}) + dts.assertNotPreparedSQL(t, b, `truncate "a"`) + + d.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{Identity: "restart"}) + dts.assertNotPreparedSQL(t, b, `TRUNCATE "a" RESTART IDENTITY`) + + d2.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{Identity: "restart"}) + dts.assertNotPreparedSQL(t, b, `truncate "a" RESTART identity`) + + d.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{Cascade: true}) + dts.assertNotPreparedSQL(t, b, `TRUNCATE "a" CASCADE`) + + d2.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{Cascade: true}) + dts.assertNotPreparedSQL(t, b, `truncate "a" cascade`) + + d.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{Restrict: true}) + dts.assertNotPreparedSQL(t, b, `TRUNCATE "a" RESTRICT`) + + d2.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{Restrict: true}) + dts.assertNotPreparedSQL(t, b, `truncate "a" restrict`) + + b = sb.NewSQLBuilder(true) + d.TruncateSQL(b, cols, exp.TruncateOptions{}) + dts.assertPreparedSQL(t, b, `TRUNCATE "a"`, emptyArgs) + + d2.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{}) + dts.assertPreparedSQL(t, b, `truncate "a"`, emptyArgs) + + d.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{Identity: "restart"}) + dts.assertPreparedSQL(t, b, `TRUNCATE "a" RESTART IDENTITY`, emptyArgs) + + d2.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{Identity: "restart"}) + dts.assertPreparedSQL(t, b, `truncate "a" RESTART identity`, emptyArgs) + + d.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{Cascade: true}) + dts.assertPreparedSQL(t, b, `TRUNCATE "a" CASCADE`, emptyArgs) + + d2.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{Cascade: true}) + dts.assertPreparedSQL(t, b, `truncate "a" cascade`, emptyArgs) + + d.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{Restrict: true}) + dts.assertPreparedSQL(t, b, `TRUNCATE "a" RESTRICT`, emptyArgs) + + d2.TruncateSQL(b.Clear(), cols, exp.TruncateOptions{Restrict: true}) + dts.assertPreparedSQL(t, b, `truncate "a" restrict`, emptyArgs) +} + +func (dts *dialectTestSuite) TestInsertSQL_empty() { + t := dts.T() + + opts := DefaultDialectOptions() + d := sqlDialect{opts} + + opts2 := DefaultDialectOptions() + opts2.DefaultValuesFragment = []byte(" default values") + d2 := sqlDialect{opts2} + + ie, err := exp.NewInsertExpression() + assert.NoError(t, err) + + b := sb.NewSQLBuilder(false) + d.InsertSQL(b, ie) + dts.assertNotPreparedSQL(t, b, " DEFAULT VALUES") + + d2.InsertSQL(b.Clear(), ie) + dts.assertNotPreparedSQL(t, b, " default values") +} + +func (dts *dialectTestSuite) TestInsertSQL_nilValues() { + t := dts.T() + + opts := DefaultDialectOptions() + d := sqlDialect{opts} + + opts2 := DefaultDialectOptions() + d2 := sqlDialect{opts2} + + ie, err := exp.NewInsertExpression() + assert.NoError(t, err) + ie = ie.SetCols(exp.NewColumnListExpression("a")). + SetVals([][]interface{}{ + {nil}, + }) + + b := sb.NewSQLBuilder(false) + d.InsertSQL(b, ie) + dts.assertNotPreparedSQL(t, b, ` ("a") VALUES (NULL)`) + + d2.InsertSQL(b.Clear(), ie) + dts.assertNotPreparedSQL(t, b, ` ("a") VALUES (NULL)`) +} + +func (dts *dialectTestSuite) TestInsertSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + opts.LeftParenRune = '{' + opts.RightParenRune = '}' + opts.ValuesFragment = []byte(" values ") + opts.LeftParenRune = '{' + opts.RightParenRune = '}' + opts.CommaRune = ';' + opts.PlaceHolderRune = '#' + d := sqlDialect{opts} + + ie, err := exp.NewInsertExpression() + assert.NoError(t, err) + ie = ie.SetCols(exp.NewColumnListExpression("a", "b")). + SetVals([][]interface{}{ + {"a1", "b1"}, + {"a2", "b2"}, + {"a3", "b3"}, + }) + + bie := ie.SetCols(exp.NewColumnListExpression("a", "b")). + SetVals([][]interface{}{ + {"a1"}, + {"a2", "b2"}, + {"a3", "b3"}, + }) + + b := sb.NewSQLBuilder(false) + d.InsertSQL(b, ie) + dts.assertNotPreparedSQL(t, b, ` {"a"; "b"} values {'a1'; 'b1'}; {'a2'; 'b2'}; {'a3'; 'b3'}`) + + b = sb.NewSQLBuilder(true) + d.InsertSQL(b, ie) + dts.assertPreparedSQL(t, b, ` {"a"; "b"} values {#; #}; {#; #}; {#; #}`, []interface{}{ + "a1", "b1", "a2", "b2", "a3", "b3", + }) + + d.InsertSQL(b.Clear(), bie) + dts.assertErrorSQL(t, b, "goqu: rows with different value length expected 1 got 2") + +} + +func (dts *dialectTestSuite) TestInsertSQL_onConflict() { + t := dts.T() + + opts := DefaultDialectOptions() + // make sure the fragments are used + opts.ConflictFragment = []byte(" on conflict") + opts.ConflictDoNothingFragment = []byte(" do nothing") + opts.ConflictDoUpdateFragment = []byte(" do update set ") + d := sqlDialect{opts} + + ienoc, err := exp.NewInsertExpression() + assert.NoError(t, err) + ienoc = ienoc.SetCols(exp.NewColumnListExpression("a")). + SetVals([][]interface{}{ + {"a1"}, + {"a2"}, + {"a3"}, + }) + + iedn := ienoc.DoNothing() + iedu := ienoc.DoUpdate("test", exp.Record{"a": "b"}) + iedoc := ienoc.DoUpdate("on constraint test", exp.Record{"a": "b"}) + ieduw := ienoc.SetOnConflict( + exp.NewDoUpdateConflictExpression("test", exp.Record{"a": "b"}).Where(exp.Ex{"foo": true}), + ) + + b := sb.NewSQLBuilder(false) + d.InsertSQL(b, ienoc) + dts.assertNotPreparedSQL(t, b, ` ("a") VALUES ('a1'), ('a2'), ('a3')`) + + d.InsertSQL(b.Clear(), iedn) + dts.assertNotPreparedSQL(t, b, ` ("a") VALUES ('a1'), ('a2'), ('a3') on conflict do nothing`) + + d.InsertSQL(b.Clear(), iedu) + dts.assertNotPreparedSQL( + t, + b, + ` ("a") VALUES ('a1'), ('a2'), ('a3') on conflict (test) do update set "a"='b'`, + ) + + d.InsertSQL(b.Clear(), iedoc) + dts.assertNotPreparedSQL(t, b, ` ("a") VALUES ('a1'), ('a2'), ('a3') on conflict on constraint test do update set "a"='b'`) + + d.InsertSQL(b.Clear(), ieduw) + dts.assertNotPreparedSQL(t, b, ` ("a") VALUES ('a1'), ('a2'), ('a3') on conflict (test) do update set "a"='b' WHERE ("foo" IS TRUE)`) + + b = sb.NewSQLBuilder(true) + d.InsertSQL(b, iedn) + dts.assertPreparedSQL(t, b, ` ("a") VALUES (?), (?), (?) on conflict do nothing`, []interface{}{ + "a1", "a2", "a3", + }) + + d.InsertSQL(b.Clear(), iedu) + dts.assertPreparedSQL( + t, + b, + ` ("a") VALUES (?), (?), (?) on conflict (test) do update set "a"=?`, + []interface{}{"a1", "a2", "a3", "b"}, + ) + + d.InsertSQL(b.Clear(), ieduw) + dts.assertPreparedSQL( + t, + b, + ` ("a") VALUES (?), (?), (?) on conflict (test) do update set "a"=? WHERE ("foo" IS TRUE)`, + []interface{}{"a1", "a2", "a3", "b"}, + ) +} + +func (dts *dialectTestSuite) TestUpdateExpressionsSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + // make sure the fragments are used + opts.SetFragment = []byte(" set ") + d := sqlDialect{opts} + u, err := exp.NewUpdateExpressions(exp.Record{"a": "b"}) + assert.NoError(t, err) + + b := sb.NewSQLBuilder(false) + d.UpdateExpressionsSQL(b, u...) + dts.assertNotPreparedSQL(t, b, ` set "a"='b'`) + + b = sb.NewSQLBuilder(true) + d.UpdateExpressionsSQL(b, u...) + dts.assertPreparedSQL(t, b, ` set "a"=?`, []interface{}{"b"}) +} + +func (dts *dialectTestSuite) TestSelectSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + // make sure the fragments are used + opts.SelectClause = []byte("select") + opts.StarRune = '#' + d := sqlDialect{opts} + ec := exp.NewColumnListExpression() + cs := exp.NewColumnListExpression("a", "b") + b := sb.NewSQLBuilder(false) + d.SelectSQL(b, ec) + dts.assertNotPreparedSQL(t, b, `select #`) + + d.SelectSQL(b.Clear(), cs) + dts.assertNotPreparedSQL(t, b, `select "a", "b"`) + + b = sb.NewSQLBuilder(true) + d.SelectSQL(b, ec) + dts.assertPreparedSQL(t, b, `select #`, emptyArgs) + + d.SelectSQL(b.Clear(), cs) + dts.assertPreparedSQL(t, b, `select "a", "b"`, emptyArgs) +} + +func (dts *dialectTestSuite) TestSelectDistinctSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + // make sure the fragments are used + opts.SelectClause = []byte("select") + opts.DistinctFragment = []byte(" distinct ") + d := sqlDialect{opts} + ec := exp.NewColumnListExpression() + cs := exp.NewColumnListExpression("a", "b") + b := sb.NewSQLBuilder(false) + d.SelectDistinctSQL(b, ec) + dts.assertNotPreparedSQL(t, b, `select distinct `) + + d.SelectDistinctSQL(b.Clear(), cs) + dts.assertNotPreparedSQL(t, b, `select distinct "a", "b"`) + + b = sb.NewSQLBuilder(true) + d.SelectDistinctSQL(b.Clear(), ec) + dts.assertPreparedSQL(t, b, `select distinct `, emptyArgs) + + d.SelectDistinctSQL(b.Clear(), cs) + dts.assertPreparedSQL(t, b, `select distinct "a", "b"`, emptyArgs) +} + +func (dts *dialectTestSuite) TestReturningSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + // make sure the fragments are used + opts.ReturningFragment = []byte(" returning ") + d := sqlDialect{opts} + ec := exp.NewColumnListExpression() + cs := exp.NewColumnListExpression("a", "b") + b := sb.NewSQLBuilder(false) + d.ReturningSQL(b, ec) + dts.assertNotPreparedSQL(t, b, ``) + + d.ReturningSQL(b.Clear(), cs) + dts.assertNotPreparedSQL(t, b, ` returning "a", "b"`) + + b = sb.NewSQLBuilder(true) + d.ReturningSQL(b.Clear(), ec) + dts.assertPreparedSQL(t, b, ``, emptyArgs) + + d.ReturningSQL(b.Clear(), cs) + dts.assertPreparedSQL(t, b, ` returning "a", "b"`, emptyArgs) +} + +func (dts *dialectTestSuite) TestFromSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + // make sure the fragments are used + opts.FromFragment = []byte(" from") + d := sqlDialect{opts} + ec := exp.NewColumnListExpression() + cs := exp.NewColumnListExpression("a", "b") + b := sb.NewSQLBuilder(false) + d.FromSQL(b, ec) + dts.assertNotPreparedSQL(t, b, ``) + + d.FromSQL(b.Clear(), cs) + dts.assertNotPreparedSQL(t, b, ` from "a", "b"`) + + b = sb.NewSQLBuilder(true) + d.FromSQL(b.Clear(), ec) + dts.assertPreparedSQL(t, b, ``, emptyArgs) + + d.FromSQL(b.Clear(), cs) + dts.assertPreparedSQL(t, b, ` from "a", "b"`, emptyArgs) +} + +func (dts *dialectTestSuite) TestSourcesSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + d := sqlDialect{opts} + ec := exp.NewColumnListExpression() + cs := exp.NewColumnListExpression("a", "b") + b := sb.NewSQLBuilder(false) + d.SourcesSQL(b, ec) + dts.assertNotPreparedSQL(t, b, ` `) + + d.SourcesSQL(b.Clear(), cs) + dts.assertNotPreparedSQL(t, b, ` "a", "b"`) + + b = sb.NewSQLBuilder(true) + d.SourcesSQL(b.Clear(), ec) + dts.assertPreparedSQL(t, b, ` `, emptyArgs) + + d.SourcesSQL(b.Clear(), cs) + dts.assertPreparedSQL(t, b, ` "a", "b"`, emptyArgs) +} + +func (dts *dialectTestSuite) TestJoinSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + d := sqlDialect{opts} + ti := exp.NewIdentifierExpression("", "test", "") + uj := exp.NewUnConditionedJoinExpression(exp.NaturalJoinType, ti) + cjo := exp.NewConditionedJoinExpression(exp.LeftJoinType, ti, exp.NewJoinOnCondition(exp.Ex{"a": "foo"})) + cju := exp.NewConditionedJoinExpression(exp.LeftJoinType, ti, exp.NewJoinUsingCondition("a")) + + b := sb.NewSQLBuilder(false) + d.JoinSQL(b.Clear(), exp.JoinExpressions{uj}) + dts.assertNotPreparedSQL(t, b, ` NATURAL JOIN "test"`) + + d.JoinSQL(b.Clear(), exp.JoinExpressions{cjo}) + dts.assertNotPreparedSQL(t, b, ` LEFT JOIN "test" ON ("a" = 'foo')`) + + d.JoinSQL(b.Clear(), exp.JoinExpressions{cju}) + dts.assertNotPreparedSQL(t, b, ` LEFT JOIN "test" USING ("a")`) + + d.JoinSQL(b.Clear(), exp.JoinExpressions{uj, cjo, cju}) + dts.assertNotPreparedSQL(t, b, ` NATURAL JOIN "test" LEFT JOIN "test" ON ("a" = 'foo') LEFT JOIN "test" USING ("a")`) + + d.JoinSQL(b.Clear(), exp.JoinExpressions{}) + dts.assertNotPreparedSQL(t, b, ``) + + b = sb.NewSQLBuilder(true) + d.JoinSQL(b.Clear(), exp.JoinExpressions{uj}) + dts.assertPreparedSQL(t, b, ` NATURAL JOIN "test"`, emptyArgs) + + d.JoinSQL(b.Clear(), exp.JoinExpressions{cjo}) + dts.assertPreparedSQL(t, b, ` LEFT JOIN "test" ON ("a" = ?)`, []interface{}{"foo"}) + + d.JoinSQL(b.Clear(), exp.JoinExpressions{cju}) + dts.assertPreparedSQL(t, b, ` LEFT JOIN "test" USING ("a")`, emptyArgs) + + d.JoinSQL(b.Clear(), exp.JoinExpressions{uj, cjo, cju}) + dts.assertPreparedSQL( + t, + b, + ` NATURAL JOIN "test" LEFT JOIN "test" ON ("a" = ?) LEFT JOIN "test" USING ("a")`, + []interface{}{"foo"}, + ) + + opts2 := DefaultDialectOptions() + // override fragements to make sure dialect is used + opts2.UsingFragment = []byte(" using ") + opts2.OnFragment = []byte(" on ") + opts2.JoinTypeLookup = map[exp.JoinType][]byte{ + exp.LeftJoinType: []byte(" left join "), + exp.NaturalJoinType: []byte(" natural join "), + } + d2 := sqlDialect{opts2} + + b = sb.NewSQLBuilder(false) + d2.JoinSQL(b.Clear(), exp.JoinExpressions{uj}) + dts.assertNotPreparedSQL(t, b, ` natural join "test"`) + + d2.JoinSQL(b.Clear(), exp.JoinExpressions{cjo}) + dts.assertNotPreparedSQL(t, b, ` left join "test" on ("a" = 'foo')`) + + d2.JoinSQL(b.Clear(), exp.JoinExpressions{cju}) + dts.assertNotPreparedSQL(t, b, ` left join "test" using ("a")`) + + d2.JoinSQL(b.Clear(), exp.JoinExpressions{uj, cjo, cju}) + dts.assertNotPreparedSQL(t, b, ` natural join "test" left join "test" on ("a" = 'foo') left join "test" using ("a")`) + + rj := exp.NewConditionedJoinExpression(exp.RightJoinType, ti, exp.NewJoinUsingCondition(exp.NewIdentifierExpression("", "", "a"))) + d2.JoinSQL(b.Clear(), exp.JoinExpressions{rj}) + dts.assertErrorSQL(t, b, "goqu: dialect does not support RightJoinType") + + badJoin := exp.NewConditionedJoinExpression(exp.LeftJoinType, ti, exp.NewJoinUsingCondition()) + d2.JoinSQL(b.Clear(), exp.JoinExpressions{badJoin}) + dts.assertErrorSQL(t, b, "goqu: join condition required for conditioned join LeftJoinType") +} + +func (dts *dialectTestSuite) TestWhereSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + opts.WhereFragment = []byte(" where ") + d := sqlDialect{opts} + w := exp.Ex{"a": "b"} + w2 := exp.Ex{"b": "c"} + + b := sb.NewSQLBuilder(false) + d.WhereSQL(b, exp.NewExpressionList(exp.AndType, w)) + dts.assertNotPreparedSQL(t, b, ` where ("a" = 'b')`) + + d.WhereSQL(b.Clear(), exp.NewExpressionList(exp.AndType, w, w2)) + dts.assertNotPreparedSQL(t, b, ` where (("a" = 'b') AND ("b" = 'c'))`) + + d.WhereSQL(b.Clear(), exp.NewExpressionList(exp.AndType)) + dts.assertNotPreparedSQL(t, b, ``) + + b = sb.NewSQLBuilder(true) + d.WhereSQL(b.Clear(), exp.NewExpressionList(exp.AndType, w)) + dts.assertPreparedSQL(t, b, ` where ("a" = ?)`, []interface{}{"b"}) + + d.WhereSQL(b.Clear(), exp.NewExpressionList(exp.AndType, w, w2)) + dts.assertPreparedSQL(t, b, ` where (("a" = ?) AND ("b" = ?))`, []interface{}{"b", "c"}) +} + +func (dts *dialectTestSuite) TestGroupBySQL() { + t := dts.T() + + opts := DefaultDialectOptions() + opts.GroupByFragment = []byte(" group by ") + d := sqlDialect{opts} + c1 := exp.NewIdentifierExpression("", "", "a") + c2 := exp.NewIdentifierExpression("", "", "b") + + b := sb.NewSQLBuilder(false) + d.GroupBySQL(b.Clear(), exp.NewColumnListExpression(c1)) + dts.assertNotPreparedSQL(t, b, ` group by "a"`) + + d.GroupBySQL(b.Clear(), exp.NewColumnListExpression(c1, c2)) + dts.assertNotPreparedSQL(t, b, ` group by "a", "b"`) + + d.GroupBySQL(b.Clear(), exp.NewColumnListExpression()) + dts.assertNotPreparedSQL(t, b, ``) + + b = sb.NewSQLBuilder(true) + d.GroupBySQL(b.Clear(), exp.NewColumnListExpression(c1)) + dts.assertPreparedSQL(t, b, ` group by "a"`, emptyArgs) + + d.GroupBySQL(b.Clear(), exp.NewColumnListExpression(c1, c2)) + dts.assertPreparedSQL(t, b, ` group by "a", "b"`, emptyArgs) +} +func (dts *dialectTestSuite) TestHavingSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + opts.HavingFragment = []byte(" having ") + d := sqlDialect{opts} + w := exp.Ex{"a": "b"} + w2 := exp.Ex{"b": "c"} + + b := sb.NewSQLBuilder(false) + d.HavingSQL(b, exp.NewExpressionList(exp.AndType, w)) + dts.assertNotPreparedSQL(t, b, ` having ("a" = 'b')`) + + d.HavingSQL(b.Clear(), exp.NewExpressionList(exp.AndType, w, w2)) + dts.assertNotPreparedSQL(t, b, ` having (("a" = 'b') AND ("b" = 'c'))`) + + d.HavingSQL(b.Clear(), exp.NewExpressionList(exp.AndType)) + dts.assertNotPreparedSQL(t, b, ``) + + b = sb.NewSQLBuilder(true) + d.HavingSQL(b.Clear(), exp.NewExpressionList(exp.AndType, w)) + dts.assertPreparedSQL(t, b, ` having ("a" = ?)`, []interface{}{"b"}) + + d.HavingSQL(b.Clear(), exp.NewExpressionList(exp.AndType, w, w2)) + dts.assertPreparedSQL(t, b, ` having (("a" = ?) AND ("b" = ?))`, []interface{}{"b", "c"}) +} + +func (dts *dialectTestSuite) TestOrderSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + // override fragments to ensure they are used + opts.OrderByFragment = []byte(" order by ") + opts.AscFragment = []byte(" asc") + opts.DescFragment = []byte(" desc") + opts.NullsFirstFragment = []byte(" nulls first") + opts.NullsLastFragment = []byte(" nulls last") + d := sqlDialect{opts} + + oa := exp.NewIdentifierExpression("", "", "a").Asc() + oanf := exp.NewIdentifierExpression("", "", "a").Asc().NullsFirst() + oanl := exp.NewIdentifierExpression("", "", "a").Asc().NullsLast() + od := exp.NewIdentifierExpression("", "", "a").Desc() + odnf := exp.NewIdentifierExpression("", "", "a").Desc().NullsFirst() + odnl := exp.NewIdentifierExpression("", "", "a").Desc().NullsLast() + + b := sb.NewSQLBuilder(false) + d.OrderSQL(b, exp.NewColumnListExpression(oa)) + dts.assertNotPreparedSQL(t, b, ` order by "a" asc`) + + d.OrderSQL(b.Clear(), exp.NewColumnListExpression(oanf)) + dts.assertNotPreparedSQL(t, b, ` order by "a" asc nulls first`) + + d.OrderSQL(b.Clear(), exp.NewColumnListExpression(oanl)) + dts.assertNotPreparedSQL(t, b, ` order by "a" asc nulls last`) + + d.OrderSQL(b.Clear(), exp.NewColumnListExpression(od)) + dts.assertNotPreparedSQL(t, b, ` order by "a" desc`) + + d.OrderSQL(b.Clear(), exp.NewColumnListExpression(odnf)) + dts.assertNotPreparedSQL(t, b, ` order by "a" desc nulls first`) + + d.OrderSQL(b.Clear(), exp.NewColumnListExpression(odnl)) + dts.assertNotPreparedSQL(t, b, ` order by "a" desc nulls last`) + + d.OrderSQL(b.Clear(), exp.NewColumnListExpression()) + dts.assertNotPreparedSQL(t, b, ``) + + b = sb.NewSQLBuilder(true) + d.OrderSQL(b, exp.NewColumnListExpression(oa)) + dts.assertPreparedSQL(t, b, ` order by "a" asc`, emptyArgs) + + d.OrderSQL(b.Clear(), exp.NewColumnListExpression(oanf)) + dts.assertPreparedSQL(t, b, ` order by "a" asc nulls first`, emptyArgs) + + d.OrderSQL(b.Clear(), exp.NewColumnListExpression(oanl)) + dts.assertPreparedSQL(t, b, ` order by "a" asc nulls last`, emptyArgs) + + d.OrderSQL(b.Clear(), exp.NewColumnListExpression(od)) + dts.assertPreparedSQL(t, b, ` order by "a" desc`, emptyArgs) + + d.OrderSQL(b.Clear(), exp.NewColumnListExpression(odnf)) + dts.assertPreparedSQL(t, b, ` order by "a" desc nulls first`, emptyArgs) + + d.OrderSQL(b.Clear(), exp.NewColumnListExpression(odnl)) + dts.assertPreparedSQL(t, b, ` order by "a" desc nulls last`, emptyArgs) + + d.OrderSQL(b.Clear(), exp.NewColumnListExpression()) + dts.assertPreparedSQL(t, b, ``, emptyArgs) + +} +func (dts *dialectTestSuite) TestLimitSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + opts.LimitFragment = []byte(" limit ") + d := sqlDialect{opts} + + b := sb.NewSQLBuilder(false) + d.LimitSQL(b, 10) + dts.assertNotPreparedSQL(t, b, ` limit 10`) + + d.LimitSQL(b.Clear(), 0) + dts.assertNotPreparedSQL(t, b, ` limit 0`) + + d.LimitSQL(b.Clear(), exp.NewLiteralExpression("ALL")) + dts.assertNotPreparedSQL(t, b, ` limit ALL`) + + d.LimitSQL(b.Clear(), nil) + dts.assertNotPreparedSQL(t, b, ``) + + b = sb.NewSQLBuilder(true) + d.LimitSQL(b.Clear(), 10) + dts.assertPreparedSQL(t, b, ` limit ?`, []interface{}{int64(10)}) + + d.LimitSQL(b.Clear(), 0) + dts.assertPreparedSQL(t, b, ` limit ?`, []interface{}{int64(0)}) + + d.LimitSQL(b.Clear(), exp.NewLiteralExpression("ALL")) + dts.assertPreparedSQL(t, b, ` limit ALL`, emptyArgs) + + d.LimitSQL(b.Clear(), nil) + dts.assertPreparedSQL(t, b, ``, emptyArgs) +} +func (dts *dialectTestSuite) TestOffsetSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + opts.OffsetFragment = []byte(" offset ") + d := sqlDialect{opts} + o := uint(10) + + b := sb.NewSQLBuilder(false) + d.OffsetSQL(b.Clear(), o) + dts.assertNotPreparedSQL(t, b, ` offset 10`) + + d.OffsetSQL(b.Clear(), 0) + dts.assertNotPreparedSQL(t, b, ``) + + b = sb.NewSQLBuilder(true) + d.OffsetSQL(b.Clear(), o) + dts.assertPreparedSQL(t, b, ` offset ?`, []interface{}{int64(o)}) + + d.OffsetSQL(b.Clear(), 0) + dts.assertPreparedSQL(t, b, ``, emptyArgs) +} + +func (dts *dialectTestSuite) TestCommonTablesSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + opts.WithFragment = []byte("with ") + opts.RecursiveFragment = []byte("recursive ") + d := sqlDialect{opts} + tse := newTestAppendableExpression("select * from foo", emptyArgs, nil, nil) + cte1 := exp.NewCommonTableExpression(false, "test_cte", tse) + cte2 := exp.NewCommonTableExpression(true, "test_cte", tse) + + b := sb.NewSQLBuilder(false) + d.CommonTablesSQL(b.Clear(), []exp.CommonTableExpression{}) + dts.assertNotPreparedSQL(t, b, ``) + + d.CommonTablesSQL(b.Clear(), []exp.CommonTableExpression{cte1}) + dts.assertNotPreparedSQL(t, b, `with test_cte AS (select * from foo) `) + + d.CommonTablesSQL(b.Clear(), []exp.CommonTableExpression{cte2}) + dts.assertNotPreparedSQL(t, b, `with recursive test_cte AS (select * from foo) `) + + d.CommonTablesSQL(b.Clear(), []exp.CommonTableExpression{cte1, cte2}) + dts.assertNotPreparedSQL( + t, + b, + `with recursive test_cte AS (select * from foo), test_cte AS (select * from foo) `, + ) + + opts = DefaultDialectOptions() + opts.SupportsWithCTE = false + d = sqlDialect{opts} + + d.CommonTablesSQL(b.Clear(), []exp.CommonTableExpression{cte1}) + dts.assertErrorSQL(t, b, "goqu: adapter does not support CTE with clause") + + opts = DefaultDialectOptions() + opts.SupportsWithCTERecursive = false + d = sqlDialect{opts} + + d.CommonTablesSQL(b.Clear(), []exp.CommonTableExpression{cte2}) + dts.assertErrorSQL(t, b, "goqu: adapter does not support CTE with recursive clause") + + d.CommonTablesSQL(b.Clear(), []exp.CommonTableExpression{cte1}) + dts.assertNotPreparedSQL(t, b, `WITH test_cte AS (select * from foo) `) + +} + +func (dts *dialectTestSuite) TestCompoundsSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + opts.UnionFragment = []byte(" union ") + opts.UnionAllFragment = []byte(" union all ") + opts.IntersectFragment = []byte(" intersect ") + opts.IntersectAllFragment = []byte(" intersect all ") + d := sqlDialect{opts} + tse := newTestAppendableExpression("select * from foo", emptyArgs, nil, nil) + u := exp.NewCompoundExpression(exp.UnionCompoundType, tse) + ua := exp.NewCompoundExpression(exp.UnionAllCompoundType, tse) + i := exp.NewCompoundExpression(exp.IntersectCompoundType, tse) + ia := exp.NewCompoundExpression(exp.IntersectAllCompoundType, tse) + + b := sb.NewSQLBuilder(false) + d.CompoundsSQL(b.Clear(), []exp.CompoundExpression{}) + dts.assertNotPreparedSQL(t, b, ``) + + d.CompoundsSQL(b.Clear(), []exp.CompoundExpression{u}) + dts.assertNotPreparedSQL(t, b, ` union (select * from foo)`) + + d.CompoundsSQL(b.Clear(), []exp.CompoundExpression{ua}) + dts.assertNotPreparedSQL(t, b, ` union all (select * from foo)`) + + d.CompoundsSQL(b.Clear(), []exp.CompoundExpression{i}) + dts.assertNotPreparedSQL(t, b, ` intersect (select * from foo)`) + + d.CompoundsSQL(b.Clear(), []exp.CompoundExpression{ia}) + dts.assertNotPreparedSQL(t, b, ` intersect all (select * from foo)`) + + d.CompoundsSQL(b.Clear(), []exp.CompoundExpression{u, ua, i, ia}) + dts.assertNotPreparedSQL( + t, + b, + ` union (select * from foo) union all (select * from foo) intersect (select * from foo) intersect all (select * from foo)`, + ) + +} + +func (dts *dialectTestSuite) TestForSQL() { + t := dts.T() + + opts := DefaultDialectOptions() + opts.ForUpdateFragment = []byte(" for update ") + opts.ForNoKeyUpdateFragment = []byte(" for no key update ") + opts.ForShareFragment = []byte(" for share ") + opts.ForKeyShareFragment = []byte(" for key share ") + opts.NowaitFragment = []byte("nowait") + opts.SkipLockedFragment = []byte("skip locked") + d := sqlDialect{opts} + + b := sb.NewSQLBuilder(false) + d.ForSQL(b.Clear(), exp.NewLock(exp.ForNolock, exp.Wait)) + dts.assertNotPreparedSQL(t, b, ``) + + d.ForSQL(b.Clear(), exp.NewLock(exp.ForShare, exp.Wait)) + dts.assertNotPreparedSQL(t, b, ` for share `) + + d.ForSQL(b.Clear(), exp.NewLock(exp.ForShare, exp.NoWait)) + dts.assertNotPreparedSQL(t, b, ` for share nowait`) + + d.ForSQL(b.Clear(), exp.NewLock(exp.ForShare, exp.SkipLocked)) + dts.assertNotPreparedSQL(t, b, ` for share skip locked`) + + d.ForSQL(b.Clear(), exp.NewLock(exp.ForKeyShare, exp.Wait)) + dts.assertNotPreparedSQL(t, b, ` for key share `) + + d.ForSQL(b.Clear(), exp.NewLock(exp.ForKeyShare, exp.NoWait)) + dts.assertNotPreparedSQL(t, b, ` for key share nowait`) + + d.ForSQL(b.Clear(), exp.NewLock(exp.ForKeyShare, exp.SkipLocked)) + dts.assertNotPreparedSQL(t, b, ` for key share skip locked`) + + d.ForSQL(b.Clear(), exp.NewLock(exp.ForUpdate, exp.Wait)) + dts.assertNotPreparedSQL(t, b, ` for update `) + + d.ForSQL(b.Clear(), exp.NewLock(exp.ForUpdate, exp.NoWait)) + dts.assertNotPreparedSQL(t, b, ` for update nowait`) + + d.ForSQL(b.Clear(), exp.NewLock(exp.ForUpdate, exp.SkipLocked)) + dts.assertNotPreparedSQL(t, b, ` for update skip locked`) + + d.ForSQL(b.Clear(), exp.NewLock(exp.ForNoKeyUpdate, exp.Wait)) + dts.assertNotPreparedSQL(t, b, ` for no key update `) + + d.ForSQL(b.Clear(), exp.NewLock(exp.ForNoKeyUpdate, exp.NoWait)) + dts.assertNotPreparedSQL(t, b, ` for no key update nowait`) + + d.ForSQL(b.Clear(), exp.NewLock(exp.ForNoKeyUpdate, exp.SkipLocked)) + dts.assertNotPreparedSQL(t, b, ` for no key update skip locked`) + + d.ForSQL(b.Clear(), nil) + dts.assertNotPreparedSQL(t, b, ``) + +} + +func (dts *dialectTestSuite) TestLiteral_FloatTypes() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + var float float64 + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), float32(10.01)) + dts.assertNotPreparedSQL(t, b, "10.010000228881836") + + d.Literal(b.Clear(), float64(10.01)) + dts.assertNotPreparedSQL(t, b, "10.01") + + d.Literal(b.Clear(), &float) + dts.assertNotPreparedSQL(t, b, "0") + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), float32(10.01)) + dts.assertPreparedSQL(t, b, "?", []interface{}{float64(float32(10.01))}) + + d.Literal(b.Clear(), float64(10.01)) + dts.assertPreparedSQL(t, b, "?", []interface{}{float64(10.01)}) + + d.Literal(b.Clear(), &float) + dts.assertPreparedSQL(t, b, "?", []interface{}{float}) +} + +func (dts *dialectTestSuite) TestLiteral_IntTypes() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + var i int64 + b := sb.NewSQLBuilder(false) + ints := []interface{}{ + int(10), + int16(10), + int32(10), + int64(10), + uint(10), + uint16(10), + uint32(10), + uint64(10), + } + for _, i := range ints { + d.Literal(b.Clear(), i) + dts.assertNotPreparedSQL(t, b, "10") + } + d.Literal(b.Clear(), &i) + dts.assertNotPreparedSQL(t, b, "0") + + b = sb.NewSQLBuilder(true) + for _, i := range ints { + d.Literal(b.Clear(), i) + dts.assertPreparedSQL(t, b, "?", []interface{}{int64(10)}) + } + d.Literal(b.Clear(), &i) + dts.assertPreparedSQL(t, b, "?", []interface{}{i}) +} + +func (dts *dialectTestSuite) TestLiteral_StringTypes() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + var str string + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), "Hello") + dts.assertNotPreparedSQL(t, b, "'Hello'") + + // should escape single quotes + d.Literal(b.Clear(), "Hello'") + dts.assertNotPreparedSQL(t, b, "'Hello'''") + + d.Literal(b.Clear(), &str) + dts.assertNotPreparedSQL(t, b, "''") + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), "Hello") + dts.assertPreparedSQL(t, b, "?", []interface{}{"Hello"}) + + // should escape single quotes + d.Literal(b.Clear(), "Hello'") + dts.assertPreparedSQL(t, b, "?", []interface{}{"Hello'"}) + + d.Literal(b.Clear(), &str) + dts.assertPreparedSQL(t, b, "?", []interface{}{str}) +} + +func (dts *dialectTestSuite) TestLiteral_BytesTypes() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), []byte("Hello")) + dts.assertNotPreparedSQL(t, b, "'Hello'") + + // should escape single quotes + d.Literal(b.Clear(), []byte("Hello'")) + dts.assertNotPreparedSQL(t, b, "'Hello'''") + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), []byte("Hello")) + dts.assertPreparedSQL(t, b, "?", []interface{}{[]byte("Hello")}) + + // should escape single quotes + d.Literal(b.Clear(), []byte("Hello'")) + dts.assertPreparedSQL(t, b, "?", []interface{}{[]byte("Hello'")}) +} + +func (dts *dialectTestSuite) TestLiteral_BoolTypes() { + t := dts.T() + var bl bool + + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), true) + dts.assertNotPreparedSQL(t, b, "TRUE") + + d.Literal(b.Clear(), false) + dts.assertNotPreparedSQL(t, b, "FALSE") + + d.Literal(b.Clear(), &bl) + dts.assertNotPreparedSQL(t, b, "FALSE") + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), true) + dts.assertPreparedSQL(t, b, "?", []interface{}{true}) + + d.Literal(b.Clear(), false) + dts.assertPreparedSQL(t, b, "?", []interface{}{false}) + + d.Literal(b.Clear(), &bl) + dts.assertPreparedSQL(t, b, "?", []interface{}{bl}) +} + +func (dts *dialectTestSuite) TestLiteral_TimeTypes() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + now := time.Now().UTC() + var nt *time.Time + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), now) + dts.assertNotPreparedSQL(t, b, "'"+now.Format(time.RFC3339Nano)+"'") + + d.Literal(b.Clear(), &now) + dts.assertNotPreparedSQL(t, b, "'"+now.Format(time.RFC3339Nano)+"'") + + d.Literal(b.Clear(), nt) + dts.assertNotPreparedSQL(t, b, "NULL") + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), now) + dts.assertPreparedSQL(t, b, "?", []interface{}{now}) + + d.Literal(b.Clear(), &now) + dts.assertPreparedSQL(t, b, "?", []interface{}{now}) + + d.Literal(b.Clear(), nt) + dts.assertPreparedSQL(t, b, "NULL", emptyArgs) +} + +func (dts *dialectTestSuite) TestLiteral_NilTypes() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), nil) + dts.assertNotPreparedSQL(t, b, "NULL") + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), nil) + dts.assertPreparedSQL(t, b, "NULL", []interface{}{}) +} + +type datasetValuerType int64 + +func (j datasetValuerType) Value() (driver.Value, error) { + return []byte(fmt.Sprintf("Hello World %d", j)), nil +} + +func (dts *dialectTestSuite) TestLiteral_Valuer() { + t := dts.T() + b := sb.NewSQLBuilder(false) + d := sqlDialect{DefaultDialectOptions()} + + d.Literal(b.Clear(), datasetValuerType(10)) + dts.assertNotPreparedSQL(t, b, "'Hello World 10'") + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), datasetValuerType(10)) + dts.assertPreparedSQL(t, b, "?", []interface{}{[]byte("Hello World 10")}) +} + +func (dts *dialectTestSuite) TestLiteral_Slice() { + t := dts.T() + b := sb.NewSQLBuilder(false) + d := sqlDialect{DefaultDialectOptions()} + d.Literal(b.Clear(), []string{"a", "b", "c"}) + dts.assertNotPreparedSQL(t, b, `('a', 'b', 'c')`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), []string{"a", "b", "c"}) + dts.assertPreparedSQL(t, b, `(?, ?, ?)`, []interface{}{"a", "b", "c"}) +} + +type unknownExpression struct { +} + +func (ue unknownExpression) Expression() exp.Expression { + return ue +} +func (ue unknownExpression) Clone() exp.Expression { + return ue +} +func (dts *dialectTestSuite) TestLiteralUnsupportedExpression() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), unknownExpression{}) + dts.assertErrorSQL(t, b, "goqu: unsupported expression type goqu.unknownExpression") +} + +func (dts *dialectTestSuite) TestLiteral_AppendableExpression() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + ti := exp.NewIdentifierExpression("", "b", "") + a := newTestAppendableExpression(`select * from "a"`, []interface{}{}, nil, nil) + aliasedA := newTestAppendableExpression(`select * from "a"`, []interface{}{}, nil, exp.NewClauses().SetAlias(ti)) + argsA := newTestAppendableExpression(`select * from "a" where x=?`, []interface{}{true}, nil, exp.NewClauses().SetAlias(ti)) + ae := newTestAppendableExpression(`select * from "a"`, emptyArgs, errors.New("expected error"), nil) + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), a) + dts.assertNotPreparedSQL(t, b, `(select * from "a")`) + + d.Literal(b.Clear(), aliasedA) + dts.assertNotPreparedSQL(t, b, `(select * from "a") AS "b"`) + + d.Literal(b.Clear(), ae) + dts.assertErrorSQL(t, b, "goqu: expected error") + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), a) + dts.assertPreparedSQL(t, b, `(select * from "a")`, emptyArgs) + + d.Literal(b.Clear(), aliasedA) + dts.assertPreparedSQL(t, b, `(select * from "a") AS "b"`, emptyArgs) + + d.Literal(b.Clear(), argsA) + dts.assertPreparedSQL(t, b, `(select * from "a" where x=?) AS "b"`, []interface{}{true}) +} + +func (dts *dialectTestSuite) TestLiteral_ColumnList() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.NewColumnListExpression("a", exp.NewLiteralExpression("true"))) + dts.assertNotPreparedSQL(t, b, `"a", true`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewColumnListExpression("a", exp.NewLiteralExpression("true"))) + dts.assertPreparedSQL(t, b, `"a", true`, emptyArgs) +} + +func (dts *dialectTestSuite) TestLiteral_ExpressionList() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.NewExpressionList( + exp.AndType, + exp.NewIdentifierExpression("", "", "a").Eq("b"), + exp.NewIdentifierExpression("", "", "c").Neq(1), + )) + dts.assertNotPreparedSQL(t, b, `(("a" = 'b') AND ("c" != 1))`) + + d.Literal(b.Clear(), exp.NewExpressionList( + exp.OrType, + exp.NewIdentifierExpression("", "", "a").Eq("b"), + exp.NewIdentifierExpression("", "", "c").Neq(1), + )) + dts.assertNotPreparedSQL(t, b, `(("a" = 'b') OR ("c" != 1))`) + + d.Literal(b.Clear(), exp.NewExpressionList(exp.OrType, + exp.NewIdentifierExpression("", "", "a").Eq("b"), + exp.NewExpressionList(exp.AndType, + exp.NewIdentifierExpression("", "", "c").Neq(1), + exp.NewIdentifierExpression("", "", "d").Eq(exp.NewLiteralExpression("NOW()")), + ), + )) + dts.assertNotPreparedSQL(t, b, `(("a" = 'b') OR (("c" != 1) AND ("d" = NOW())))`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewExpressionList( + exp.AndType, + exp.NewIdentifierExpression("", "", "a").Eq("b"), + exp.NewIdentifierExpression("", "", "c").Neq(1), + )) + dts.assertPreparedSQL(t, b, `(("a" = ?) AND ("c" != ?))`, []interface{}{"b", int64(1)}) + + d.Literal(b.Clear(), exp.NewExpressionList( + exp.OrType, + exp.NewIdentifierExpression("", "", "a").Eq("b"), + exp.NewIdentifierExpression("", "", "c").Neq(1)), + ) + dts.assertPreparedSQL(t, b, `(("a" = ?) OR ("c" != ?))`, []interface{}{"b", int64(1)}) + + d.Literal(b.Clear(), exp.NewExpressionList( + exp.OrType, + exp.NewIdentifierExpression("", "", "a").Eq("b"), + exp.NewExpressionList( + exp.AndType, + exp.NewIdentifierExpression("", "", "c").Neq(1), + exp.NewIdentifierExpression("", "", "d").Eq(exp.NewLiteralExpression("NOW()")), + ), + )) + dts.assertPreparedSQL(t, b, `(("a" = ?) OR (("c" != ?) AND ("d" = NOW())))`, []interface{}{"b", int64(1)}) +} + +func (dts *dialectTestSuite) TestLiteral_LiteralExpression() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.NewLiteralExpression(`"b"::DATE = '2010-09-02'`)) + dts.assertNotPreparedSQL(t, b, `"b"::DATE = '2010-09-02'`) + + d.Literal(b.Clear(), exp.NewLiteralExpression( + `"b" = ? or "c" = ? or d IN ?`, + "a", 1, []int{1, 2, 3, 4}), + ) + dts.assertNotPreparedSQL(t, b, `"b" = 'a' or "c" = 1 or d IN (1, 2, 3, 4)`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewLiteralExpression(`"b"::DATE = '2010-09-02'`)) + dts.assertPreparedSQL(t, b, `"b"::DATE = '2010-09-02'`, emptyArgs) + + d.Literal(b.Clear(), exp.NewLiteralExpression( + `"b" = ? or "c" = ? or d IN ?`, + "a", 1, []int{1, 2, 3, 4}, + )) + dts.assertPreparedSQL(t, b, `"b" = ? or "c" = ? or d IN (?, ?, ?, ?)`, []interface{}{ + "a", + int64(1), + int64(1), + int64(2), + int64(3), + int64(4), + }) +} + +func (dts *dialectTestSuite) TestLiteral_AliasedExpression() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").As("b")) + dts.assertNotPreparedSQL(t, b, `"a" AS "b"`) + + d.Literal(b.Clear(), exp.NewLiteralExpression("count(*)").As("count")) + dts.assertNotPreparedSQL(t, b, `count(*) AS "count"`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + As(exp.NewIdentifierExpression("", "", "b"))) + dts.assertNotPreparedSQL(t, b, `"a" AS "b"`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").As("b")) + dts.assertPreparedSQL(t, b, `"a" AS "b"`, emptyArgs) + + d.Literal(b.Clear(), exp.NewLiteralExpression("count(*)").As("count")) + dts.assertPreparedSQL(t, b, `count(*) AS "count"`, emptyArgs) +} + +func (dts *dialectTestSuite) TestLiteral_BooleanExpression() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + ae := newTestAppendableExpression(`SELECT "id" FROM "test2"`, emptyArgs, nil, nil) + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Eq(1)) + dts.assertNotPreparedSQL(t, b, `("a" = 1)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Eq(true)) + dts.assertNotPreparedSQL(t, b, `("a" IS TRUE)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Eq(false)) + dts.assertNotPreparedSQL(t, b, `("a" IS FALSE)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Eq(nil)) + dts.assertNotPreparedSQL(t, b, `("a" IS NULL)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Eq([]int64{1, 2, 3})) + dts.assertNotPreparedSQL(t, b, `("a" IN (1, 2, 3))`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Eq(ae)) + dts.assertNotPreparedSQL(t, b, `("a" IN (SELECT "id" FROM "test2"))`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Neq(1)) + dts.assertNotPreparedSQL(t, b, `("a" != 1)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Neq(true)) + dts.assertNotPreparedSQL(t, b, `("a" IS NOT TRUE)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Neq(false)) + dts.assertNotPreparedSQL(t, b, `("a" IS NOT FALSE)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Neq(nil)) + dts.assertNotPreparedSQL(t, b, `("a" IS NOT NULL)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Neq([]int64{1, 2, 3})) + dts.assertNotPreparedSQL(t, b, `("a" NOT IN (1, 2, 3))`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Neq(ae)) + dts.assertNotPreparedSQL(t, b, `("a" NOT IN (SELECT "id" FROM "test2"))`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Is(nil)) + dts.assertNotPreparedSQL(t, b, `("a" IS NULL)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Is(false)) + dts.assertNotPreparedSQL(t, b, `("a" IS FALSE)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Is(true)) + dts.assertNotPreparedSQL(t, b, `("a" IS TRUE)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").IsNot(nil)) + dts.assertNotPreparedSQL(t, b, `("a" IS NOT NULL)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").IsNot(false)) + dts.assertNotPreparedSQL(t, b, `("a" IS NOT FALSE)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").IsNot(true)) + dts.assertNotPreparedSQL(t, b, `("a" IS NOT TRUE)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Gt(1)) + dts.assertNotPreparedSQL(t, b, `("a" > 1)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Gte(1)) + dts.assertNotPreparedSQL(t, b, `("a" >= 1)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Lt(1)) + dts.assertNotPreparedSQL(t, b, `("a" < 1)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Lte(1)) + dts.assertNotPreparedSQL(t, b, `("a" <= 1)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").In([]int{1, 2, 3})) + dts.assertNotPreparedSQL(t, b, `("a" IN (1, 2, 3))`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").NotIn([]int{1, 2, 3})) + dts.assertNotPreparedSQL(t, b, `("a" NOT IN (1, 2, 3))`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Like("a%")) + dts.assertNotPreparedSQL(t, b, `("a" LIKE 'a%')`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + Like(regexp.MustCompile("(a|b)"))) + dts.assertNotPreparedSQL(t, b, `("a" ~ '(a|b)')`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").NotLike("a%")) + dts.assertNotPreparedSQL(t, b, `("a" NOT LIKE 'a%')`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + NotLike(regexp.MustCompile("(a|b)"))) + dts.assertNotPreparedSQL(t, b, `("a" !~ '(a|b)')`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").ILike("a%")) + dts.assertNotPreparedSQL(t, b, `("a" ILIKE 'a%')`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + ILike(regexp.MustCompile("(a|b)"))) + dts.assertNotPreparedSQL(t, b, `("a" ~* '(a|b)')`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").NotILike("a%")) + dts.assertNotPreparedSQL(t, b, `("a" NOT ILIKE 'a%')`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + NotILike(regexp.MustCompile("(a|b)"))) + dts.assertNotPreparedSQL(t, b, `("a" !~* '(a|b)')`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Eq(1)) + dts.assertPreparedSQL(t, b, `("a" = ?)`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Eq(true)) + dts.assertPreparedSQL(t, b, `("a" IS TRUE)`, []interface{}{}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Eq(false)) + dts.assertPreparedSQL(t, b, `("a" IS FALSE)`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Eq(nil)) + dts.assertPreparedSQL(t, b, `("a" IS NULL)`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Eq([]int64{1, 2, 3})) + dts.assertPreparedSQL(t, b, `("a" IN (?, ?, ?))`, []interface{}{int64(1), int64(2), int64(3)}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Neq(1)) + dts.assertPreparedSQL(t, b, `("a" != ?)`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Neq(true)) + dts.assertPreparedSQL(t, b, `("a" IS NOT TRUE)`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Neq(false)) + dts.assertPreparedSQL(t, b, `("a" IS NOT FALSE)`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Neq(nil)) + dts.assertPreparedSQL(t, b, `("a" IS NOT NULL)`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Neq([]int64{1, 2, 3})) + dts.assertPreparedSQL(t, b, `("a" NOT IN (?, ?, ?))`, []interface{}{int64(1), int64(2), int64(3)}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Is(nil)) + dts.assertPreparedSQL(t, b, `("a" IS NULL)`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Is(false)) + dts.assertPreparedSQL(t, b, `("a" IS FALSE)`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Is(true)) + dts.assertPreparedSQL(t, b, `("a" IS TRUE)`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").IsNot(nil)) + dts.assertPreparedSQL(t, b, `("a" IS NOT NULL)`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").IsNot(false)) + dts.assertPreparedSQL(t, b, `("a" IS NOT FALSE)`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").IsNot(true)) + dts.assertPreparedSQL(t, b, `("a" IS NOT TRUE)`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Gt(1)) + dts.assertPreparedSQL(t, b, `("a" > ?)`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Gte(1)) + dts.assertPreparedSQL(t, b, `("a" >= ?)`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Lt(1)) + dts.assertPreparedSQL(t, b, `("a" < ?)`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Lte(1)) + dts.assertPreparedSQL(t, b, `("a" <= ?)`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").In([]int{1, 2, 3})) + dts.assertPreparedSQL(t, b, `("a" IN (?, ?, ?))`, []interface{}{int64(1), int64(2), int64(3)}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").NotIn([]int{1, 2, 3})) + dts.assertPreparedSQL(t, b, `("a" NOT IN (?, ?, ?))`, []interface{}{int64(1), int64(2), int64(3)}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Like("a%")) + dts.assertPreparedSQL(t, b, `("a" LIKE ?)`, []interface{}{"a%"}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + Like(regexp.MustCompile("(a|b)"))) + dts.assertPreparedSQL(t, b, `("a" ~ ?)`, []interface{}{"(a|b)"}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").NotLike("a%")) + dts.assertPreparedSQL(t, b, `("a" NOT LIKE ?)`, []interface{}{"a%"}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + NotLike(regexp.MustCompile("(a|b)"))) + dts.assertPreparedSQL(t, b, `("a" !~ ?)`, []interface{}{"(a|b)"}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").ILike("a%")) + dts.assertPreparedSQL(t, b, `("a" ILIKE ?)`, []interface{}{"a%"}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + ILike(regexp.MustCompile("(a|b)"))) + dts.assertPreparedSQL(t, b, `("a" ~* ?)`, []interface{}{"(a|b)"}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").NotILike("a%")) + dts.assertPreparedSQL(t, b, `("a" NOT ILIKE ?)`, []interface{}{"a%"}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + NotILike(regexp.MustCompile("(a|b)"))) + dts.assertPreparedSQL(t, b, `("a" !~* ?)`, []interface{}{"(a|b)"}) +} + +func (dts *dialectTestSuite) TestLiteral_RangeExpression() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + Between(exp.NewRangeVal(1, 2))) + dts.assertNotPreparedSQL(t, b, `("a" BETWEEN 1 AND 2)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + NotBetween(exp.NewRangeVal(1, 2))) + dts.assertNotPreparedSQL(t, b, `("a" NOT BETWEEN 1 AND 2)`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + Between(exp.NewRangeVal("aaa", "zzz"))) + dts.assertNotPreparedSQL(t, b, `("a" BETWEEN 'aaa' AND 'zzz')`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + Between(exp.NewRangeVal(1, 2))) + dts.assertPreparedSQL(t, b, `("a" BETWEEN ? AND ?)`, []interface{}{int64(1), int64(2)}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + NotBetween(exp.NewRangeVal(1, 2))) + dts.assertPreparedSQL(t, b, `("a" NOT BETWEEN ? AND ?)`, []interface{}{int64(1), int64(2)}) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a"). + Between(exp.NewRangeVal("aaa", "zzz"))) + dts.assertPreparedSQL(t, b, `("a" BETWEEN ? AND ?)`, []interface{}{"aaa", "zzz"}) +} + +func (dts *dialectTestSuite) TestLiteral_OrderedExpression() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Asc()) + dts.assertNotPreparedSQL(t, b, `"a" ASC`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Desc()) + dts.assertNotPreparedSQL(t, b, `"a" DESC`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Asc().NullsLast()) + dts.assertNotPreparedSQL(t, b, `"a" ASC NULLS LAST`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Desc().NullsLast()) + dts.assertNotPreparedSQL(t, b, `"a" DESC NULLS LAST`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Asc().NullsFirst()) + dts.assertNotPreparedSQL(t, b, `"a" ASC NULLS FIRST`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Desc().NullsFirst()) + dts.assertNotPreparedSQL(t, b, `"a" DESC NULLS FIRST`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Asc()) + dts.assertPreparedSQL(t, b, `"a" ASC`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Desc()) + dts.assertPreparedSQL(t, b, `"a" DESC`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Asc().NullsLast()) + dts.assertPreparedSQL(t, b, `"a" ASC NULLS LAST`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Desc().NullsLast()) + dts.assertPreparedSQL(t, b, `"a" DESC NULLS LAST`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Asc().NullsFirst()) + dts.assertPreparedSQL(t, b, `"a" ASC NULLS FIRST`, emptyArgs) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Desc().NullsFirst()) + dts.assertPreparedSQL(t, b, `"a" DESC NULLS FIRST`, emptyArgs) +} + +func (dts *dialectTestSuite) TestLiteral_UpdateExpression() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Set(1)) + dts.assertNotPreparedSQL(t, b, `"a"=1`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Set(1)) + dts.assertPreparedSQL(t, b, `"a"=?`, []interface{}{int64(1)}) +} + +func (dts *dialectTestSuite) TestLiteral_SQLFunctionExpression() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.NewSQLFunctionExpression("MIN", exp.NewIdentifierExpression("", "", "a"))) + dts.assertNotPreparedSQL(t, b, `MIN("a")`) + + d.Literal(b.Clear(), exp.NewSQLFunctionExpression("COALESCE", exp.NewIdentifierExpression("", "", "a"), "a")) + dts.assertNotPreparedSQL(t, b, `COALESCE("a", 'a')`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewSQLFunctionExpression("MIN", exp.NewIdentifierExpression("", "", "a"))) + dts.assertNotPreparedSQL(t, b, `MIN("a")`) + + d.Literal(b.Clear(), exp.NewSQLFunctionExpression("COALESCE", exp.NewIdentifierExpression("", "", "a"), "a")) + dts.assertPreparedSQL(t, b, `COALESCE("a", ?)`, []interface{}{"a"}) + +} + +func (dts *dialectTestSuite) TestLiteral_CastExpression() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Cast("DATE")) + dts.assertNotPreparedSQL(t, b, `CAST("a" AS DATE)`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "a").Cast("DATE")) + dts.assertPreparedSQL(t, b, `CAST("a" AS DATE)`, emptyArgs) +} + +func (dts *dialectTestSuite) TestLiteral_CommonTableExpression() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + ae := newTestAppendableExpression(`SELECT * FROM "b"`, emptyArgs, nil, nil) + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.NewCommonTableExpression(false, "a", ae)) + dts.assertNotPreparedSQL(t, b, `a AS (SELECT * FROM "b")`) + + d.Literal(b.Clear(), exp.NewCommonTableExpression(false, "a(x,y)", ae)) + dts.assertNotPreparedSQL(t, b, `a(x,y) AS (SELECT * FROM "b")`) + + d.Literal(b.Clear(), exp.NewCommonTableExpression(true, "a", ae)) + dts.assertNotPreparedSQL(t, b, `a AS (SELECT * FROM "b")`) + + d.Literal(b.Clear(), exp.NewCommonTableExpression(true, "a(x,y)", ae)) + dts.assertNotPreparedSQL(t, b, `a(x,y) AS (SELECT * FROM "b")`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewCommonTableExpression(false, "a", ae)) + dts.assertPreparedSQL(t, b, `a AS (SELECT * FROM "b")`, emptyArgs) + + d.Literal(b.Clear(), exp.NewCommonTableExpression(false, "a(x,y)", ae)) + dts.assertPreparedSQL(t, b, `a(x,y) AS (SELECT * FROM "b")`, emptyArgs) + + d.Literal(b.Clear(), exp.NewCommonTableExpression(true, "a", ae)) + dts.assertPreparedSQL(t, b, `a AS (SELECT * FROM "b")`, emptyArgs) + + d.Literal(b.Clear(), exp.NewCommonTableExpression(true, "a(x,y)", ae)) + dts.assertPreparedSQL(t, b, `a(x,y) AS (SELECT * FROM "b")`, emptyArgs) +} + +func (dts *dialectTestSuite) TestLiteral_CompoundExpression() { + t := dts.T() + ae := newTestAppendableExpression(`SELECT * FROM "b"`, emptyArgs, nil, nil) + + b := sb.NewSQLBuilder(false) + d := sqlDialect{DefaultDialectOptions()} + + d.Literal(b.Clear(), exp.NewCompoundExpression(exp.UnionCompoundType, ae)) + dts.assertNotPreparedSQL(t, b, ` UNION (SELECT * FROM "b")`) + + d.Literal(b.Clear(), exp.NewCompoundExpression(exp.UnionAllCompoundType, ae)) + dts.assertNotPreparedSQL(t, b, ` UNION ALL (SELECT * FROM "b")`) + + d.Literal(b.Clear(), exp.NewCompoundExpression(exp.IntersectCompoundType, ae)) + dts.assertNotPreparedSQL(t, b, ` INTERSECT (SELECT * FROM "b")`) + + d.Literal(b.Clear(), exp.NewCompoundExpression(exp.IntersectAllCompoundType, ae)) + dts.assertNotPreparedSQL(t, b, ` INTERSECT ALL (SELECT * FROM "b")`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewCompoundExpression(exp.UnionCompoundType, ae)) + dts.assertNotPreparedSQL(t, b, ` UNION (SELECT * FROM "b")`) + + d.Literal(b.Clear(), exp.NewCompoundExpression(exp.UnionAllCompoundType, ae)) + dts.assertNotPreparedSQL(t, b, ` UNION ALL (SELECT * FROM "b")`) + + d.Literal(b.Clear(), exp.NewCompoundExpression(exp.IntersectCompoundType, ae)) + dts.assertNotPreparedSQL(t, b, ` INTERSECT (SELECT * FROM "b")`) + + d.Literal(b.Clear(), exp.NewCompoundExpression(exp.IntersectAllCompoundType, ae)) + dts.assertNotPreparedSQL(t, b, ` INTERSECT ALL (SELECT * FROM "b")`) +} + +func (dts *dialectTestSuite) TestLiteral_IdentifierExpression() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "col")) + dts.assertNotPreparedSQL(t, b, `"col"`) + + d.Literal(b.Clear(), exp.ParseIdentifier("table.col")) + dts.assertNotPreparedSQL(t, b, `"table"."col"`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "col").Table("table")) + dts.assertNotPreparedSQL(t, b, `"table"."col"`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "table", "col")) + dts.assertNotPreparedSQL(t, b, `"table"."col"`) + + d.Literal(b.Clear(), exp.ParseIdentifier("a.b.c")) + dts.assertNotPreparedSQL(t, b, `"a"."b"."c"`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("schema", "table", "col")) + dts.assertNotPreparedSQL(t, b, `"schema"."table"."col"`) + + d.Literal(b.Clear(), exp.ParseIdentifier("schema.table.*")) + dts.assertNotPreparedSQL(t, b, `"schema"."table".*`) + + d.Literal(b.Clear(), exp.ParseIdentifier("table.*")) + dts.assertNotPreparedSQL(t, b, `"table".*`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "col")) + dts.assertNotPreparedSQL(t, b, `"col"`) + + d.Literal(b.Clear(), exp.ParseIdentifier("table.col")) + dts.assertNotPreparedSQL(t, b, `"table"."col"`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "", "col").Table("table")) + dts.assertNotPreparedSQL(t, b, `"table"."col"`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("", "table", "col")) + dts.assertNotPreparedSQL(t, b, `"table"."col"`) + + d.Literal(b.Clear(), exp.ParseIdentifier("a.b.c")) + dts.assertNotPreparedSQL(t, b, `"a"."b"."c"`) + + d.Literal(b.Clear(), exp.NewIdentifierExpression("schema", "table", "col")) + dts.assertNotPreparedSQL(t, b, `"schema"."table"."col"`) + + d.Literal(b.Clear(), exp.ParseIdentifier("schema.table.*")) + dts.assertNotPreparedSQL(t, b, `"schema"."table".*`) + + d.Literal(b.Clear(), exp.ParseIdentifier("table.*")) + dts.assertNotPreparedSQL(t, b, `"table".*`) +} + +func (dts *dialectTestSuite) TestLiteral_ExpressionMap() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.Ex{"a": 1}) + dts.assertNotPreparedSQL(t, b, `("a" = 1)`) + + d.Literal(b.Clear(), exp.Ex{"a": true}) + dts.assertNotPreparedSQL(t, b, `("a" IS TRUE)`) + + d.Literal(b.Clear(), exp.Ex{"a": false}) + dts.assertNotPreparedSQL(t, b, `("a" IS FALSE)`) + + d.Literal(b.Clear(), exp.Ex{"a": nil}) + dts.assertNotPreparedSQL(t, b, `("a" IS NULL)`) + + d.Literal(b.Clear(), exp.Ex{"a": []string{"a", "b", "c"}}) + dts.assertNotPreparedSQL(t, b, `("a" IN ('a', 'b', 'c'))`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"neq": 1}}) + dts.assertNotPreparedSQL(t, b, `("a" != 1)`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"isnot": true}}) + dts.assertNotPreparedSQL(t, b, `("a" IS NOT TRUE)`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"gt": 1}}) + dts.assertNotPreparedSQL(t, b, `("a" > 1)`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"gte": 1}}) + dts.assertNotPreparedSQL(t, b, `("a" >= 1)`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"lt": 1}}) + dts.assertNotPreparedSQL(t, b, `("a" < 1)`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"lte": 1}}) + dts.assertNotPreparedSQL(t, b, `("a" <= 1)`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"like": "a%"}}) + dts.assertNotPreparedSQL(t, b, `("a" LIKE 'a%')`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"notLike": "a%"}}) + dts.assertNotPreparedSQL(t, b, `("a" NOT LIKE 'a%')`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"notLike": "a%"}}) + dts.assertNotPreparedSQL(t, b, `("a" NOT LIKE 'a%')`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"in": []string{"a", "b", "c"}}}) + dts.assertNotPreparedSQL(t, b, `("a" IN ('a', 'b', 'c'))`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"notIn": []string{"a", "b", "c"}}}) + dts.assertNotPreparedSQL(t, b, `("a" NOT IN ('a', 'b', 'c'))`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"is": nil, "eq": 10}}) + dts.assertNotPreparedSQL(t, b, `(("a" = 10) OR ("a" IS NULL))`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"between": exp.NewRangeVal(1, 10)}}) + dts.assertNotPreparedSQL(t, b, `("a" BETWEEN 1 AND 10)`) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"notbetween": exp.NewRangeVal(1, 10)}}) + dts.assertNotPreparedSQL(t, b, `("a" NOT BETWEEN 1 AND 10)`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.Ex{"a": 1}) + dts.assertPreparedSQL(t, b, `("a" = ?)`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.Ex{"a": true}) + dts.assertPreparedSQL(t, b, `("a" IS TRUE)`, emptyArgs) + + d.Literal(b.Clear(), exp.Ex{"a": false}) + dts.assertPreparedSQL(t, b, `("a" IS FALSE)`, emptyArgs) + + d.Literal(b.Clear(), exp.Ex{"a": nil}) + dts.assertPreparedSQL(t, b, `("a" IS NULL)`, emptyArgs) + + d.Literal(b.Clear(), exp.Ex{"a": []string{"a", "b", "c"}}) + dts.assertPreparedSQL(t, b, `("a" IN (?, ?, ?))`, []interface{}{"a", "b", "c"}) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"neq": 1}}) + dts.assertPreparedSQL(t, b, `("a" != ?)`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"isnot": true}}) + dts.assertPreparedSQL(t, b, `("a" IS NOT TRUE)`, emptyArgs) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"gt": 1}}) + dts.assertPreparedSQL(t, b, `("a" > ?)`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"gte": 1}}) + dts.assertPreparedSQL(t, b, `("a" >= ?)`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"lt": 1}}) + dts.assertPreparedSQL(t, b, `("a" < ?)`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"lte": 1}}) + dts.assertPreparedSQL(t, b, `("a" <= ?)`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"like": "a%"}}) + dts.assertPreparedSQL(t, b, `("a" LIKE ?)`, []interface{}{"a%"}) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"notLike": "a%"}}) + dts.assertPreparedSQL(t, b, `("a" NOT LIKE ?)`, []interface{}{"a%"}) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"in": []string{"a", "b", "c"}}}) + dts.assertPreparedSQL(t, b, `("a" IN (?, ?, ?))`, []interface{}{"a", "b", "c"}) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"notIn": []string{"a", "b", "c"}}}) + dts.assertPreparedSQL(t, b, `("a" NOT IN (?, ?, ?))`, []interface{}{"a", "b", "c"}) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"is": nil, "eq": 10}}) + dts.assertPreparedSQL(t, b, `(("a" = ?) OR ("a" IS NULL))`, []interface{}{int64(10)}) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"between": exp.NewRangeVal(1, 10)}}) + dts.assertPreparedSQL(t, b, `("a" BETWEEN ? AND ?)`, []interface{}{int64(1), int64(10)}) + + d.Literal(b.Clear(), exp.Ex{"a": exp.Op{"notbetween": exp.NewRangeVal(1, 10)}}) + dts.assertPreparedSQL(t, b, `("a" NOT BETWEEN ? AND ?)`, []interface{}{int64(1), int64(10)}) +} + +func (dts *dialectTestSuite) TestLiteral_ExpressionOrMap() { + t := dts.T() + d := sqlDialect{DefaultDialectOptions()} + + b := sb.NewSQLBuilder(false) + d.Literal(b.Clear(), exp.ExOr{"a": 1, "b": true}) + dts.assertNotPreparedSQL(t, b, `(("a" = 1) OR ("b" IS TRUE))`) + + d.Literal(b.Clear(), exp.ExOr{"a": 1, "b": []string{"a", "b", "c"}}) + dts.assertNotPreparedSQL(t, b, `(("a" = 1) OR ("b" IN ('a', 'b', 'c')))`) + + b = sb.NewSQLBuilder(true) + d.Literal(b.Clear(), exp.ExOr{"a": 1, "b": true}) + dts.assertPreparedSQL(t, b, `(("a" = ?) OR ("b" IS TRUE))`, []interface{}{int64(1)}) + + d.Literal(b.Clear(), exp.ExOr{"a": 1, "b": []string{"a", "b", "c"}}) + dts.assertPreparedSQL(t, b, `(("a" = ?) OR ("b" IN (?, ?, ?)))`, []interface{}{int64(1), "a", "b", "c"}) + +} +func TestDialectSuite(t *testing.T) { + suite.Run(t, new(dialectTestSuite)) +} diff --git a/vendor/github.com/davecgh/go-spew/LICENSE b/vendor/github.com/davecgh/go-spew/LICENSE index c8364161..bc52e96f 100644 --- a/vendor/github.com/davecgh/go-spew/LICENSE +++ b/vendor/github.com/davecgh/go-spew/LICENSE @@ -2,7 +2,7 @@ ISC License Copyright (c) 2012-2016 Dave Collins -Permission to use, copy, modify, and distribute this software for any +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go index 8a4a6589..79299478 100644 --- a/vendor/github.com/davecgh/go-spew/spew/bypass.go +++ b/vendor/github.com/davecgh/go-spew/spew/bypass.go @@ -16,7 +16,9 @@ // when the code is not running on Google App Engine, compiled by GopherJS, and // "-tags safe" is not added to the go build command line. The "disableunsafe" // tag is deprecated and thus should not be used. -// +build !js,!appengine,!safe,!disableunsafe +// Go versions prior to 1.4 are disabled because they use a different layout +// for interfaces which make the implementation of unsafeReflectValue more complex. +// +build !js,!appengine,!safe,!disableunsafe,go1.4 package spew @@ -34,80 +36,49 @@ const ( ptrSize = unsafe.Sizeof((*byte)(nil)) ) +type flag uintptr + var ( - // offsetPtr, offsetScalar, and offsetFlag are the offsets for the - // internal reflect.Value fields. These values are valid before golang - // commit ecccf07e7f9d which changed the format. The are also valid - // after commit 82f48826c6c7 which changed the format again to mirror - // the original format. Code in the init function updates these offsets - // as necessary. - offsetPtr = uintptr(ptrSize) - offsetScalar = uintptr(0) - offsetFlag = uintptr(ptrSize * 2) - - // flagKindWidth and flagKindShift indicate various bits that the - // reflect package uses internally to track kind information. - // - // flagRO indicates whether or not the value field of a reflect.Value is - // read-only. - // - // flagIndir indicates whether the value field of a reflect.Value is - // the actual data or a pointer to the data. - // - // These values are valid before golang commit 90a7c3c86944 which - // changed their positions. Code in the init function updates these - // flags as necessary. - flagKindWidth = uintptr(5) - flagKindShift = uintptr(flagKindWidth - 1) - flagRO = uintptr(1 << 0) - flagIndir = uintptr(1 << 1) + // flagRO indicates whether the value field of a reflect.Value + // is read-only. + flagRO flag + + // flagAddr indicates whether the address of the reflect.Value's + // value may be taken. + flagAddr flag ) -func init() { - // Older versions of reflect.Value stored small integers directly in the - // ptr field (which is named val in the older versions). Versions - // between commits ecccf07e7f9d and 82f48826c6c7 added a new field named - // scalar for this purpose which unfortunately came before the flag - // field, so the offset of the flag field is different for those - // versions. - // - // This code constructs a new reflect.Value from a known small integer - // and checks if the size of the reflect.Value struct indicates it has - // the scalar field. When it does, the offsets are updated accordingly. - vv := reflect.ValueOf(0xf00) - if unsafe.Sizeof(vv) == (ptrSize * 4) { - offsetScalar = ptrSize * 2 - offsetFlag = ptrSize * 3 - } +// flagKindMask holds the bits that make up the kind +// part of the flags field. In all the supported versions, +// it is in the lower 5 bits. +const flagKindMask = flag(0x1f) - // Commit 90a7c3c86944 changed the flag positions such that the low - // order bits are the kind. This code extracts the kind from the flags - // field and ensures it's the correct type. When it's not, the flag - // order has been changed to the newer format, so the flags are updated - // accordingly. - upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag) - upfv := *(*uintptr)(upf) - flagKindMask := uintptr((1<>flagKindShift != uintptr(reflect.Int) { - flagKindShift = 0 - flagRO = 1 << 5 - flagIndir = 1 << 6 - - // Commit adf9b30e5594 modified the flags to separate the - // flagRO flag into two bits which specifies whether or not the - // field is embedded. This causes flagIndir to move over a bit - // and means that flagRO is the combination of either of the - // original flagRO bit and the new bit. - // - // This code detects the change by extracting what used to be - // the indirect bit to ensure it's set. When it's not, the flag - // order has been changed to the newer format, so the flags are - // updated accordingly. - if upfv&flagIndir == 0 { - flagRO = 3 << 5 - flagIndir = 1 << 7 - } +// Different versions of Go have used different +// bit layouts for the flags type. This table +// records the known combinations. +var okFlags = []struct { + ro, addr flag +}{{ + // From Go 1.4 to 1.5 + ro: 1 << 5, + addr: 1 << 7, +}, { + // Up to Go tip. + ro: 1<<5 | 1<<6, + addr: 1 << 8, +}} + +var flagValOffset = func() uintptr { + field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") + if !ok { + panic("reflect.Value has no flag field") } + return field.Offset +}() + +// flagField returns a pointer to the flag field of a reflect.Value. +func flagField(v *reflect.Value) *flag { + return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset)) } // unsafeReflectValue converts the passed reflect.Value into a one that bypasses @@ -119,34 +90,56 @@ func init() { // This allows us to check for implementations of the Stringer and error // interfaces to be used for pretty printing ordinarily unaddressable and // inaccessible values such as unexported struct fields. -func unsafeReflectValue(v reflect.Value) (rv reflect.Value) { - indirects := 1 - vt := v.Type() - upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr) - rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag)) - if rvf&flagIndir != 0 { - vt = reflect.PtrTo(v.Type()) - indirects++ - } else if offsetScalar != 0 { - // The value is in the scalar field when it's not one of the - // reference types. - switch vt.Kind() { - case reflect.Uintptr: - case reflect.Chan: - case reflect.Func: - case reflect.Map: - case reflect.Ptr: - case reflect.UnsafePointer: - default: - upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + - offsetScalar) - } +func unsafeReflectValue(v reflect.Value) reflect.Value { + if !v.IsValid() || (v.CanInterface() && v.CanAddr()) { + return v } + flagFieldPtr := flagField(&v) + *flagFieldPtr &^= flagRO + *flagFieldPtr |= flagAddr + return v +} - pv := reflect.NewAt(vt, upv) - rv = pv - for i := 0; i < indirects; i++ { - rv = rv.Elem() +// Sanity checks against future reflect package changes +// to the type or semantics of the Value.flag field. +func init() { + field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") + if !ok { + panic("reflect.Value has no flag field") + } + if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() { + panic("reflect.Value flag field has changed kind") + } + type t0 int + var t struct { + A t0 + // t0 will have flagEmbedRO set. + t0 + // a will have flagStickyRO set + a t0 + } + vA := reflect.ValueOf(t).FieldByName("A") + va := reflect.ValueOf(t).FieldByName("a") + vt0 := reflect.ValueOf(t).FieldByName("t0") + + // Infer flagRO from the difference between the flags + // for the (otherwise identical) fields in t. + flagPublic := *flagField(&vA) + flagWithRO := *flagField(&va) | *flagField(&vt0) + flagRO = flagPublic ^ flagWithRO + + // Infer flagAddr from the difference between a value + // taken from a pointer and not. + vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A") + flagNoPtr := *flagField(&vA) + flagPtr := *flagField(&vPtrA) + flagAddr = flagNoPtr ^ flagPtr + + // Check that the inferred flags tally with one of the known versions. + for _, f := range okFlags { + if flagRO == f.ro && flagAddr == f.addr { + return + } } - return rv + panic("reflect.Value read-only flag has changed semantics") } diff --git a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go index 1fe3cf3d..205c28d6 100644 --- a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go +++ b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go @@ -16,7 +16,7 @@ // when the code is running on Google App Engine, compiled by GopherJS, or // "-tags safe" is added to the go build command line. The "disableunsafe" // tag is deprecated and thus should not be used. -// +build js appengine safe disableunsafe +// +build js appengine safe disableunsafe !go1.4 package spew diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go index 7c519ff4..1be8ce94 100644 --- a/vendor/github.com/davecgh/go-spew/spew/common.go +++ b/vendor/github.com/davecgh/go-spew/spew/common.go @@ -180,7 +180,7 @@ func printComplex(w io.Writer, c complex128, floatPrecision int) { w.Write(closeParenBytes) } -// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x' +// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x' // prefix to Writer w. func printHexPtr(w io.Writer, p uintptr) { // Null pointer. diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go index df1d582a..f78d89fc 100644 --- a/vendor/github.com/davecgh/go-spew/spew/dump.go +++ b/vendor/github.com/davecgh/go-spew/spew/dump.go @@ -35,16 +35,16 @@ var ( // cCharRE is a regular expression that matches a cgo char. // It is used to detect character arrays to hexdump them. - cCharRE = regexp.MustCompile("^.*\\._Ctype_char$") + cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`) // cUnsignedCharRE is a regular expression that matches a cgo unsigned // char. It is used to detect unsigned character arrays to hexdump // them. - cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$") + cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`) // cUint8tCharRE is a regular expression that matches a cgo uint8_t. // It is used to detect uint8_t arrays to hexdump them. - cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$") + cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`) ) // dumpState contains information about the state of a dump operation. @@ -143,10 +143,10 @@ func (d *dumpState) dumpPtr(v reflect.Value) { // Display dereferenced value. d.w.Write(openParenBytes) switch { - case nilFound == true: + case nilFound: d.w.Write(nilAngleBytes) - case cycleFound == true: + case cycleFound: d.w.Write(circularBytes) default: diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go index c49875ba..b04edb7d 100644 --- a/vendor/github.com/davecgh/go-spew/spew/format.go +++ b/vendor/github.com/davecgh/go-spew/spew/format.go @@ -182,10 +182,10 @@ func (f *formatState) formatPtr(v reflect.Value) { // Display dereferenced value. switch { - case nilFound == true: + case nilFound: f.fs.Write(nilAngleBytes) - case cycleFound == true: + case cycleFound: f.fs.Write(circularShortBytes) default: diff --git a/vendor/github.com/stretchr/objx/.gitignore b/vendor/github.com/stretchr/objx/.gitignore new file mode 100644 index 00000000..e0170a5f --- /dev/null +++ b/vendor/github.com/stretchr/objx/.gitignore @@ -0,0 +1,4 @@ +/dep +/testdep +/profile.out +/coverage.txt diff --git a/vendor/github.com/stretchr/objx/.travis.yml b/vendor/github.com/stretchr/objx/.travis.yml new file mode 100644 index 00000000..1456363e --- /dev/null +++ b/vendor/github.com/stretchr/objx/.travis.yml @@ -0,0 +1,13 @@ +language: go +go: + - 1.8 + - 1.9 + - tip + +install: +- go get github.com/go-task/task/cmd/task + +script: +- task dl-deps +- task lint +- task test diff --git a/vendor/github.com/stretchr/objx/Gopkg.lock b/vendor/github.com/stretchr/objx/Gopkg.lock new file mode 100644 index 00000000..1f5739c9 --- /dev/null +++ b/vendor/github.com/stretchr/objx/Gopkg.lock @@ -0,0 +1,27 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" + +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + name = "github.com/stretchr/testify" + packages = ["assert"] + revision = "b91bfb9ebec76498946beb6af7c0230c7cc7ba6c" + version = "v1.2.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "50e2495ec1af6e2f7ffb2f3551e4300d30357d7c7fe38ff6056469fa9cfb3673" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/stretchr/objx/Gopkg.toml b/vendor/github.com/stretchr/objx/Gopkg.toml new file mode 100644 index 00000000..f87e18eb --- /dev/null +++ b/vendor/github.com/stretchr/objx/Gopkg.toml @@ -0,0 +1,3 @@ +[[constraint]] + name = "github.com/stretchr/testify" + version = "~1.2.0" diff --git a/vendor/github.com/stretchr/objx/LICENSE b/vendor/github.com/stretchr/objx/LICENSE new file mode 100644 index 00000000..44d4d9d5 --- /dev/null +++ b/vendor/github.com/stretchr/objx/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2014 Stretchr, Inc. +Copyright (c) 2017-2018 objx contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/stretchr/objx/README.md b/vendor/github.com/stretchr/objx/README.md new file mode 100644 index 00000000..4e2400eb --- /dev/null +++ b/vendor/github.com/stretchr/objx/README.md @@ -0,0 +1,78 @@ +# Objx +[![Build Status](https://travis-ci.org/stretchr/objx.svg?branch=master)](https://travis-ci.org/stretchr/objx) +[![Go Report Card](https://goreportcard.com/badge/github.com/stretchr/objx)](https://goreportcard.com/report/github.com/stretchr/objx) +[![Sourcegraph](https://sourcegraph.com/github.com/stretchr/objx/-/badge.svg)](https://sourcegraph.com/github.com/stretchr/objx) +[![GoDoc](https://godoc.org/github.com/stretchr/objx?status.svg)](https://godoc.org/github.com/stretchr/objx) + +Objx - Go package for dealing with maps, slices, JSON and other data. + +Get started: + +- Install Objx with [one line of code](#installation), or [update it with another](#staying-up-to-date) +- Check out the API Documentation http://godoc.org/github.com/stretchr/objx + +## Overview +Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes a powerful `Get` method (among others) that allows you to easily and quickly get access to data within the map, without having to worry too much about type assertions, missing data, default values etc. + +### Pattern +Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy. Call one of the `objx.` functions to create your `objx.Map` to get going: + + m, err := objx.FromJSON(json) + +NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, the rest will be optimistic and try to figure things out without panicking. + +Use `Get` to access the value you're interested in. You can use dot and array +notation too: + + m.Get("places[0].latlng") + +Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type. + + if m.Get("code").IsStr() { // Your code... } + +Or you can just assume the type, and use one of the strong type methods to extract the real value: + + m.Get("code").Int() + +If there's no value there (or if it's the wrong type) then a default value will be returned, or you can be explicit about the default value. + + Get("code").Int(-1) + +If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating, manipulating and selecting that data. You can find out more by exploring the index below. + +### Reading data +A simple example of how to use Objx: + + // Use MustFromJSON to make an objx.Map from some JSON + m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`) + + // Get the details + name := m.Get("name").Str() + age := m.Get("age").Int() + + // Get their nickname (or use their name if they don't have one) + nickname := m.Get("nickname").Str(name) + +### Ranging +Since `objx.Map` is a `map[string]interface{}` you can treat it as such. For example, to `range` the data, do what you would expect: + + m := objx.MustFromJSON(json) + for key, value := range m { + // Your code... + } + +## Installation +To install Objx, use go get: + + go get github.com/stretchr/objx + +### Staying up to date +To update Objx to the latest version, run: + + go get -u github.com/stretchr/objx + +### Supported go versions +We support the lastest two major Go versions, which are 1.8 and 1.9 at the moment. + +## Contributing +Please feel free to submit issues, fork the repository and send pull requests! diff --git a/vendor/github.com/stretchr/objx/Taskfile.yml b/vendor/github.com/stretchr/objx/Taskfile.yml new file mode 100644 index 00000000..403b5f06 --- /dev/null +++ b/vendor/github.com/stretchr/objx/Taskfile.yml @@ -0,0 +1,26 @@ +default: + deps: [test] + +dl-deps: + desc: Downloads cli dependencies + cmds: + - go get -u github.com/golang/lint/golint + - go get -u github.com/golang/dep/cmd/dep + +update-deps: + desc: Updates dependencies + cmds: + - dep ensure + - dep ensure -update + - dep prune + +lint: + desc: Runs golint + cmds: + - golint $(ls *.go | grep -v "doc.go") + silent: true + +test: + desc: Runs go tests + cmds: + - go test -race . diff --git a/vendor/github.com/stretchr/objx/accessors.go b/vendor/github.com/stretchr/objx/accessors.go new file mode 100644 index 00000000..d95be0ca --- /dev/null +++ b/vendor/github.com/stretchr/objx/accessors.go @@ -0,0 +1,171 @@ +package objx + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +// arrayAccesRegexString is the regex used to extract the array number +// from the access path +const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$` + +// arrayAccesRegex is the compiled arrayAccesRegexString +var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString) + +// Get gets the value using the specified selector and +// returns it inside a new Obj object. +// +// If it cannot find the value, Get will return a nil +// value inside an instance of Obj. +// +// Get can only operate directly on map[string]interface{} and []interface. +// +// Example +// +// To access the title of the third chapter of the second book, do: +// +// o.Get("books[1].chapters[2].title") +func (m Map) Get(selector string) *Value { + rawObj := access(m, selector, nil, false, false) + return &Value{data: rawObj} +} + +// Set sets the value using the specified selector and +// returns the object on which Set was called. +// +// Set can only operate directly on map[string]interface{} and []interface +// +// Example +// +// To set the title of the third chapter of the second book, do: +// +// o.Set("books[1].chapters[2].title","Time to Go") +func (m Map) Set(selector string, value interface{}) Map { + access(m, selector, value, true, false) + return m +} + +// access accesses the object using the selector and performs the +// appropriate action. +func access(current, selector, value interface{}, isSet, panics bool) interface{} { + + switch selector.(type) { + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + + if array, ok := current.([]interface{}); ok { + index := intFromInterface(selector) + + if index >= len(array) { + if panics { + panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array))) + } + return nil + } + + return array[index] + } + + return nil + + case string: + + selStr := selector.(string) + selSegs := strings.SplitN(selStr, PathSeparator, 2) + thisSel := selSegs[0] + index := -1 + var err error + + if strings.Contains(thisSel, "[") { + arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel) + + if len(arrayMatches) > 0 { + // Get the key into the map + thisSel = arrayMatches[1] + + // Get the index into the array at the key + index, err = strconv.Atoi(arrayMatches[2]) + + if err != nil { + // This should never happen. If it does, something has gone + // seriously wrong. Panic. + panic("objx: Array index is not an integer. Must use array[int].") + } + } + } + + if curMap, ok := current.(Map); ok { + current = map[string]interface{}(curMap) + } + + // get the object in question + switch current.(type) { + case map[string]interface{}: + curMSI := current.(map[string]interface{}) + if len(selSegs) <= 1 && isSet { + curMSI[thisSel] = value + return nil + } + current = curMSI[thisSel] + default: + current = nil + } + + if current == nil && panics { + panic(fmt.Sprintf("objx: '%v' invalid on object.", selector)) + } + + // do we need to access the item of an array? + if index > -1 { + if array, ok := current.([]interface{}); ok { + if index < len(array) { + current = array[index] + } else { + if panics { + panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array))) + } + current = nil + } + } + } + + if len(selSegs) > 1 { + current = access(current, selSegs[1], value, isSet, panics) + } + + } + return current +} + +// intFromInterface converts an interface object to the largest +// representation of an unsigned integer using a type switch and +// assertions +func intFromInterface(selector interface{}) int { + var value int + switch selector.(type) { + case int: + value = selector.(int) + case int8: + value = int(selector.(int8)) + case int16: + value = int(selector.(int16)) + case int32: + value = int(selector.(int32)) + case int64: + value = int(selector.(int64)) + case uint: + value = int(selector.(uint)) + case uint8: + value = int(selector.(uint8)) + case uint16: + value = int(selector.(uint16)) + case uint32: + value = int(selector.(uint32)) + case uint64: + value = int(selector.(uint64)) + default: + panic("objx: array access argument is not an integer type (this should never happen)") + } + return value +} diff --git a/vendor/github.com/stretchr/objx/constants.go b/vendor/github.com/stretchr/objx/constants.go new file mode 100644 index 00000000..f9eb42a2 --- /dev/null +++ b/vendor/github.com/stretchr/objx/constants.go @@ -0,0 +1,13 @@ +package objx + +const ( + // PathSeparator is the character used to separate the elements + // of the keypath. + // + // For example, `location.address.city` + PathSeparator string = "." + + // SignatureSeparator is the character that is used to + // separate the Base64 string from the security signature. + SignatureSeparator = "_" +) diff --git a/vendor/github.com/stretchr/objx/conversions.go b/vendor/github.com/stretchr/objx/conversions.go new file mode 100644 index 00000000..5e020f31 --- /dev/null +++ b/vendor/github.com/stretchr/objx/conversions.go @@ -0,0 +1,108 @@ +package objx + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "net/url" +) + +// JSON converts the contained object to a JSON string +// representation +func (m Map) JSON() (string, error) { + result, err := json.Marshal(m) + if err != nil { + err = errors.New("objx: JSON encode failed with: " + err.Error()) + } + return string(result), err +} + +// MustJSON converts the contained object to a JSON string +// representation and panics if there is an error +func (m Map) MustJSON() string { + result, err := m.JSON() + if err != nil { + panic(err.Error()) + } + return result +} + +// Base64 converts the contained object to a Base64 string +// representation of the JSON string representation +func (m Map) Base64() (string, error) { + var buf bytes.Buffer + + jsonData, err := m.JSON() + if err != nil { + return "", err + } + + encoder := base64.NewEncoder(base64.StdEncoding, &buf) + _, err = encoder.Write([]byte(jsonData)) + if err != nil { + return "", err + } + _ = encoder.Close() + + return buf.String(), nil +} + +// MustBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and panics +// if there is an error +func (m Map) MustBase64() string { + result, err := m.Base64() + if err != nil { + panic(err.Error()) + } + return result +} + +// SignedBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and signs it +// using the provided key. +func (m Map) SignedBase64(key string) (string, error) { + base64, err := m.Base64() + if err != nil { + return "", err + } + + sig := HashWithKey(base64, key) + return base64 + SignatureSeparator + sig, nil +} + +// MustSignedBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and signs it +// using the provided key and panics if there is an error +func (m Map) MustSignedBase64(key string) string { + result, err := m.SignedBase64(key) + if err != nil { + panic(err.Error()) + } + return result +} + +/* + URL Query + ------------------------------------------------ +*/ + +// URLValues creates a url.Values object from an Obj. This +// function requires that the wrapped object be a map[string]interface{} +func (m Map) URLValues() url.Values { + vals := make(url.Values) + for k, v := range m { + //TODO: can this be done without sprintf? + vals.Set(k, fmt.Sprintf("%v", v)) + } + return vals +} + +// URLQuery gets an encoded URL query representing the given +// Obj. This function requires that the wrapped object be a +// map[string]interface{} +func (m Map) URLQuery() (string, error) { + return m.URLValues().Encode(), nil +} diff --git a/vendor/github.com/stretchr/objx/doc.go b/vendor/github.com/stretchr/objx/doc.go new file mode 100644 index 00000000..6d6af1a8 --- /dev/null +++ b/vendor/github.com/stretchr/objx/doc.go @@ -0,0 +1,66 @@ +/* +Objx - Go package for dealing with maps, slices, JSON and other data. + +Overview + +Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes +a powerful `Get` method (among others) that allows you to easily and quickly get +access to data within the map, without having to worry too much about type assertions, +missing data, default values etc. + +Pattern + +Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy. +Call one of the `objx.` functions to create your `objx.Map` to get going: + + m, err := objx.FromJSON(json) + +NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, +the rest will be optimistic and try to figure things out without panicking. + +Use `Get` to access the value you're interested in. You can use dot and array +notation too: + + m.Get("places[0].latlng") + +Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type. + + if m.Get("code").IsStr() { // Your code... } + +Or you can just assume the type, and use one of the strong type methods to extract the real value: + + m.Get("code").Int() + +If there's no value there (or if it's the wrong type) then a default value will be returned, +or you can be explicit about the default value. + + Get("code").Int(-1) + +If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating, +manipulating and selecting that data. You can find out more by exploring the index below. + +Reading data + +A simple example of how to use Objx: + + // Use MustFromJSON to make an objx.Map from some JSON + m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`) + + // Get the details + name := m.Get("name").Str() + age := m.Get("age").Int() + + // Get their nickname (or use their name if they don't have one) + nickname := m.Get("nickname").Str(name) + +Ranging + +Since `objx.Map` is a `map[string]interface{}` you can treat it as such. +For example, to `range` the data, do what you would expect: + + m := objx.MustFromJSON(json) + for key, value := range m { + // Your code... + } +*/ +package objx diff --git a/vendor/github.com/stretchr/objx/map.go b/vendor/github.com/stretchr/objx/map.go new file mode 100644 index 00000000..7e9389a2 --- /dev/null +++ b/vendor/github.com/stretchr/objx/map.go @@ -0,0 +1,193 @@ +package objx + +import ( + "encoding/base64" + "encoding/json" + "errors" + "io/ioutil" + "net/url" + "strings" +) + +// MSIConvertable is an interface that defines methods for converting your +// custom types to a map[string]interface{} representation. +type MSIConvertable interface { + // MSI gets a map[string]interface{} (msi) representing the + // object. + MSI() map[string]interface{} +} + +// Map provides extended functionality for working with +// untyped data, in particular map[string]interface (msi). +type Map map[string]interface{} + +// Value returns the internal value instance +func (m Map) Value() *Value { + return &Value{data: m} +} + +// Nil represents a nil Map. +var Nil = New(nil) + +// New creates a new Map containing the map[string]interface{} in the data argument. +// If the data argument is not a map[string]interface, New attempts to call the +// MSI() method on the MSIConvertable interface to create one. +func New(data interface{}) Map { + if _, ok := data.(map[string]interface{}); !ok { + if converter, ok := data.(MSIConvertable); ok { + data = converter.MSI() + } else { + return nil + } + } + return Map(data.(map[string]interface{})) +} + +// MSI creates a map[string]interface{} and puts it inside a new Map. +// +// The arguments follow a key, value pattern. +// +// Panics +// +// Panics if any key argument is non-string or if there are an odd number of arguments. +// +// Example +// +// To easily create Maps: +// +// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true)) +// +// // creates an Map equivalent to +// m := objx.New(map[string]interface{}{"name": "Mat", "age": 29, "subobj": map[string]interface{}{"active": true}}) +func MSI(keyAndValuePairs ...interface{}) Map { + newMap := make(map[string]interface{}) + keyAndValuePairsLen := len(keyAndValuePairs) + if keyAndValuePairsLen%2 != 0 { + panic("objx: MSI must have an even number of arguments following the 'key, value' pattern.") + } + + for i := 0; i < keyAndValuePairsLen; i = i + 2 { + key := keyAndValuePairs[i] + value := keyAndValuePairs[i+1] + + // make sure the key is a string + keyString, keyStringOK := key.(string) + if !keyStringOK { + panic("objx: MSI must follow 'string, interface{}' pattern. " + keyString + " is not a valid key.") + } + newMap[keyString] = value + } + return New(newMap) +} + +// ****** Conversion Constructors + +// MustFromJSON creates a new Map containing the data specified in the +// jsonString. +// +// Panics if the JSON is invalid. +func MustFromJSON(jsonString string) Map { + o, err := FromJSON(jsonString) + if err != nil { + panic("objx: MustFromJSON failed with error: " + err.Error()) + } + return o +} + +// FromJSON creates a new Map containing the data specified in the +// jsonString. +// +// Returns an error if the JSON is invalid. +func FromJSON(jsonString string) (Map, error) { + var data interface{} + err := json.Unmarshal([]byte(jsonString), &data) + if err != nil { + return Nil, err + } + return New(data), nil +} + +// FromBase64 creates a new Obj containing the data specified +// in the Base64 string. +// +// The string is an encoded JSON string returned by Base64 +func FromBase64(base64String string) (Map, error) { + decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String)) + decoded, err := ioutil.ReadAll(decoder) + if err != nil { + return nil, err + } + return FromJSON(string(decoded)) +} + +// MustFromBase64 creates a new Obj containing the data specified +// in the Base64 string and panics if there is an error. +// +// The string is an encoded JSON string returned by Base64 +func MustFromBase64(base64String string) Map { + result, err := FromBase64(base64String) + if err != nil { + panic("objx: MustFromBase64 failed with error: " + err.Error()) + } + return result +} + +// FromSignedBase64 creates a new Obj containing the data specified +// in the Base64 string. +// +// The string is an encoded JSON string returned by SignedBase64 +func FromSignedBase64(base64String, key string) (Map, error) { + parts := strings.Split(base64String, SignatureSeparator) + if len(parts) != 2 { + return nil, errors.New("objx: Signed base64 string is malformed") + } + + sig := HashWithKey(parts[0], key) + if parts[1] != sig { + return nil, errors.New("objx: Signature for base64 data does not match") + } + return FromBase64(parts[0]) +} + +// MustFromSignedBase64 creates a new Obj containing the data specified +// in the Base64 string and panics if there is an error. +// +// The string is an encoded JSON string returned by Base64 +func MustFromSignedBase64(base64String, key string) Map { + result, err := FromSignedBase64(base64String, key) + if err != nil { + panic("objx: MustFromSignedBase64 failed with error: " + err.Error()) + } + return result +} + +// FromURLQuery generates a new Obj by parsing the specified +// query. +// +// For queries with multiple values, the first value is selected. +func FromURLQuery(query string) (Map, error) { + vals, err := url.ParseQuery(query) + if err != nil { + return nil, err + } + + m := make(map[string]interface{}) + for k, vals := range vals { + m[k] = vals[0] + } + return New(m), nil +} + +// MustFromURLQuery generates a new Obj by parsing the specified +// query. +// +// For queries with multiple values, the first value is selected. +// +// Panics if it encounters an error +func MustFromURLQuery(query string) Map { + o, err := FromURLQuery(query) + if err != nil { + panic("objx: MustFromURLQuery failed with error: " + err.Error()) + } + return o +} diff --git a/vendor/github.com/stretchr/objx/mutations.go b/vendor/github.com/stretchr/objx/mutations.go new file mode 100644 index 00000000..e7b8eb79 --- /dev/null +++ b/vendor/github.com/stretchr/objx/mutations.go @@ -0,0 +1,74 @@ +package objx + +// Exclude returns a new Map with the keys in the specified []string +// excluded. +func (m Map) Exclude(exclude []string) Map { + excluded := make(Map) + for k, v := range m { + var shouldInclude = true + for _, toExclude := range exclude { + if k == toExclude { + shouldInclude = false + break + } + } + if shouldInclude { + excluded[k] = v + } + } + return excluded +} + +// Copy creates a shallow copy of the Obj. +func (m Map) Copy() Map { + copied := make(map[string]interface{}) + for k, v := range m { + copied[k] = v + } + return New(copied) +} + +// Merge blends the specified map with a copy of this map and returns the result. +// +// Keys that appear in both will be selected from the specified map. +// This method requires that the wrapped object be a map[string]interface{} +func (m Map) Merge(merge Map) Map { + return m.Copy().MergeHere(merge) +} + +// MergeHere blends the specified map with this map and returns the current map. +// +// Keys that appear in both will be selected from the specified map. The original map +// will be modified. This method requires that +// the wrapped object be a map[string]interface{} +func (m Map) MergeHere(merge Map) Map { + for k, v := range merge { + m[k] = v + } + return m +} + +// Transform builds a new Obj giving the transformer a chance +// to change the keys and values as it goes. This method requires that +// the wrapped object be a map[string]interface{} +func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map { + newMap := make(map[string]interface{}) + for k, v := range m { + modifiedKey, modifiedVal := transformer(k, v) + newMap[modifiedKey] = modifiedVal + } + return New(newMap) +} + +// TransformKeys builds a new map using the specified key mapping. +// +// Unspecified keys will be unaltered. +// This method requires that the wrapped object be a map[string]interface{} +func (m Map) TransformKeys(mapping map[string]string) Map { + return m.Transform(func(key string, value interface{}) (string, interface{}) { + if newKey, ok := mapping[key]; ok { + return newKey, value + } + return key, value + }) +} diff --git a/vendor/github.com/stretchr/objx/security.go b/vendor/github.com/stretchr/objx/security.go new file mode 100644 index 00000000..e052ff89 --- /dev/null +++ b/vendor/github.com/stretchr/objx/security.go @@ -0,0 +1,17 @@ +package objx + +import ( + "crypto/sha1" + "encoding/hex" +) + +// HashWithKey hashes the specified string using the security +// key. +func HashWithKey(data, key string) string { + hash := sha1.New() + _, err := hash.Write([]byte(data + ":" + key)) + if err != nil { + return "" + } + return hex.EncodeToString(hash.Sum(nil)) +} diff --git a/vendor/github.com/stretchr/objx/tests.go b/vendor/github.com/stretchr/objx/tests.go new file mode 100644 index 00000000..d9e0b479 --- /dev/null +++ b/vendor/github.com/stretchr/objx/tests.go @@ -0,0 +1,17 @@ +package objx + +// Has gets whether there is something at the specified selector +// or not. +// +// If m is nil, Has will always return false. +func (m Map) Has(selector string) bool { + if m == nil { + return false + } + return !m.Get(selector).IsNil() +} + +// IsNil gets whether the data is nil or not. +func (v *Value) IsNil() bool { + return v == nil || v.data == nil +} diff --git a/vendor/github.com/stretchr/objx/type_specific_codegen.go b/vendor/github.com/stretchr/objx/type_specific_codegen.go new file mode 100644 index 00000000..202a91f8 --- /dev/null +++ b/vendor/github.com/stretchr/objx/type_specific_codegen.go @@ -0,0 +1,2501 @@ +package objx + +/* + Inter (interface{} and []interface{}) +*/ + +// Inter gets the value as a interface{}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Inter(optionalDefault ...interface{}) interface{} { + if s, ok := v.data.(interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInter gets the value as a interface{}. +// +// Panics if the object is not a interface{}. +func (v *Value) MustInter() interface{} { + return v.data.(interface{}) +} + +// InterSlice gets the value as a []interface{}, returns the optionalDefault +// value or nil if the value is not a []interface{}. +func (v *Value) InterSlice(optionalDefault ...[]interface{}) []interface{} { + if s, ok := v.data.([]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInterSlice gets the value as a []interface{}. +// +// Panics if the object is not a []interface{}. +func (v *Value) MustInterSlice() []interface{} { + return v.data.([]interface{}) +} + +// IsInter gets whether the object contained is a interface{} or not. +func (v *Value) IsInter() bool { + _, ok := v.data.(interface{}) + return ok +} + +// IsInterSlice gets whether the object contained is a []interface{} or not. +func (v *Value) IsInterSlice() bool { + _, ok := v.data.([]interface{}) + return ok +} + +// EachInter calls the specified callback for each object +// in the []interface{}. +// +// Panics if the object is the wrong type. +func (v *Value) EachInter(callback func(int, interface{}) bool) *Value { + for index, val := range v.MustInterSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInter uses the specified decider function to select items +// from the []interface{}. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInter(decider func(int, interface{}) bool) *Value { + var selected []interface{} + v.EachInter(func(index int, val interface{}) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInter uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]interface{}. +func (v *Value) GroupInter(grouper func(int, interface{}) string) *Value { + groups := make(map[string][]interface{}) + v.EachInter(func(index int, val interface{}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]interface{}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInter uses the specified function to replace each interface{}s +// by iterating each item. The data in the returned result will be a +// []interface{} containing the replaced items. +func (v *Value) ReplaceInter(replacer func(int, interface{}) interface{}) *Value { + arr := v.MustInterSlice() + replaced := make([]interface{}, len(arr)) + v.EachInter(func(index int, val interface{}) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInter uses the specified collector function to collect a value +// for each of the interface{}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInter(collector func(int, interface{}) interface{}) *Value { + arr := v.MustInterSlice() + collected := make([]interface{}, len(arr)) + v.EachInter(func(index int, val interface{}) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + MSI (map[string]interface{} and []map[string]interface{}) +*/ + +// MSI gets the value as a map[string]interface{}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) MSI(optionalDefault ...map[string]interface{}) map[string]interface{} { + if s, ok := v.data.(map[string]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustMSI gets the value as a map[string]interface{}. +// +// Panics if the object is not a map[string]interface{}. +func (v *Value) MustMSI() map[string]interface{} { + return v.data.(map[string]interface{}) +} + +// MSISlice gets the value as a []map[string]interface{}, returns the optionalDefault +// value or nil if the value is not a []map[string]interface{}. +func (v *Value) MSISlice(optionalDefault ...[]map[string]interface{}) []map[string]interface{} { + if s, ok := v.data.([]map[string]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustMSISlice gets the value as a []map[string]interface{}. +// +// Panics if the object is not a []map[string]interface{}. +func (v *Value) MustMSISlice() []map[string]interface{} { + return v.data.([]map[string]interface{}) +} + +// IsMSI gets whether the object contained is a map[string]interface{} or not. +func (v *Value) IsMSI() bool { + _, ok := v.data.(map[string]interface{}) + return ok +} + +// IsMSISlice gets whether the object contained is a []map[string]interface{} or not. +func (v *Value) IsMSISlice() bool { + _, ok := v.data.([]map[string]interface{}) + return ok +} + +// EachMSI calls the specified callback for each object +// in the []map[string]interface{}. +// +// Panics if the object is the wrong type. +func (v *Value) EachMSI(callback func(int, map[string]interface{}) bool) *Value { + for index, val := range v.MustMSISlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereMSI uses the specified decider function to select items +// from the []map[string]interface{}. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereMSI(decider func(int, map[string]interface{}) bool) *Value { + var selected []map[string]interface{} + v.EachMSI(func(index int, val map[string]interface{}) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupMSI uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]map[string]interface{}. +func (v *Value) GroupMSI(grouper func(int, map[string]interface{}) string) *Value { + groups := make(map[string][]map[string]interface{}) + v.EachMSI(func(index int, val map[string]interface{}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]map[string]interface{}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceMSI uses the specified function to replace each map[string]interface{}s +// by iterating each item. The data in the returned result will be a +// []map[string]interface{} containing the replaced items. +func (v *Value) ReplaceMSI(replacer func(int, map[string]interface{}) map[string]interface{}) *Value { + arr := v.MustMSISlice() + replaced := make([]map[string]interface{}, len(arr)) + v.EachMSI(func(index int, val map[string]interface{}) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectMSI uses the specified collector function to collect a value +// for each of the map[string]interface{}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectMSI(collector func(int, map[string]interface{}) interface{}) *Value { + arr := v.MustMSISlice() + collected := make([]interface{}, len(arr)) + v.EachMSI(func(index int, val map[string]interface{}) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + ObjxMap ((Map) and [](Map)) +*/ + +// ObjxMap gets the value as a (Map), returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) ObjxMap(optionalDefault ...(Map)) Map { + if s, ok := v.data.((Map)); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return New(nil) +} + +// MustObjxMap gets the value as a (Map). +// +// Panics if the object is not a (Map). +func (v *Value) MustObjxMap() Map { + return v.data.((Map)) +} + +// ObjxMapSlice gets the value as a [](Map), returns the optionalDefault +// value or nil if the value is not a [](Map). +func (v *Value) ObjxMapSlice(optionalDefault ...[](Map)) [](Map) { + if s, ok := v.data.([](Map)); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustObjxMapSlice gets the value as a [](Map). +// +// Panics if the object is not a [](Map). +func (v *Value) MustObjxMapSlice() [](Map) { + return v.data.([](Map)) +} + +// IsObjxMap gets whether the object contained is a (Map) or not. +func (v *Value) IsObjxMap() bool { + _, ok := v.data.((Map)) + return ok +} + +// IsObjxMapSlice gets whether the object contained is a [](Map) or not. +func (v *Value) IsObjxMapSlice() bool { + _, ok := v.data.([](Map)) + return ok +} + +// EachObjxMap calls the specified callback for each object +// in the [](Map). +// +// Panics if the object is the wrong type. +func (v *Value) EachObjxMap(callback func(int, Map) bool) *Value { + for index, val := range v.MustObjxMapSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereObjxMap uses the specified decider function to select items +// from the [](Map). The object contained in the result will contain +// only the selected items. +func (v *Value) WhereObjxMap(decider func(int, Map) bool) *Value { + var selected [](Map) + v.EachObjxMap(func(index int, val Map) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupObjxMap uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][](Map). +func (v *Value) GroupObjxMap(grouper func(int, Map) string) *Value { + groups := make(map[string][](Map)) + v.EachObjxMap(func(index int, val Map) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([](Map), 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceObjxMap uses the specified function to replace each (Map)s +// by iterating each item. The data in the returned result will be a +// [](Map) containing the replaced items. +func (v *Value) ReplaceObjxMap(replacer func(int, Map) Map) *Value { + arr := v.MustObjxMapSlice() + replaced := make([](Map), len(arr)) + v.EachObjxMap(func(index int, val Map) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectObjxMap uses the specified collector function to collect a value +// for each of the (Map)s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectObjxMap(collector func(int, Map) interface{}) *Value { + arr := v.MustObjxMapSlice() + collected := make([]interface{}, len(arr)) + v.EachObjxMap(func(index int, val Map) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Bool (bool and []bool) +*/ + +// Bool gets the value as a bool, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Bool(optionalDefault ...bool) bool { + if s, ok := v.data.(bool); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return false +} + +// MustBool gets the value as a bool. +// +// Panics if the object is not a bool. +func (v *Value) MustBool() bool { + return v.data.(bool) +} + +// BoolSlice gets the value as a []bool, returns the optionalDefault +// value or nil if the value is not a []bool. +func (v *Value) BoolSlice(optionalDefault ...[]bool) []bool { + if s, ok := v.data.([]bool); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustBoolSlice gets the value as a []bool. +// +// Panics if the object is not a []bool. +func (v *Value) MustBoolSlice() []bool { + return v.data.([]bool) +} + +// IsBool gets whether the object contained is a bool or not. +func (v *Value) IsBool() bool { + _, ok := v.data.(bool) + return ok +} + +// IsBoolSlice gets whether the object contained is a []bool or not. +func (v *Value) IsBoolSlice() bool { + _, ok := v.data.([]bool) + return ok +} + +// EachBool calls the specified callback for each object +// in the []bool. +// +// Panics if the object is the wrong type. +func (v *Value) EachBool(callback func(int, bool) bool) *Value { + for index, val := range v.MustBoolSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereBool uses the specified decider function to select items +// from the []bool. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereBool(decider func(int, bool) bool) *Value { + var selected []bool + v.EachBool(func(index int, val bool) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupBool uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]bool. +func (v *Value) GroupBool(grouper func(int, bool) string) *Value { + groups := make(map[string][]bool) + v.EachBool(func(index int, val bool) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]bool, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceBool uses the specified function to replace each bools +// by iterating each item. The data in the returned result will be a +// []bool containing the replaced items. +func (v *Value) ReplaceBool(replacer func(int, bool) bool) *Value { + arr := v.MustBoolSlice() + replaced := make([]bool, len(arr)) + v.EachBool(func(index int, val bool) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectBool uses the specified collector function to collect a value +// for each of the bools in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectBool(collector func(int, bool) interface{}) *Value { + arr := v.MustBoolSlice() + collected := make([]interface{}, len(arr)) + v.EachBool(func(index int, val bool) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Str (string and []string) +*/ + +// Str gets the value as a string, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Str(optionalDefault ...string) string { + if s, ok := v.data.(string); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return "" +} + +// MustStr gets the value as a string. +// +// Panics if the object is not a string. +func (v *Value) MustStr() string { + return v.data.(string) +} + +// StrSlice gets the value as a []string, returns the optionalDefault +// value or nil if the value is not a []string. +func (v *Value) StrSlice(optionalDefault ...[]string) []string { + if s, ok := v.data.([]string); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustStrSlice gets the value as a []string. +// +// Panics if the object is not a []string. +func (v *Value) MustStrSlice() []string { + return v.data.([]string) +} + +// IsStr gets whether the object contained is a string or not. +func (v *Value) IsStr() bool { + _, ok := v.data.(string) + return ok +} + +// IsStrSlice gets whether the object contained is a []string or not. +func (v *Value) IsStrSlice() bool { + _, ok := v.data.([]string) + return ok +} + +// EachStr calls the specified callback for each object +// in the []string. +// +// Panics if the object is the wrong type. +func (v *Value) EachStr(callback func(int, string) bool) *Value { + for index, val := range v.MustStrSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereStr uses the specified decider function to select items +// from the []string. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereStr(decider func(int, string) bool) *Value { + var selected []string + v.EachStr(func(index int, val string) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupStr uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]string. +func (v *Value) GroupStr(grouper func(int, string) string) *Value { + groups := make(map[string][]string) + v.EachStr(func(index int, val string) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]string, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceStr uses the specified function to replace each strings +// by iterating each item. The data in the returned result will be a +// []string containing the replaced items. +func (v *Value) ReplaceStr(replacer func(int, string) string) *Value { + arr := v.MustStrSlice() + replaced := make([]string, len(arr)) + v.EachStr(func(index int, val string) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectStr uses the specified collector function to collect a value +// for each of the strings in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectStr(collector func(int, string) interface{}) *Value { + arr := v.MustStrSlice() + collected := make([]interface{}, len(arr)) + v.EachStr(func(index int, val string) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int (int and []int) +*/ + +// Int gets the value as a int, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int(optionalDefault ...int) int { + if s, ok := v.data.(int); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt gets the value as a int. +// +// Panics if the object is not a int. +func (v *Value) MustInt() int { + return v.data.(int) +} + +// IntSlice gets the value as a []int, returns the optionalDefault +// value or nil if the value is not a []int. +func (v *Value) IntSlice(optionalDefault ...[]int) []int { + if s, ok := v.data.([]int); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustIntSlice gets the value as a []int. +// +// Panics if the object is not a []int. +func (v *Value) MustIntSlice() []int { + return v.data.([]int) +} + +// IsInt gets whether the object contained is a int or not. +func (v *Value) IsInt() bool { + _, ok := v.data.(int) + return ok +} + +// IsIntSlice gets whether the object contained is a []int or not. +func (v *Value) IsIntSlice() bool { + _, ok := v.data.([]int) + return ok +} + +// EachInt calls the specified callback for each object +// in the []int. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt(callback func(int, int) bool) *Value { + for index, val := range v.MustIntSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt uses the specified decider function to select items +// from the []int. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt(decider func(int, int) bool) *Value { + var selected []int + v.EachInt(func(index int, val int) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int. +func (v *Value) GroupInt(grouper func(int, int) string) *Value { + groups := make(map[string][]int) + v.EachInt(func(index int, val int) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt uses the specified function to replace each ints +// by iterating each item. The data in the returned result will be a +// []int containing the replaced items. +func (v *Value) ReplaceInt(replacer func(int, int) int) *Value { + arr := v.MustIntSlice() + replaced := make([]int, len(arr)) + v.EachInt(func(index int, val int) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt uses the specified collector function to collect a value +// for each of the ints in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt(collector func(int, int) interface{}) *Value { + arr := v.MustIntSlice() + collected := make([]interface{}, len(arr)) + v.EachInt(func(index int, val int) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int8 (int8 and []int8) +*/ + +// Int8 gets the value as a int8, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int8(optionalDefault ...int8) int8 { + if s, ok := v.data.(int8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt8 gets the value as a int8. +// +// Panics if the object is not a int8. +func (v *Value) MustInt8() int8 { + return v.data.(int8) +} + +// Int8Slice gets the value as a []int8, returns the optionalDefault +// value or nil if the value is not a []int8. +func (v *Value) Int8Slice(optionalDefault ...[]int8) []int8 { + if s, ok := v.data.([]int8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt8Slice gets the value as a []int8. +// +// Panics if the object is not a []int8. +func (v *Value) MustInt8Slice() []int8 { + return v.data.([]int8) +} + +// IsInt8 gets whether the object contained is a int8 or not. +func (v *Value) IsInt8() bool { + _, ok := v.data.(int8) + return ok +} + +// IsInt8Slice gets whether the object contained is a []int8 or not. +func (v *Value) IsInt8Slice() bool { + _, ok := v.data.([]int8) + return ok +} + +// EachInt8 calls the specified callback for each object +// in the []int8. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt8(callback func(int, int8) bool) *Value { + for index, val := range v.MustInt8Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt8 uses the specified decider function to select items +// from the []int8. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt8(decider func(int, int8) bool) *Value { + var selected []int8 + v.EachInt8(func(index int, val int8) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt8 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int8. +func (v *Value) GroupInt8(grouper func(int, int8) string) *Value { + groups := make(map[string][]int8) + v.EachInt8(func(index int, val int8) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int8, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt8 uses the specified function to replace each int8s +// by iterating each item. The data in the returned result will be a +// []int8 containing the replaced items. +func (v *Value) ReplaceInt8(replacer func(int, int8) int8) *Value { + arr := v.MustInt8Slice() + replaced := make([]int8, len(arr)) + v.EachInt8(func(index int, val int8) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt8 uses the specified collector function to collect a value +// for each of the int8s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt8(collector func(int, int8) interface{}) *Value { + arr := v.MustInt8Slice() + collected := make([]interface{}, len(arr)) + v.EachInt8(func(index int, val int8) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int16 (int16 and []int16) +*/ + +// Int16 gets the value as a int16, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int16(optionalDefault ...int16) int16 { + if s, ok := v.data.(int16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt16 gets the value as a int16. +// +// Panics if the object is not a int16. +func (v *Value) MustInt16() int16 { + return v.data.(int16) +} + +// Int16Slice gets the value as a []int16, returns the optionalDefault +// value or nil if the value is not a []int16. +func (v *Value) Int16Slice(optionalDefault ...[]int16) []int16 { + if s, ok := v.data.([]int16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt16Slice gets the value as a []int16. +// +// Panics if the object is not a []int16. +func (v *Value) MustInt16Slice() []int16 { + return v.data.([]int16) +} + +// IsInt16 gets whether the object contained is a int16 or not. +func (v *Value) IsInt16() bool { + _, ok := v.data.(int16) + return ok +} + +// IsInt16Slice gets whether the object contained is a []int16 or not. +func (v *Value) IsInt16Slice() bool { + _, ok := v.data.([]int16) + return ok +} + +// EachInt16 calls the specified callback for each object +// in the []int16. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt16(callback func(int, int16) bool) *Value { + for index, val := range v.MustInt16Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt16 uses the specified decider function to select items +// from the []int16. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt16(decider func(int, int16) bool) *Value { + var selected []int16 + v.EachInt16(func(index int, val int16) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt16 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int16. +func (v *Value) GroupInt16(grouper func(int, int16) string) *Value { + groups := make(map[string][]int16) + v.EachInt16(func(index int, val int16) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int16, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt16 uses the specified function to replace each int16s +// by iterating each item. The data in the returned result will be a +// []int16 containing the replaced items. +func (v *Value) ReplaceInt16(replacer func(int, int16) int16) *Value { + arr := v.MustInt16Slice() + replaced := make([]int16, len(arr)) + v.EachInt16(func(index int, val int16) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt16 uses the specified collector function to collect a value +// for each of the int16s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt16(collector func(int, int16) interface{}) *Value { + arr := v.MustInt16Slice() + collected := make([]interface{}, len(arr)) + v.EachInt16(func(index int, val int16) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int32 (int32 and []int32) +*/ + +// Int32 gets the value as a int32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int32(optionalDefault ...int32) int32 { + if s, ok := v.data.(int32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt32 gets the value as a int32. +// +// Panics if the object is not a int32. +func (v *Value) MustInt32() int32 { + return v.data.(int32) +} + +// Int32Slice gets the value as a []int32, returns the optionalDefault +// value or nil if the value is not a []int32. +func (v *Value) Int32Slice(optionalDefault ...[]int32) []int32 { + if s, ok := v.data.([]int32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt32Slice gets the value as a []int32. +// +// Panics if the object is not a []int32. +func (v *Value) MustInt32Slice() []int32 { + return v.data.([]int32) +} + +// IsInt32 gets whether the object contained is a int32 or not. +func (v *Value) IsInt32() bool { + _, ok := v.data.(int32) + return ok +} + +// IsInt32Slice gets whether the object contained is a []int32 or not. +func (v *Value) IsInt32Slice() bool { + _, ok := v.data.([]int32) + return ok +} + +// EachInt32 calls the specified callback for each object +// in the []int32. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt32(callback func(int, int32) bool) *Value { + for index, val := range v.MustInt32Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt32 uses the specified decider function to select items +// from the []int32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt32(decider func(int, int32) bool) *Value { + var selected []int32 + v.EachInt32(func(index int, val int32) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int32. +func (v *Value) GroupInt32(grouper func(int, int32) string) *Value { + groups := make(map[string][]int32) + v.EachInt32(func(index int, val int32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt32 uses the specified function to replace each int32s +// by iterating each item. The data in the returned result will be a +// []int32 containing the replaced items. +func (v *Value) ReplaceInt32(replacer func(int, int32) int32) *Value { + arr := v.MustInt32Slice() + replaced := make([]int32, len(arr)) + v.EachInt32(func(index int, val int32) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt32 uses the specified collector function to collect a value +// for each of the int32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt32(collector func(int, int32) interface{}) *Value { + arr := v.MustInt32Slice() + collected := make([]interface{}, len(arr)) + v.EachInt32(func(index int, val int32) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int64 (int64 and []int64) +*/ + +// Int64 gets the value as a int64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int64(optionalDefault ...int64) int64 { + if s, ok := v.data.(int64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt64 gets the value as a int64. +// +// Panics if the object is not a int64. +func (v *Value) MustInt64() int64 { + return v.data.(int64) +} + +// Int64Slice gets the value as a []int64, returns the optionalDefault +// value or nil if the value is not a []int64. +func (v *Value) Int64Slice(optionalDefault ...[]int64) []int64 { + if s, ok := v.data.([]int64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt64Slice gets the value as a []int64. +// +// Panics if the object is not a []int64. +func (v *Value) MustInt64Slice() []int64 { + return v.data.([]int64) +} + +// IsInt64 gets whether the object contained is a int64 or not. +func (v *Value) IsInt64() bool { + _, ok := v.data.(int64) + return ok +} + +// IsInt64Slice gets whether the object contained is a []int64 or not. +func (v *Value) IsInt64Slice() bool { + _, ok := v.data.([]int64) + return ok +} + +// EachInt64 calls the specified callback for each object +// in the []int64. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt64(callback func(int, int64) bool) *Value { + for index, val := range v.MustInt64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt64 uses the specified decider function to select items +// from the []int64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt64(decider func(int, int64) bool) *Value { + var selected []int64 + v.EachInt64(func(index int, val int64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int64. +func (v *Value) GroupInt64(grouper func(int, int64) string) *Value { + groups := make(map[string][]int64) + v.EachInt64(func(index int, val int64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt64 uses the specified function to replace each int64s +// by iterating each item. The data in the returned result will be a +// []int64 containing the replaced items. +func (v *Value) ReplaceInt64(replacer func(int, int64) int64) *Value { + arr := v.MustInt64Slice() + replaced := make([]int64, len(arr)) + v.EachInt64(func(index int, val int64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt64 uses the specified collector function to collect a value +// for each of the int64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt64(collector func(int, int64) interface{}) *Value { + arr := v.MustInt64Slice() + collected := make([]interface{}, len(arr)) + v.EachInt64(func(index int, val int64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint (uint and []uint) +*/ + +// Uint gets the value as a uint, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint(optionalDefault ...uint) uint { + if s, ok := v.data.(uint); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint gets the value as a uint. +// +// Panics if the object is not a uint. +func (v *Value) MustUint() uint { + return v.data.(uint) +} + +// UintSlice gets the value as a []uint, returns the optionalDefault +// value or nil if the value is not a []uint. +func (v *Value) UintSlice(optionalDefault ...[]uint) []uint { + if s, ok := v.data.([]uint); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUintSlice gets the value as a []uint. +// +// Panics if the object is not a []uint. +func (v *Value) MustUintSlice() []uint { + return v.data.([]uint) +} + +// IsUint gets whether the object contained is a uint or not. +func (v *Value) IsUint() bool { + _, ok := v.data.(uint) + return ok +} + +// IsUintSlice gets whether the object contained is a []uint or not. +func (v *Value) IsUintSlice() bool { + _, ok := v.data.([]uint) + return ok +} + +// EachUint calls the specified callback for each object +// in the []uint. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint(callback func(int, uint) bool) *Value { + for index, val := range v.MustUintSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint uses the specified decider function to select items +// from the []uint. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint(decider func(int, uint) bool) *Value { + var selected []uint + v.EachUint(func(index int, val uint) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint. +func (v *Value) GroupUint(grouper func(int, uint) string) *Value { + groups := make(map[string][]uint) + v.EachUint(func(index int, val uint) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint uses the specified function to replace each uints +// by iterating each item. The data in the returned result will be a +// []uint containing the replaced items. +func (v *Value) ReplaceUint(replacer func(int, uint) uint) *Value { + arr := v.MustUintSlice() + replaced := make([]uint, len(arr)) + v.EachUint(func(index int, val uint) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint uses the specified collector function to collect a value +// for each of the uints in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint(collector func(int, uint) interface{}) *Value { + arr := v.MustUintSlice() + collected := make([]interface{}, len(arr)) + v.EachUint(func(index int, val uint) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint8 (uint8 and []uint8) +*/ + +// Uint8 gets the value as a uint8, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint8(optionalDefault ...uint8) uint8 { + if s, ok := v.data.(uint8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint8 gets the value as a uint8. +// +// Panics if the object is not a uint8. +func (v *Value) MustUint8() uint8 { + return v.data.(uint8) +} + +// Uint8Slice gets the value as a []uint8, returns the optionalDefault +// value or nil if the value is not a []uint8. +func (v *Value) Uint8Slice(optionalDefault ...[]uint8) []uint8 { + if s, ok := v.data.([]uint8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint8Slice gets the value as a []uint8. +// +// Panics if the object is not a []uint8. +func (v *Value) MustUint8Slice() []uint8 { + return v.data.([]uint8) +} + +// IsUint8 gets whether the object contained is a uint8 or not. +func (v *Value) IsUint8() bool { + _, ok := v.data.(uint8) + return ok +} + +// IsUint8Slice gets whether the object contained is a []uint8 or not. +func (v *Value) IsUint8Slice() bool { + _, ok := v.data.([]uint8) + return ok +} + +// EachUint8 calls the specified callback for each object +// in the []uint8. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint8(callback func(int, uint8) bool) *Value { + for index, val := range v.MustUint8Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint8 uses the specified decider function to select items +// from the []uint8. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint8(decider func(int, uint8) bool) *Value { + var selected []uint8 + v.EachUint8(func(index int, val uint8) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint8 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint8. +func (v *Value) GroupUint8(grouper func(int, uint8) string) *Value { + groups := make(map[string][]uint8) + v.EachUint8(func(index int, val uint8) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint8, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint8 uses the specified function to replace each uint8s +// by iterating each item. The data in the returned result will be a +// []uint8 containing the replaced items. +func (v *Value) ReplaceUint8(replacer func(int, uint8) uint8) *Value { + arr := v.MustUint8Slice() + replaced := make([]uint8, len(arr)) + v.EachUint8(func(index int, val uint8) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint8 uses the specified collector function to collect a value +// for each of the uint8s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint8(collector func(int, uint8) interface{}) *Value { + arr := v.MustUint8Slice() + collected := make([]interface{}, len(arr)) + v.EachUint8(func(index int, val uint8) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint16 (uint16 and []uint16) +*/ + +// Uint16 gets the value as a uint16, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint16(optionalDefault ...uint16) uint16 { + if s, ok := v.data.(uint16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint16 gets the value as a uint16. +// +// Panics if the object is not a uint16. +func (v *Value) MustUint16() uint16 { + return v.data.(uint16) +} + +// Uint16Slice gets the value as a []uint16, returns the optionalDefault +// value or nil if the value is not a []uint16. +func (v *Value) Uint16Slice(optionalDefault ...[]uint16) []uint16 { + if s, ok := v.data.([]uint16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint16Slice gets the value as a []uint16. +// +// Panics if the object is not a []uint16. +func (v *Value) MustUint16Slice() []uint16 { + return v.data.([]uint16) +} + +// IsUint16 gets whether the object contained is a uint16 or not. +func (v *Value) IsUint16() bool { + _, ok := v.data.(uint16) + return ok +} + +// IsUint16Slice gets whether the object contained is a []uint16 or not. +func (v *Value) IsUint16Slice() bool { + _, ok := v.data.([]uint16) + return ok +} + +// EachUint16 calls the specified callback for each object +// in the []uint16. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint16(callback func(int, uint16) bool) *Value { + for index, val := range v.MustUint16Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint16 uses the specified decider function to select items +// from the []uint16. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint16(decider func(int, uint16) bool) *Value { + var selected []uint16 + v.EachUint16(func(index int, val uint16) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint16 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint16. +func (v *Value) GroupUint16(grouper func(int, uint16) string) *Value { + groups := make(map[string][]uint16) + v.EachUint16(func(index int, val uint16) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint16, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint16 uses the specified function to replace each uint16s +// by iterating each item. The data in the returned result will be a +// []uint16 containing the replaced items. +func (v *Value) ReplaceUint16(replacer func(int, uint16) uint16) *Value { + arr := v.MustUint16Slice() + replaced := make([]uint16, len(arr)) + v.EachUint16(func(index int, val uint16) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint16 uses the specified collector function to collect a value +// for each of the uint16s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint16(collector func(int, uint16) interface{}) *Value { + arr := v.MustUint16Slice() + collected := make([]interface{}, len(arr)) + v.EachUint16(func(index int, val uint16) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint32 (uint32 and []uint32) +*/ + +// Uint32 gets the value as a uint32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint32(optionalDefault ...uint32) uint32 { + if s, ok := v.data.(uint32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint32 gets the value as a uint32. +// +// Panics if the object is not a uint32. +func (v *Value) MustUint32() uint32 { + return v.data.(uint32) +} + +// Uint32Slice gets the value as a []uint32, returns the optionalDefault +// value or nil if the value is not a []uint32. +func (v *Value) Uint32Slice(optionalDefault ...[]uint32) []uint32 { + if s, ok := v.data.([]uint32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint32Slice gets the value as a []uint32. +// +// Panics if the object is not a []uint32. +func (v *Value) MustUint32Slice() []uint32 { + return v.data.([]uint32) +} + +// IsUint32 gets whether the object contained is a uint32 or not. +func (v *Value) IsUint32() bool { + _, ok := v.data.(uint32) + return ok +} + +// IsUint32Slice gets whether the object contained is a []uint32 or not. +func (v *Value) IsUint32Slice() bool { + _, ok := v.data.([]uint32) + return ok +} + +// EachUint32 calls the specified callback for each object +// in the []uint32. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint32(callback func(int, uint32) bool) *Value { + for index, val := range v.MustUint32Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint32 uses the specified decider function to select items +// from the []uint32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint32(decider func(int, uint32) bool) *Value { + var selected []uint32 + v.EachUint32(func(index int, val uint32) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint32. +func (v *Value) GroupUint32(grouper func(int, uint32) string) *Value { + groups := make(map[string][]uint32) + v.EachUint32(func(index int, val uint32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint32 uses the specified function to replace each uint32s +// by iterating each item. The data in the returned result will be a +// []uint32 containing the replaced items. +func (v *Value) ReplaceUint32(replacer func(int, uint32) uint32) *Value { + arr := v.MustUint32Slice() + replaced := make([]uint32, len(arr)) + v.EachUint32(func(index int, val uint32) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint32 uses the specified collector function to collect a value +// for each of the uint32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint32(collector func(int, uint32) interface{}) *Value { + arr := v.MustUint32Slice() + collected := make([]interface{}, len(arr)) + v.EachUint32(func(index int, val uint32) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint64 (uint64 and []uint64) +*/ + +// Uint64 gets the value as a uint64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint64(optionalDefault ...uint64) uint64 { + if s, ok := v.data.(uint64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint64 gets the value as a uint64. +// +// Panics if the object is not a uint64. +func (v *Value) MustUint64() uint64 { + return v.data.(uint64) +} + +// Uint64Slice gets the value as a []uint64, returns the optionalDefault +// value or nil if the value is not a []uint64. +func (v *Value) Uint64Slice(optionalDefault ...[]uint64) []uint64 { + if s, ok := v.data.([]uint64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint64Slice gets the value as a []uint64. +// +// Panics if the object is not a []uint64. +func (v *Value) MustUint64Slice() []uint64 { + return v.data.([]uint64) +} + +// IsUint64 gets whether the object contained is a uint64 or not. +func (v *Value) IsUint64() bool { + _, ok := v.data.(uint64) + return ok +} + +// IsUint64Slice gets whether the object contained is a []uint64 or not. +func (v *Value) IsUint64Slice() bool { + _, ok := v.data.([]uint64) + return ok +} + +// EachUint64 calls the specified callback for each object +// in the []uint64. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint64(callback func(int, uint64) bool) *Value { + for index, val := range v.MustUint64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint64 uses the specified decider function to select items +// from the []uint64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint64(decider func(int, uint64) bool) *Value { + var selected []uint64 + v.EachUint64(func(index int, val uint64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint64. +func (v *Value) GroupUint64(grouper func(int, uint64) string) *Value { + groups := make(map[string][]uint64) + v.EachUint64(func(index int, val uint64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint64 uses the specified function to replace each uint64s +// by iterating each item. The data in the returned result will be a +// []uint64 containing the replaced items. +func (v *Value) ReplaceUint64(replacer func(int, uint64) uint64) *Value { + arr := v.MustUint64Slice() + replaced := make([]uint64, len(arr)) + v.EachUint64(func(index int, val uint64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint64 uses the specified collector function to collect a value +// for each of the uint64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint64(collector func(int, uint64) interface{}) *Value { + arr := v.MustUint64Slice() + collected := make([]interface{}, len(arr)) + v.EachUint64(func(index int, val uint64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uintptr (uintptr and []uintptr) +*/ + +// Uintptr gets the value as a uintptr, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uintptr(optionalDefault ...uintptr) uintptr { + if s, ok := v.data.(uintptr); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUintptr gets the value as a uintptr. +// +// Panics if the object is not a uintptr. +func (v *Value) MustUintptr() uintptr { + return v.data.(uintptr) +} + +// UintptrSlice gets the value as a []uintptr, returns the optionalDefault +// value or nil if the value is not a []uintptr. +func (v *Value) UintptrSlice(optionalDefault ...[]uintptr) []uintptr { + if s, ok := v.data.([]uintptr); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUintptrSlice gets the value as a []uintptr. +// +// Panics if the object is not a []uintptr. +func (v *Value) MustUintptrSlice() []uintptr { + return v.data.([]uintptr) +} + +// IsUintptr gets whether the object contained is a uintptr or not. +func (v *Value) IsUintptr() bool { + _, ok := v.data.(uintptr) + return ok +} + +// IsUintptrSlice gets whether the object contained is a []uintptr or not. +func (v *Value) IsUintptrSlice() bool { + _, ok := v.data.([]uintptr) + return ok +} + +// EachUintptr calls the specified callback for each object +// in the []uintptr. +// +// Panics if the object is the wrong type. +func (v *Value) EachUintptr(callback func(int, uintptr) bool) *Value { + for index, val := range v.MustUintptrSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUintptr uses the specified decider function to select items +// from the []uintptr. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUintptr(decider func(int, uintptr) bool) *Value { + var selected []uintptr + v.EachUintptr(func(index int, val uintptr) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUintptr uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uintptr. +func (v *Value) GroupUintptr(grouper func(int, uintptr) string) *Value { + groups := make(map[string][]uintptr) + v.EachUintptr(func(index int, val uintptr) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uintptr, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUintptr uses the specified function to replace each uintptrs +// by iterating each item. The data in the returned result will be a +// []uintptr containing the replaced items. +func (v *Value) ReplaceUintptr(replacer func(int, uintptr) uintptr) *Value { + arr := v.MustUintptrSlice() + replaced := make([]uintptr, len(arr)) + v.EachUintptr(func(index int, val uintptr) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUintptr uses the specified collector function to collect a value +// for each of the uintptrs in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUintptr(collector func(int, uintptr) interface{}) *Value { + arr := v.MustUintptrSlice() + collected := make([]interface{}, len(arr)) + v.EachUintptr(func(index int, val uintptr) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Float32 (float32 and []float32) +*/ + +// Float32 gets the value as a float32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Float32(optionalDefault ...float32) float32 { + if s, ok := v.data.(float32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustFloat32 gets the value as a float32. +// +// Panics if the object is not a float32. +func (v *Value) MustFloat32() float32 { + return v.data.(float32) +} + +// Float32Slice gets the value as a []float32, returns the optionalDefault +// value or nil if the value is not a []float32. +func (v *Value) Float32Slice(optionalDefault ...[]float32) []float32 { + if s, ok := v.data.([]float32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustFloat32Slice gets the value as a []float32. +// +// Panics if the object is not a []float32. +func (v *Value) MustFloat32Slice() []float32 { + return v.data.([]float32) +} + +// IsFloat32 gets whether the object contained is a float32 or not. +func (v *Value) IsFloat32() bool { + _, ok := v.data.(float32) + return ok +} + +// IsFloat32Slice gets whether the object contained is a []float32 or not. +func (v *Value) IsFloat32Slice() bool { + _, ok := v.data.([]float32) + return ok +} + +// EachFloat32 calls the specified callback for each object +// in the []float32. +// +// Panics if the object is the wrong type. +func (v *Value) EachFloat32(callback func(int, float32) bool) *Value { + for index, val := range v.MustFloat32Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereFloat32 uses the specified decider function to select items +// from the []float32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereFloat32(decider func(int, float32) bool) *Value { + var selected []float32 + v.EachFloat32(func(index int, val float32) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupFloat32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]float32. +func (v *Value) GroupFloat32(grouper func(int, float32) string) *Value { + groups := make(map[string][]float32) + v.EachFloat32(func(index int, val float32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]float32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceFloat32 uses the specified function to replace each float32s +// by iterating each item. The data in the returned result will be a +// []float32 containing the replaced items. +func (v *Value) ReplaceFloat32(replacer func(int, float32) float32) *Value { + arr := v.MustFloat32Slice() + replaced := make([]float32, len(arr)) + v.EachFloat32(func(index int, val float32) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectFloat32 uses the specified collector function to collect a value +// for each of the float32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectFloat32(collector func(int, float32) interface{}) *Value { + arr := v.MustFloat32Slice() + collected := make([]interface{}, len(arr)) + v.EachFloat32(func(index int, val float32) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Float64 (float64 and []float64) +*/ + +// Float64 gets the value as a float64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Float64(optionalDefault ...float64) float64 { + if s, ok := v.data.(float64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustFloat64 gets the value as a float64. +// +// Panics if the object is not a float64. +func (v *Value) MustFloat64() float64 { + return v.data.(float64) +} + +// Float64Slice gets the value as a []float64, returns the optionalDefault +// value or nil if the value is not a []float64. +func (v *Value) Float64Slice(optionalDefault ...[]float64) []float64 { + if s, ok := v.data.([]float64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustFloat64Slice gets the value as a []float64. +// +// Panics if the object is not a []float64. +func (v *Value) MustFloat64Slice() []float64 { + return v.data.([]float64) +} + +// IsFloat64 gets whether the object contained is a float64 or not. +func (v *Value) IsFloat64() bool { + _, ok := v.data.(float64) + return ok +} + +// IsFloat64Slice gets whether the object contained is a []float64 or not. +func (v *Value) IsFloat64Slice() bool { + _, ok := v.data.([]float64) + return ok +} + +// EachFloat64 calls the specified callback for each object +// in the []float64. +// +// Panics if the object is the wrong type. +func (v *Value) EachFloat64(callback func(int, float64) bool) *Value { + for index, val := range v.MustFloat64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereFloat64 uses the specified decider function to select items +// from the []float64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereFloat64(decider func(int, float64) bool) *Value { + var selected []float64 + v.EachFloat64(func(index int, val float64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupFloat64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]float64. +func (v *Value) GroupFloat64(grouper func(int, float64) string) *Value { + groups := make(map[string][]float64) + v.EachFloat64(func(index int, val float64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]float64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceFloat64 uses the specified function to replace each float64s +// by iterating each item. The data in the returned result will be a +// []float64 containing the replaced items. +func (v *Value) ReplaceFloat64(replacer func(int, float64) float64) *Value { + arr := v.MustFloat64Slice() + replaced := make([]float64, len(arr)) + v.EachFloat64(func(index int, val float64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectFloat64 uses the specified collector function to collect a value +// for each of the float64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectFloat64(collector func(int, float64) interface{}) *Value { + arr := v.MustFloat64Slice() + collected := make([]interface{}, len(arr)) + v.EachFloat64(func(index int, val float64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Complex64 (complex64 and []complex64) +*/ + +// Complex64 gets the value as a complex64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Complex64(optionalDefault ...complex64) complex64 { + if s, ok := v.data.(complex64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustComplex64 gets the value as a complex64. +// +// Panics if the object is not a complex64. +func (v *Value) MustComplex64() complex64 { + return v.data.(complex64) +} + +// Complex64Slice gets the value as a []complex64, returns the optionalDefault +// value or nil if the value is not a []complex64. +func (v *Value) Complex64Slice(optionalDefault ...[]complex64) []complex64 { + if s, ok := v.data.([]complex64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustComplex64Slice gets the value as a []complex64. +// +// Panics if the object is not a []complex64. +func (v *Value) MustComplex64Slice() []complex64 { + return v.data.([]complex64) +} + +// IsComplex64 gets whether the object contained is a complex64 or not. +func (v *Value) IsComplex64() bool { + _, ok := v.data.(complex64) + return ok +} + +// IsComplex64Slice gets whether the object contained is a []complex64 or not. +func (v *Value) IsComplex64Slice() bool { + _, ok := v.data.([]complex64) + return ok +} + +// EachComplex64 calls the specified callback for each object +// in the []complex64. +// +// Panics if the object is the wrong type. +func (v *Value) EachComplex64(callback func(int, complex64) bool) *Value { + for index, val := range v.MustComplex64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereComplex64 uses the specified decider function to select items +// from the []complex64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereComplex64(decider func(int, complex64) bool) *Value { + var selected []complex64 + v.EachComplex64(func(index int, val complex64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupComplex64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]complex64. +func (v *Value) GroupComplex64(grouper func(int, complex64) string) *Value { + groups := make(map[string][]complex64) + v.EachComplex64(func(index int, val complex64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]complex64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceComplex64 uses the specified function to replace each complex64s +// by iterating each item. The data in the returned result will be a +// []complex64 containing the replaced items. +func (v *Value) ReplaceComplex64(replacer func(int, complex64) complex64) *Value { + arr := v.MustComplex64Slice() + replaced := make([]complex64, len(arr)) + v.EachComplex64(func(index int, val complex64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectComplex64 uses the specified collector function to collect a value +// for each of the complex64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectComplex64(collector func(int, complex64) interface{}) *Value { + arr := v.MustComplex64Slice() + collected := make([]interface{}, len(arr)) + v.EachComplex64(func(index int, val complex64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Complex128 (complex128 and []complex128) +*/ + +// Complex128 gets the value as a complex128, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Complex128(optionalDefault ...complex128) complex128 { + if s, ok := v.data.(complex128); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustComplex128 gets the value as a complex128. +// +// Panics if the object is not a complex128. +func (v *Value) MustComplex128() complex128 { + return v.data.(complex128) +} + +// Complex128Slice gets the value as a []complex128, returns the optionalDefault +// value or nil if the value is not a []complex128. +func (v *Value) Complex128Slice(optionalDefault ...[]complex128) []complex128 { + if s, ok := v.data.([]complex128); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustComplex128Slice gets the value as a []complex128. +// +// Panics if the object is not a []complex128. +func (v *Value) MustComplex128Slice() []complex128 { + return v.data.([]complex128) +} + +// IsComplex128 gets whether the object contained is a complex128 or not. +func (v *Value) IsComplex128() bool { + _, ok := v.data.(complex128) + return ok +} + +// IsComplex128Slice gets whether the object contained is a []complex128 or not. +func (v *Value) IsComplex128Slice() bool { + _, ok := v.data.([]complex128) + return ok +} + +// EachComplex128 calls the specified callback for each object +// in the []complex128. +// +// Panics if the object is the wrong type. +func (v *Value) EachComplex128(callback func(int, complex128) bool) *Value { + for index, val := range v.MustComplex128Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereComplex128 uses the specified decider function to select items +// from the []complex128. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereComplex128(decider func(int, complex128) bool) *Value { + var selected []complex128 + v.EachComplex128(func(index int, val complex128) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupComplex128 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]complex128. +func (v *Value) GroupComplex128(grouper func(int, complex128) string) *Value { + groups := make(map[string][]complex128) + v.EachComplex128(func(index int, val complex128) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]complex128, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceComplex128 uses the specified function to replace each complex128s +// by iterating each item. The data in the returned result will be a +// []complex128 containing the replaced items. +func (v *Value) ReplaceComplex128(replacer func(int, complex128) complex128) *Value { + arr := v.MustComplex128Slice() + replaced := make([]complex128, len(arr)) + v.EachComplex128(func(index int, val complex128) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectComplex128 uses the specified collector function to collect a value +// for each of the complex128s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectComplex128(collector func(int, complex128) interface{}) *Value { + arr := v.MustComplex128Slice() + collected := make([]interface{}, len(arr)) + v.EachComplex128(func(index int, val complex128) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} diff --git a/vendor/github.com/stretchr/objx/value.go b/vendor/github.com/stretchr/objx/value.go new file mode 100644 index 00000000..956a2211 --- /dev/null +++ b/vendor/github.com/stretchr/objx/value.go @@ -0,0 +1,56 @@ +package objx + +import ( + "fmt" + "strconv" +) + +// Value provides methods for extracting interface{} data in various +// types. +type Value struct { + // data contains the raw data being managed by this Value + data interface{} +} + +// Data returns the raw data contained by this Value +func (v *Value) Data() interface{} { + return v.data +} + +// String returns the value always as a string +func (v *Value) String() string { + switch { + case v.IsStr(): + return v.Str() + case v.IsBool(): + return strconv.FormatBool(v.Bool()) + case v.IsFloat32(): + return strconv.FormatFloat(float64(v.Float32()), 'f', -1, 32) + case v.IsFloat64(): + return strconv.FormatFloat(v.Float64(), 'f', -1, 64) + case v.IsInt(): + return strconv.FormatInt(int64(v.Int()), 10) + case v.IsInt(): + return strconv.FormatInt(int64(v.Int()), 10) + case v.IsInt8(): + return strconv.FormatInt(int64(v.Int8()), 10) + case v.IsInt16(): + return strconv.FormatInt(int64(v.Int16()), 10) + case v.IsInt32(): + return strconv.FormatInt(int64(v.Int32()), 10) + case v.IsInt64(): + return strconv.FormatInt(v.Int64(), 10) + case v.IsUint(): + return strconv.FormatUint(uint64(v.Uint()), 10) + case v.IsUint8(): + return strconv.FormatUint(uint64(v.Uint8()), 10) + case v.IsUint16(): + return strconv.FormatUint(uint64(v.Uint16()), 10) + case v.IsUint32(): + return strconv.FormatUint(uint64(v.Uint32()), 10) + case v.IsUint64(): + return strconv.FormatUint(v.Uint64(), 10) + } + + return fmt.Sprintf("%#v", v.Data()) +} diff --git a/vendor/github.com/stretchr/testify/mock/doc.go b/vendor/github.com/stretchr/testify/mock/doc.go new file mode 100644 index 00000000..7324128e --- /dev/null +++ b/vendor/github.com/stretchr/testify/mock/doc.go @@ -0,0 +1,44 @@ +// Package mock provides a system by which it is possible to mock your objects +// and verify calls are happening as expected. +// +// Example Usage +// +// The mock package provides an object, Mock, that tracks activity on another object. It is usually +// embedded into a test object as shown below: +// +// type MyTestObject struct { +// // add a Mock object instance +// mock.Mock +// +// // other fields go here as normal +// } +// +// When implementing the methods of an interface, you wire your functions up +// to call the Mock.Called(args...) method, and return the appropriate values. +// +// For example, to mock a method that saves the name and age of a person and returns +// the year of their birth or an error, you might write this: +// +// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) { +// args := o.Called(firstname, lastname, age) +// return args.Int(0), args.Error(1) +// } +// +// The Int, Error and Bool methods are examples of strongly typed getters that take the argument +// index position. Given this argument list: +// +// (12, true, "Something") +// +// You could read them out strongly typed like this: +// +// args.Int(0) +// args.Bool(1) +// args.String(2) +// +// For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion: +// +// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine) +// +// This may cause a panic if the object you are getting is nil (the type assertion will fail), in those +// cases you should check for nil first. +package mock diff --git a/vendor/github.com/stretchr/testify/mock/mock.go b/vendor/github.com/stretchr/testify/mock/mock.go new file mode 100644 index 00000000..d6694ed7 --- /dev/null +++ b/vendor/github.com/stretchr/testify/mock/mock.go @@ -0,0 +1,886 @@ +package mock + +import ( + "errors" + "fmt" + "reflect" + "regexp" + "runtime" + "strings" + "sync" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/pmezard/go-difflib/difflib" + "github.com/stretchr/objx" + "github.com/stretchr/testify/assert" +) + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Logf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + FailNow() +} + +/* + Call +*/ + +// Call represents a method call and is used for setting expectations, +// as well as recording activity. +type Call struct { + Parent *Mock + + // The name of the method that was or will be called. + Method string + + // Holds the arguments of the method. + Arguments Arguments + + // Holds the arguments that should be returned when + // this method is called. + ReturnArguments Arguments + + // Holds the caller info for the On() call + callerInfo []string + + // The number of times to return the return arguments when setting + // expectations. 0 means to always return the value. + Repeatability int + + // Amount of times this call has been called + totalCalls int + + // Call to this method can be optional + optional bool + + // Holds a channel that will be used to block the Return until it either + // receives a message or is closed. nil means it returns immediately. + WaitFor <-chan time.Time + + waitTime time.Duration + + // Holds a handler used to manipulate arguments content that are passed by + // reference. It's useful when mocking methods such as unmarshalers or + // decoders. + RunFn func(Arguments) +} + +func newCall(parent *Mock, methodName string, callerInfo []string, methodArguments ...interface{}) *Call { + return &Call{ + Parent: parent, + Method: methodName, + Arguments: methodArguments, + ReturnArguments: make([]interface{}, 0), + callerInfo: callerInfo, + Repeatability: 0, + WaitFor: nil, + RunFn: nil, + } +} + +func (c *Call) lock() { + c.Parent.mutex.Lock() +} + +func (c *Call) unlock() { + c.Parent.mutex.Unlock() +} + +// Return specifies the return arguments for the expectation. +// +// Mock.On("DoSomething").Return(errors.New("failed")) +func (c *Call) Return(returnArguments ...interface{}) *Call { + c.lock() + defer c.unlock() + + c.ReturnArguments = returnArguments + + return c +} + +// Once indicates that that the mock should only return the value once. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once() +func (c *Call) Once() *Call { + return c.Times(1) +} + +// Twice indicates that that the mock should only return the value twice. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice() +func (c *Call) Twice() *Call { + return c.Times(2) +} + +// Times indicates that that the mock should only return the indicated number +// of times. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5) +func (c *Call) Times(i int) *Call { + c.lock() + defer c.unlock() + c.Repeatability = i + return c +} + +// WaitUntil sets the channel that will block the mock's return until its closed +// or a message is received. +// +// Mock.On("MyMethod", arg1, arg2).WaitUntil(time.After(time.Second)) +func (c *Call) WaitUntil(w <-chan time.Time) *Call { + c.lock() + defer c.unlock() + c.WaitFor = w + return c +} + +// After sets how long to block until the call returns +// +// Mock.On("MyMethod", arg1, arg2).After(time.Second) +func (c *Call) After(d time.Duration) *Call { + c.lock() + defer c.unlock() + c.waitTime = d + return c +} + +// Run sets a handler to be called before returning. It can be used when +// mocking a method such as unmarshalers that takes a pointer to a struct and +// sets properties in such struct +// +// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}").Return().Run(func(args Arguments) { +// arg := args.Get(0).(*map[string]interface{}) +// arg["foo"] = "bar" +// }) +func (c *Call) Run(fn func(args Arguments)) *Call { + c.lock() + defer c.unlock() + c.RunFn = fn + return c +} + +// Maybe allows the method call to be optional. Not calling an optional method +// will not cause an error while asserting expectations +func (c *Call) Maybe() *Call { + c.lock() + defer c.unlock() + c.optional = true + return c +} + +// On chains a new expectation description onto the mocked interface. This +// allows syntax like. +// +// Mock. +// On("MyMethod", 1).Return(nil). +// On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error")) +//go:noinline +func (c *Call) On(methodName string, arguments ...interface{}) *Call { + return c.Parent.On(methodName, arguments...) +} + +// Mock is the workhorse used to track activity on another object. +// For an example of its usage, refer to the "Example Usage" section at the top +// of this document. +type Mock struct { + // Represents the calls that are expected of + // an object. + ExpectedCalls []*Call + + // Holds the calls that were made to this mocked object. + Calls []Call + + // test is An optional variable that holds the test struct, to be used when an + // invalid mock call was made. + test TestingT + + // TestData holds any data that might be useful for testing. Testify ignores + // this data completely allowing you to do whatever you like with it. + testData objx.Map + + mutex sync.Mutex +} + +// TestData holds any data that might be useful for testing. Testify ignores +// this data completely allowing you to do whatever you like with it. +func (m *Mock) TestData() objx.Map { + + if m.testData == nil { + m.testData = make(objx.Map) + } + + return m.testData +} + +/* + Setting expectations +*/ + +// Test sets the test struct variable of the mock object +func (m *Mock) Test(t TestingT) { + m.mutex.Lock() + defer m.mutex.Unlock() + m.test = t +} + +// fail fails the current test with the given formatted format and args. +// In case that a test was defined, it uses the test APIs for failing a test, +// otherwise it uses panic. +func (m *Mock) fail(format string, args ...interface{}) { + m.mutex.Lock() + defer m.mutex.Unlock() + + if m.test == nil { + panic(fmt.Sprintf(format, args...)) + } + m.test.Errorf(format, args...) + m.test.FailNow() +} + +// On starts a description of an expectation of the specified method +// being called. +// +// Mock.On("MyMethod", arg1, arg2) +func (m *Mock) On(methodName string, arguments ...interface{}) *Call { + for _, arg := range arguments { + if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { + panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg)) + } + } + + m.mutex.Lock() + defer m.mutex.Unlock() + c := newCall(m, methodName, assert.CallerInfo(), arguments...) + m.ExpectedCalls = append(m.ExpectedCalls, c) + return c +} + +// /* +// Recording and responding to activity +// */ + +func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, *Call) { + for i, call := range m.ExpectedCalls { + if call.Method == method && call.Repeatability > -1 { + + _, diffCount := call.Arguments.Diff(arguments) + if diffCount == 0 { + return i, call + } + + } + } + return -1, nil +} + +func (m *Mock) findClosestCall(method string, arguments ...interface{}) (*Call, string) { + var diffCount int + var closestCall *Call + var err string + + for _, call := range m.expectedCalls() { + if call.Method == method { + + errInfo, tempDiffCount := call.Arguments.Diff(arguments) + if tempDiffCount < diffCount || diffCount == 0 { + diffCount = tempDiffCount + closestCall = call + err = errInfo + } + + } + } + + return closestCall, err +} + +func callString(method string, arguments Arguments, includeArgumentValues bool) string { + + var argValsString string + if includeArgumentValues { + var argVals []string + for argIndex, arg := range arguments { + argVals = append(argVals, fmt.Sprintf("%d: %#v", argIndex, arg)) + } + argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t")) + } + + return fmt.Sprintf("%s(%s)%s", method, arguments.String(), argValsString) +} + +// Called tells the mock object that a method has been called, and gets an array +// of arguments to return. Panics if the call is unexpected (i.e. not preceded by +// appropriate .On .Return() calls) +// If Call.WaitFor is set, blocks until the channel is closed or receives a message. +func (m *Mock) Called(arguments ...interface{}) Arguments { + // get the calling function's name + pc, _, _, ok := runtime.Caller(1) + if !ok { + panic("Couldn't get the caller information") + } + functionPath := runtime.FuncForPC(pc).Name() + //Next four lines are required to use GCCGO function naming conventions. + //For Ex: github_com_docker_libkv_store_mock.WatchTree.pN39_github_com_docker_libkv_store_mock.Mock + //uses interface information unlike golang github.com/docker/libkv/store/mock.(*Mock).WatchTree + //With GCCGO we need to remove interface information starting from pN
. + re := regexp.MustCompile("\\.pN\\d+_") + if re.MatchString(functionPath) { + functionPath = re.Split(functionPath, -1)[0] + } + parts := strings.Split(functionPath, ".") + functionName := parts[len(parts)-1] + return m.MethodCalled(functionName, arguments...) +} + +// MethodCalled tells the mock object that the given method has been called, and gets +// an array of arguments to return. Panics if the call is unexpected (i.e. not preceded +// by appropriate .On .Return() calls) +// If Call.WaitFor is set, blocks until the channel is closed or receives a message. +func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Arguments { + m.mutex.Lock() + //TODO: could combine expected and closes in single loop + found, call := m.findExpectedCall(methodName, arguments...) + + if found < 0 { + // we have to fail here - because we don't know what to do + // as the return arguments. This is because: + // + // a) this is a totally unexpected call to this method, + // b) the arguments are not what was expected, or + // c) the developer has forgotten to add an accompanying On...Return pair. + + closestCall, mismatch := m.findClosestCall(methodName, arguments...) + m.mutex.Unlock() + + if closestCall != nil { + m.fail("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n\n%s\nDiff: %s", + callString(methodName, arguments, true), + callString(methodName, closestCall.Arguments, true), + diffArguments(closestCall.Arguments, arguments), + strings.TrimSpace(mismatch), + ) + } else { + m.fail("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", methodName, methodName, callString(methodName, arguments, true), assert.CallerInfo()) + } + } + + if call.Repeatability == 1 { + call.Repeatability = -1 + } else if call.Repeatability > 1 { + call.Repeatability-- + } + call.totalCalls++ + + // add the call + m.Calls = append(m.Calls, *newCall(m, methodName, assert.CallerInfo(), arguments...)) + m.mutex.Unlock() + + // block if specified + if call.WaitFor != nil { + <-call.WaitFor + } else { + time.Sleep(call.waitTime) + } + + m.mutex.Lock() + runFn := call.RunFn + m.mutex.Unlock() + + if runFn != nil { + runFn(arguments) + } + + m.mutex.Lock() + returnArgs := call.ReturnArguments + m.mutex.Unlock() + + return returnArgs +} + +/* + Assertions +*/ + +type assertExpectationser interface { + AssertExpectations(TestingT) bool +} + +// AssertExpectationsForObjects asserts that everything specified with On and Return +// of the specified objects was in fact called as expected. +// +// Calls may have occurred in any order. +func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + for _, obj := range testObjects { + if m, ok := obj.(Mock); ok { + t.Logf("Deprecated mock.AssertExpectationsForObjects(myMock.Mock) use mock.AssertExpectationsForObjects(myMock)") + obj = &m + } + m := obj.(assertExpectationser) + if !m.AssertExpectations(t) { + t.Logf("Expectations didn't match for Mock: %+v", reflect.TypeOf(m)) + return false + } + } + return true +} + +// AssertExpectations asserts that everything specified with On and Return was +// in fact called as expected. Calls may have occurred in any order. +func (m *Mock) AssertExpectations(t TestingT) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() + var somethingMissing bool + var failedExpectations int + + // iterate through each expectation + expectedCalls := m.expectedCalls() + for _, expectedCall := range expectedCalls { + if !expectedCall.optional && !m.methodWasCalled(expectedCall.Method, expectedCall.Arguments) && expectedCall.totalCalls == 0 { + somethingMissing = true + failedExpectations++ + t.Logf("FAIL:\t%s(%s)\n\t\tat: %s", expectedCall.Method, expectedCall.Arguments.String(), expectedCall.callerInfo) + } else { + if expectedCall.Repeatability > 0 { + somethingMissing = true + failedExpectations++ + t.Logf("FAIL:\t%s(%s)\n\t\tat: %s", expectedCall.Method, expectedCall.Arguments.String(), expectedCall.callerInfo) + } else { + t.Logf("PASS:\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String()) + } + } + } + + if somethingMissing { + t.Errorf("FAIL: %d out of %d expectation(s) were met.\n\tThe code you are testing needs to make %d more call(s).\n\tat: %s", len(expectedCalls)-failedExpectations, len(expectedCalls), failedExpectations, assert.CallerInfo()) + } + + return !somethingMissing +} + +// AssertNumberOfCalls asserts that the method was called expectedCalls times. +func (m *Mock) AssertNumberOfCalls(t TestingT, methodName string, expectedCalls int) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() + var actualCalls int + for _, call := range m.calls() { + if call.Method == methodName { + actualCalls++ + } + } + return assert.Equal(t, expectedCalls, actualCalls, fmt.Sprintf("Expected number of calls (%d) does not match the actual number of calls (%d).", expectedCalls, actualCalls)) +} + +// AssertCalled asserts that the method was called. +// It can produce a false result when an argument is a pointer type and the underlying value changed after calling the mocked method. +func (m *Mock) AssertCalled(t TestingT, methodName string, arguments ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() + if !m.methodWasCalled(methodName, arguments) { + var calledWithArgs []string + for _, call := range m.calls() { + calledWithArgs = append(calledWithArgs, fmt.Sprintf("%v", call.Arguments)) + } + if len(calledWithArgs) == 0 { + return assert.Fail(t, "Should have called with given arguments", + fmt.Sprintf("Expected %q to have been called with:\n%v\nbut no actual calls happened", methodName, arguments)) + } + return assert.Fail(t, "Should have called with given arguments", + fmt.Sprintf("Expected %q to have been called with:\n%v\nbut actual calls were:\n %v", methodName, arguments, strings.Join(calledWithArgs, "\n"))) + } + return true +} + +// AssertNotCalled asserts that the method was not called. +// It can produce a false result when an argument is a pointer type and the underlying value changed after calling the mocked method. +func (m *Mock) AssertNotCalled(t TestingT, methodName string, arguments ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() + if m.methodWasCalled(methodName, arguments) { + return assert.Fail(t, "Should not have called with given arguments", + fmt.Sprintf("Expected %q to not have been called with:\n%v\nbut actually it was.", methodName, arguments)) + } + return true +} + +func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool { + for _, call := range m.calls() { + if call.Method == methodName { + + _, differences := Arguments(expected).Diff(call.Arguments) + + if differences == 0 { + // found the expected call + return true + } + + } + } + // we didn't find the expected call + return false +} + +func (m *Mock) expectedCalls() []*Call { + return append([]*Call{}, m.ExpectedCalls...) +} + +func (m *Mock) calls() []Call { + return append([]Call{}, m.Calls...) +} + +/* + Arguments +*/ + +// Arguments holds an array of method arguments or return values. +type Arguments []interface{} + +const ( + // Anything is used in Diff and Assert when the argument being tested + // shouldn't be taken into consideration. + Anything = "mock.Anything" +) + +// AnythingOfTypeArgument is a string that contains the type of an argument +// for use when type checking. Used in Diff and Assert. +type AnythingOfTypeArgument string + +// AnythingOfType returns an AnythingOfTypeArgument object containing the +// name of the type to check for. Used in Diff and Assert. +// +// For example: +// Assert(t, AnythingOfType("string"), AnythingOfType("int")) +func AnythingOfType(t string) AnythingOfTypeArgument { + return AnythingOfTypeArgument(t) +} + +// argumentMatcher performs custom argument matching, returning whether or +// not the argument is matched by the expectation fixture function. +type argumentMatcher struct { + // fn is a function which accepts one argument, and returns a bool. + fn reflect.Value +} + +func (f argumentMatcher) Matches(argument interface{}) bool { + expectType := f.fn.Type().In(0) + expectTypeNilSupported := false + switch expectType.Kind() { + case reflect.Interface, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Ptr: + expectTypeNilSupported = true + } + + argType := reflect.TypeOf(argument) + var arg reflect.Value + if argType == nil { + arg = reflect.New(expectType).Elem() + } else { + arg = reflect.ValueOf(argument) + } + + if argType == nil && !expectTypeNilSupported { + panic(errors.New("attempting to call matcher with nil for non-nil expected type")) + } + if argType == nil || argType.AssignableTo(expectType) { + result := f.fn.Call([]reflect.Value{arg}) + return result[0].Bool() + } + return false +} + +func (f argumentMatcher) String() string { + return fmt.Sprintf("func(%s) bool", f.fn.Type().In(0).Name()) +} + +// MatchedBy can be used to match a mock call based on only certain properties +// from a complex struct or some calculation. It takes a function that will be +// evaluated with the called argument and will return true when there's a match +// and false otherwise. +// +// Example: +// m.On("Do", MatchedBy(func(req *http.Request) bool { return req.Host == "example.com" })) +// +// |fn|, must be a function accepting a single argument (of the expected type) +// which returns a bool. If |fn| doesn't match the required signature, +// MatchedBy() panics. +func MatchedBy(fn interface{}) argumentMatcher { + fnType := reflect.TypeOf(fn) + + if fnType.Kind() != reflect.Func { + panic(fmt.Sprintf("assert: arguments: %s is not a func", fn)) + } + if fnType.NumIn() != 1 { + panic(fmt.Sprintf("assert: arguments: %s does not take exactly one argument", fn)) + } + if fnType.NumOut() != 1 || fnType.Out(0).Kind() != reflect.Bool { + panic(fmt.Sprintf("assert: arguments: %s does not return a bool", fn)) + } + + return argumentMatcher{fn: reflect.ValueOf(fn)} +} + +// Get Returns the argument at the specified index. +func (args Arguments) Get(index int) interface{} { + if index+1 > len(args) { + panic(fmt.Sprintf("assert: arguments: Cannot call Get(%d) because there are %d argument(s).", index, len(args))) + } + return args[index] +} + +// Is gets whether the objects match the arguments specified. +func (args Arguments) Is(objects ...interface{}) bool { + for i, obj := range args { + if obj != objects[i] { + return false + } + } + return true +} + +// Diff gets a string describing the differences between the arguments +// and the specified objects. +// +// Returns the diff string and number of differences found. +func (args Arguments) Diff(objects []interface{}) (string, int) { + //TODO: could return string as error and nil for No difference + + var output = "\n" + var differences int + + var maxArgCount = len(args) + if len(objects) > maxArgCount { + maxArgCount = len(objects) + } + + for i := 0; i < maxArgCount; i++ { + var actual, expected interface{} + var actualFmt, expectedFmt string + + if len(objects) <= i { + actual = "(Missing)" + actualFmt = "(Missing)" + } else { + actual = objects[i] + actualFmt = fmt.Sprintf("(%[1]T=%[1]v)", actual) + } + + if len(args) <= i { + expected = "(Missing)" + expectedFmt = "(Missing)" + } else { + expected = args[i] + expectedFmt = fmt.Sprintf("(%[1]T=%[1]v)", expected) + } + + if matcher, ok := expected.(argumentMatcher); ok { + if matcher.Matches(actual) { + output = fmt.Sprintf("%s\t%d: PASS: %s matched by %s\n", output, i, actualFmt, matcher) + } else { + differences++ + output = fmt.Sprintf("%s\t%d: FAIL: %s not matched by %s\n", output, i, actualFmt, matcher) + } + } else if reflect.TypeOf(expected) == reflect.TypeOf((*AnythingOfTypeArgument)(nil)).Elem() { + + // type checking + if reflect.TypeOf(actual).Name() != string(expected.(AnythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(AnythingOfTypeArgument)) { + // not match + differences++ + output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actualFmt) + } + + } else { + + // normal checking + + if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) { + // match + output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, actualFmt, expectedFmt) + } else { + // not match + differences++ + output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, actualFmt, expectedFmt) + } + } + + } + + if differences == 0 { + return "No differences.", differences + } + + return output, differences + +} + +// Assert compares the arguments with the specified objects and fails if +// they do not exactly match. +func (args Arguments) Assert(t TestingT, objects ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + // get the differences + diff, diffCount := args.Diff(objects) + + if diffCount == 0 { + return true + } + + // there are differences... report them... + t.Logf(diff) + t.Errorf("%sArguments do not match.", assert.CallerInfo()) + + return false + +} + +// String gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +// +// If no index is provided, String() returns a complete string representation +// of the arguments. +func (args Arguments) String(indexOrNil ...int) string { + + if len(indexOrNil) == 0 { + // normal String() method - return a string representation of the args + var argsStr []string + for _, arg := range args { + argsStr = append(argsStr, fmt.Sprintf("%s", reflect.TypeOf(arg))) + } + return strings.Join(argsStr, ",") + } else if len(indexOrNil) == 1 { + // Index has been specified - get the argument at that index + var index = indexOrNil[0] + var s string + var ok bool + if s, ok = args.Get(index).(string); !ok { + panic(fmt.Sprintf("assert: arguments: String(%d) failed because object wasn't correct type: %s", index, args.Get(index))) + } + return s + } + + panic(fmt.Sprintf("assert: arguments: Wrong number of arguments passed to String. Must be 0 or 1, not %d", len(indexOrNil))) + +} + +// Int gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +func (args Arguments) Int(index int) int { + var s int + var ok bool + if s, ok = args.Get(index).(int); !ok { + panic(fmt.Sprintf("assert: arguments: Int(%d) failed because object wasn't correct type: %v", index, args.Get(index))) + } + return s +} + +// Error gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +func (args Arguments) Error(index int) error { + obj := args.Get(index) + var s error + var ok bool + if obj == nil { + return nil + } + if s, ok = obj.(error); !ok { + panic(fmt.Sprintf("assert: arguments: Error(%d) failed because object wasn't correct type: %v", index, args.Get(index))) + } + return s +} + +// Bool gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +func (args Arguments) Bool(index int) bool { + var s bool + var ok bool + if s, ok = args.Get(index).(bool); !ok { + panic(fmt.Sprintf("assert: arguments: Bool(%d) failed because object wasn't correct type: %v", index, args.Get(index))) + } + return s +} + +func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) { + t := reflect.TypeOf(v) + k := t.Kind() + + if k == reflect.Ptr { + t = t.Elem() + k = t.Kind() + } + return t, k +} + +func diffArguments(expected Arguments, actual Arguments) string { + if len(expected) != len(actual) { + return fmt.Sprintf("Provided %v arguments, mocked for %v arguments", len(expected), len(actual)) + } + + for x := range expected { + if diffString := diff(expected[x], actual[x]); diffString != "" { + return fmt.Sprintf("Difference found in argument %v:\n\n%s", x, diffString) + } + } + + return "" +} + +// diff returns a diff of both values as long as both are of the same type and +// are a struct, map, slice or array. Otherwise it returns an empty string. +func diff(expected interface{}, actual interface{}) string { + if expected == nil || actual == nil { + return "" + } + + et, ek := typeAndKind(expected) + at, _ := typeAndKind(actual) + + if et != at { + return "" + } + + if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array { + return "" + } + + e := spewConfig.Sdump(expected) + a := spewConfig.Sdump(actual) + + diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ + A: difflib.SplitLines(e), + B: difflib.SplitLines(a), + FromFile: "Expected", + FromDate: "", + ToFile: "Actual", + ToDate: "", + Context: 1, + }) + + return diff +} + +var spewConfig = spew.ConfigState{ + Indent: " ", + DisablePointerAddresses: true, + DisableCapacities: true, + SortKeys: true, +} + +type tHelper interface { + Helper() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 73be730d..18c43e01 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,6 +1,6 @@ # github.com/DATA-DOG/go-sqlmock v1.3.3 github.com/DATA-DOG/go-sqlmock -# github.com/davecgh/go-spew v1.1.0 +# github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew # github.com/go-sql-driver/mysql v1.4.1 github.com/go-sql-driver/mysql @@ -12,7 +12,10 @@ github.com/lib/pq/scram github.com/mattn/go-sqlite3 # github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib/difflib +# github.com/stretchr/objx v0.1.0 +github.com/stretchr/objx # github.com/stretchr/testify v1.3.0 +github.com/stretchr/testify/mock github.com/stretchr/testify/assert github.com/stretchr/testify/suite github.com/stretchr/testify/require