diff --git a/.circleci/config.yml b/.circleci/config.yml index b1cb643..35e0a6d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ jobs: build-postgres-and-mysql: docker: # specify the version - - image: circleci/golang:1.11 + - image: circleci/golang:1.13 - image: circleci/postgres:10.8-alpine environment: # environment variables for primary container @@ -14,7 +14,7 @@ jobs: POSTGRES_PASSWORD: jet POSTGRES_DB: jetdb - - image: circleci/mysql:8.0 + - image: circleci/mysql:8.0.16 command: [--default-authentication-plugin=mysql_native_password] environment: MYSQL_ROOT_PASSWORD: jet @@ -41,17 +41,9 @@ jobs: - run: name: Install dependencies command: | - go get github.com/google/uuid - go get github.com/lib/pq - go get github.com/go-sql-driver/mysql - - go get github.com/pkg/profile - go get github.com/stretchr/testify/assert - go get github.com/google/go-cmp/cmp - go get github.com/davecgh/go-spew/spew + cd /go/src/github.com/go-jet/jet go get github.com/jstemmer/go-junit-report - - go install github.com/go-jet/jet/cmd/jet + go build -o /home/circleci/.local/bin/jet ./cmd/jet/ - run: name: Waiting for Postgres to be ready @@ -85,6 +77,7 @@ jobs: mysql -h 127.0.0.1 -u root -pjet -e "grant all privileges on *.* to 'jet'@'%';" mysql -h 127.0.0.1 -u root -pjet -e "set global sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';" mysql -h 127.0.0.1 -u jet -pjet -e "create database test_sample" + mysql -h 127.0.0.1 -u jet -pjet -e "create database dvds2" - run: name: Init Postgres database @@ -95,7 +88,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - - run: go test -v ./... -coverpkg=github.com/go-jet/jet/postgres/...,github.com/go-jet/jet/mysql/...,github.com/go-jet/jet/qrm/...,github.com/go-jet/jet/generator/...,github.com/go-jet/jet/internal/... -coverprofile=cover.out 2>&1 | go-junit-report > $TEST_RESULTS/results.xml + - run: MY_SQL_SOURCE=MySQL go test -v ./... -coverpkg=github.com/go-jet/jet/postgres/...,github.com/go-jet/jet/mysql/...,github.com/go-jet/jet/qrm/...,github.com/go-jet/jet/generator/...,github.com/go-jet/jet/internal/... -coverprofile=cover.out 2>&1 | go-junit-report > $TEST_RESULTS/results.xml - run: name: Upload code coverage @@ -110,7 +103,7 @@ jobs: build-mariadb: docker: # specify the version - - image: circleci/golang:1.11 + - image: circleci/golang:1.13 - image: circleci/mariadb:10.3 command: [--default-authentication-plugin=mysql_native_password] @@ -138,17 +131,9 @@ jobs: - run: name: Install dependencies command: | - go get github.com/google/uuid - go get github.com/lib/pq - go get github.com/go-sql-driver/mysql - - go get github.com/pkg/profile - go get github.com/stretchr/testify/assert - go get github.com/google/go-cmp/cmp - go get github.com/davecgh/go-spew/spew + cd /go/src/github.com/go-jet/jet go get github.com/jstemmer/go-junit-report - - go install github.com/go-jet/jet/cmd/jet + go build -o /home/circleci/.local/bin/jet ./cmd/jet/ - run: name: Install MySQL CLI; @@ -161,6 +146,7 @@ jobs: mysql -h 127.0.0.1 -u root -pjet -e "grant all privileges on *.* to 'jet'@'%';" mysql -h 127.0.0.1 -u root -pjet -e "set global sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';" mysql -h 127.0.0.1 -u jet -pjet -e "create database test_sample" + mysql -h 127.0.0.1 -u jet -pjet -e "create database dvds2" - run: name: Init MariaDB database @@ -172,7 +158,7 @@ jobs: - run: name: Run MariaDB tests command: | - go test -v ./tests/mysql/ -source=MariaDB + MY_SQL_SOURCE=MariaDB go test -v ./tests/mysql/ workflows: version: 2 diff --git a/README.md b/README.md index cb3499f..e79e86c 100644 --- a/README.md +++ b/README.md @@ -62,24 +62,33 @@ To install Jet package, you need to install Go and set your Go workspace first. ### Installation -Use the bellow command to add jet as a dependency into `go.mod` project: +Use the command bellow to add jet as a dependency into `go.mod` project: ```sh $ go get -u github.com/go-jet/jet/v2 ``` -Use the bellow command to add jet as a dependency into `GOPATH` project: - -```sh -$ go get -u github.com/go-jet/jet -``` - -Install jet generator to GOPATH bin folder. This will allow generating jet files from the command line. +Jet generator can be install in the following ways: +1) Install jet generator to GOPATH/bin folder: ```sh cd $GOPATH/src/ && GO111MODULE=off go get -u github.com/go-jet/jet/cmd/jet ``` +*Make sure GOPATH/bin folder is added to the PATH environment variable.* -*Make sure GOPATH bin folder is added to the PATH environment variable.* +2) Install jet generator to specific folder: + +```sh +git clone https://github.com/go-jet/jet.git +cd jet && go build -o dir_path ./cmd/jet +``` +*Make sure `dir_path` folder is added to the PATH environment variable.* + +3) (Go1.16+) Install jet generator using go install: +```sh +go install github.com/go-jet/jet/v2/cmd/jet@latest +``` +*Jet generator is installed to the directory named by the GOBIN environment variable, +which defaults to $GOPATH/bin or $HOME/go/bin if the GOPATH environment variable is not set.* ### Quick Start For this quick start example we will use PostgreSQL sample _'dvd rental'_ database. Full database dump can be found in [./tests/testdata/init/postgres/dvds.sql](./tests/testdata/init/postgres/dvds.sql). @@ -519,26 +528,17 @@ The biggest benefit is speed. Speed is improved in 3 major areas: ##### Speed of development -Writing SQL queries is faster and easier, because the developers have help of SQL code completion and SQL type safety directly from Go. +Writing SQL queries is faster and easier as the developers have help of SQL code completion and SQL type safety directly from Go. Automatic scan to arbitrary structure removes a lot of headache and boilerplate code needed to structure database query result. ##### Speed of execution While ORM libraries can introduce significant performance penalties due to number of round-trips to the database, -Jet will always perform much better, because of the single database call. +Jet will always perform better as developers can write complex query and retrieve result with a single database call. +Thus handler time lost on latency between server and database can be constant. Handler execution will be proportional +only to the query complexity and the number of rows returned from database. -Common web and database server usually are not on the same physical machine, and there is some latency between them. -Latency can vary from 5ms to 50+ms. In majority of cases query executed on database is simple query lasting no more than 1ms. -In those cases web server handler execution time is directly proportional to latency between server and database. -This is not such a big problem if handler calls database couple of times, but what if web server is using ORM to retrieve data from database. -ORM sometimes can access the database once for every object needed. Now lets say latency is 30ms and there are 100 -different objects required from the database. This handler will last 3 seconds !!!. - -With Jet, handler time lost on latency between server and database is constant. Because we can write complex query and -return result in one database call. Handler execution will be only proportional to the number of rows returned from database. -ORM example replaced with jet will take just 30ms + 'result scan time' = 31ms (rough estimate). - -With Jet you can even join the whole database and store the whole structured result in one database call. +With Jet it is even possible to join the whole database and store the whole structured result in one database call. This is exactly what is being done in one of the tests: [TestJoinEverything](/tests/postgres/chinook_db_test.go#L40). The whole test database is joined and query result(~10,000 rows) is stored in a structured variable in less than 0.7s. @@ -570,6 +570,7 @@ To run the tests, additional dependencies are required: - `github.com/pkg/profile` - `github.com/stretchr/testify` - `github.com/google/go-cmp` +- `github.com/jackc/pgx/v4` ## Versioning @@ -577,5 +578,5 @@ To run the tests, additional dependencies are required: ## License -Copyright 2019-2020 Goran Bjelanovic +Copyright 2019-2021 Goran Bjelanovic Licensed under the Apache License, Version 2.0. diff --git a/cmd/jet/main.go b/cmd/jet/main.go index 8e0a606..136e58b 100644 --- a/cmd/jet/main.go +++ b/cmd/jet/main.go @@ -47,7 +47,7 @@ func main() { flag.Usage = func() { _, _ = fmt.Fprint(os.Stdout, ` -Jet generator 2.3.0 +Jet generator 2.5.0 Usage: -source string diff --git a/examples/quick-start/.gen/jetdb/dvds/table/actor.go b/examples/quick-start/.gen/jetdb/dvds/table/actor.go index 376d73e..bbeb7e8 100644 --- a/examples/quick-start/.gen/jetdb/dvds/table/actor.go +++ b/examples/quick-start/.gen/jetdb/dvds/table/actor.go @@ -11,7 +11,7 @@ import ( "github.com/go-jet/jet/v2/postgres" ) -var Actor = newActorTable() +var Actor = newActorTable("dvds", "actor", "") type actorTable struct { postgres.Table @@ -33,20 +33,23 @@ type ActorTable struct { } // AS creates new ActorTable with assigned alias -func (a *ActorTable) AS(alias string) *ActorTable { - aliasTable := newActorTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a ActorTable) AS(alias string) *ActorTable { + return newActorTable(a.SchemaName(), a.TableName(), alias) } -func newActorTable() *ActorTable { +// Schema creates new ActorTable with assigned schema name +func (a ActorTable) FromSchema(schemaName string) *ActorTable { + return newActorTable(schemaName, a.TableName(), a.Alias()) +} + +func newActorTable(schemaName, tableName, alias string) *ActorTable { return &ActorTable{ - actorTable: newActorTableImpl("dvds", "actor"), - EXCLUDED: newActorTableImpl("", "excluded"), + actorTable: newActorTableImpl(schemaName, tableName, alias), + EXCLUDED: newActorTableImpl("", "excluded", ""), } } -func newActorTableImpl(schemaName, tableName string) actorTable { +func newActorTableImpl(schemaName, tableName, alias string) actorTable { var ( ActorIDColumn = postgres.IntegerColumn("actor_id") FirstNameColumn = postgres.StringColumn("first_name") @@ -57,7 +60,7 @@ func newActorTableImpl(schemaName, tableName string) actorTable { ) return actorTable{ - Table: postgres.NewTable(schemaName, tableName, allColumns...), + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns ActorID: ActorIDColumn, diff --git a/examples/quick-start/.gen/jetdb/dvds/table/category.go b/examples/quick-start/.gen/jetdb/dvds/table/category.go index ac2caac..563938d 100644 --- a/examples/quick-start/.gen/jetdb/dvds/table/category.go +++ b/examples/quick-start/.gen/jetdb/dvds/table/category.go @@ -11,7 +11,7 @@ import ( "github.com/go-jet/jet/v2/postgres" ) -var Category = newCategoryTable() +var Category = newCategoryTable("dvds", "category", "") type categoryTable struct { postgres.Table @@ -32,20 +32,23 @@ type CategoryTable struct { } // AS creates new CategoryTable with assigned alias -func (a *CategoryTable) AS(alias string) *CategoryTable { - aliasTable := newCategoryTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a CategoryTable) AS(alias string) *CategoryTable { + return newCategoryTable(a.SchemaName(), a.TableName(), alias) } -func newCategoryTable() *CategoryTable { +// Schema creates new CategoryTable with assigned schema name +func (a CategoryTable) FromSchema(schemaName string) *CategoryTable { + return newCategoryTable(schemaName, a.TableName(), a.Alias()) +} + +func newCategoryTable(schemaName, tableName, alias string) *CategoryTable { return &CategoryTable{ - categoryTable: newCategoryTableImpl("dvds", "category"), - EXCLUDED: newCategoryTableImpl("", "excluded"), + categoryTable: newCategoryTableImpl(schemaName, tableName, alias), + EXCLUDED: newCategoryTableImpl("", "excluded", ""), } } -func newCategoryTableImpl(schemaName, tableName string) categoryTable { +func newCategoryTableImpl(schemaName, tableName, alias string) categoryTable { var ( CategoryIDColumn = postgres.IntegerColumn("category_id") NameColumn = postgres.StringColumn("name") @@ -55,7 +58,7 @@ func newCategoryTableImpl(schemaName, tableName string) categoryTable { ) return categoryTable{ - Table: postgres.NewTable(schemaName, tableName, allColumns...), + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns CategoryID: CategoryIDColumn, diff --git a/examples/quick-start/.gen/jetdb/dvds/table/film.go b/examples/quick-start/.gen/jetdb/dvds/table/film.go index 2f8c68e..65db900 100644 --- a/examples/quick-start/.gen/jetdb/dvds/table/film.go +++ b/examples/quick-start/.gen/jetdb/dvds/table/film.go @@ -11,7 +11,7 @@ import ( "github.com/go-jet/jet/v2/postgres" ) -var Film = newFilmTable() +var Film = newFilmTable("dvds", "film", "") type filmTable struct { postgres.Table @@ -42,20 +42,23 @@ type FilmTable struct { } // AS creates new FilmTable with assigned alias -func (a *FilmTable) AS(alias string) *FilmTable { - aliasTable := newFilmTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a FilmTable) AS(alias string) *FilmTable { + return newFilmTable(a.SchemaName(), a.TableName(), alias) } -func newFilmTable() *FilmTable { +// Schema creates new FilmTable with assigned schema name +func (a FilmTable) FromSchema(schemaName string) *FilmTable { + return newFilmTable(schemaName, a.TableName(), a.Alias()) +} + +func newFilmTable(schemaName, tableName, alias string) *FilmTable { return &FilmTable{ - filmTable: newFilmTableImpl("dvds", "film"), - EXCLUDED: newFilmTableImpl("", "excluded"), + filmTable: newFilmTableImpl(schemaName, tableName, alias), + EXCLUDED: newFilmTableImpl("", "excluded", ""), } } -func newFilmTableImpl(schemaName, tableName string) filmTable { +func newFilmTableImpl(schemaName, tableName, alias string) filmTable { var ( FilmIDColumn = postgres.IntegerColumn("film_id") TitleColumn = postgres.StringColumn("title") @@ -75,7 +78,7 @@ func newFilmTableImpl(schemaName, tableName string) filmTable { ) return filmTable{ - Table: postgres.NewTable(schemaName, tableName, allColumns...), + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns FilmID: FilmIDColumn, diff --git a/examples/quick-start/.gen/jetdb/dvds/table/film_actor.go b/examples/quick-start/.gen/jetdb/dvds/table/film_actor.go index 1f6bb57..30c3ad3 100644 --- a/examples/quick-start/.gen/jetdb/dvds/table/film_actor.go +++ b/examples/quick-start/.gen/jetdb/dvds/table/film_actor.go @@ -11,7 +11,7 @@ import ( "github.com/go-jet/jet/v2/postgres" ) -var FilmActor = newFilmActorTable() +var FilmActor = newFilmActorTable("dvds", "film_actor", "") type filmActorTable struct { postgres.Table @@ -32,20 +32,23 @@ type FilmActorTable struct { } // AS creates new FilmActorTable with assigned alias -func (a *FilmActorTable) AS(alias string) *FilmActorTable { - aliasTable := newFilmActorTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a FilmActorTable) AS(alias string) *FilmActorTable { + return newFilmActorTable(a.SchemaName(), a.TableName(), alias) } -func newFilmActorTable() *FilmActorTable { +// Schema creates new FilmActorTable with assigned schema name +func (a FilmActorTable) FromSchema(schemaName string) *FilmActorTable { + return newFilmActorTable(schemaName, a.TableName(), a.Alias()) +} + +func newFilmActorTable(schemaName, tableName, alias string) *FilmActorTable { return &FilmActorTable{ - filmActorTable: newFilmActorTableImpl("dvds", "film_actor"), - EXCLUDED: newFilmActorTableImpl("", "excluded"), + filmActorTable: newFilmActorTableImpl(schemaName, tableName, alias), + EXCLUDED: newFilmActorTableImpl("", "excluded", ""), } } -func newFilmActorTableImpl(schemaName, tableName string) filmActorTable { +func newFilmActorTableImpl(schemaName, tableName, alias string) filmActorTable { var ( ActorIDColumn = postgres.IntegerColumn("actor_id") FilmIDColumn = postgres.IntegerColumn("film_id") @@ -55,7 +58,7 @@ func newFilmActorTableImpl(schemaName, tableName string) filmActorTable { ) return filmActorTable{ - Table: postgres.NewTable(schemaName, tableName, allColumns...), + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns ActorID: ActorIDColumn, diff --git a/examples/quick-start/.gen/jetdb/dvds/table/film_category.go b/examples/quick-start/.gen/jetdb/dvds/table/film_category.go index 41fa6f3..8368177 100644 --- a/examples/quick-start/.gen/jetdb/dvds/table/film_category.go +++ b/examples/quick-start/.gen/jetdb/dvds/table/film_category.go @@ -11,7 +11,7 @@ import ( "github.com/go-jet/jet/v2/postgres" ) -var FilmCategory = newFilmCategoryTable() +var FilmCategory = newFilmCategoryTable("dvds", "film_category", "") type filmCategoryTable struct { postgres.Table @@ -32,20 +32,23 @@ type FilmCategoryTable struct { } // AS creates new FilmCategoryTable with assigned alias -func (a *FilmCategoryTable) AS(alias string) *FilmCategoryTable { - aliasTable := newFilmCategoryTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a FilmCategoryTable) AS(alias string) *FilmCategoryTable { + return newFilmCategoryTable(a.SchemaName(), a.TableName(), alias) } -func newFilmCategoryTable() *FilmCategoryTable { +// Schema creates new FilmCategoryTable with assigned schema name +func (a FilmCategoryTable) FromSchema(schemaName string) *FilmCategoryTable { + return newFilmCategoryTable(schemaName, a.TableName(), a.Alias()) +} + +func newFilmCategoryTable(schemaName, tableName, alias string) *FilmCategoryTable { return &FilmCategoryTable{ - filmCategoryTable: newFilmCategoryTableImpl("dvds", "film_category"), - EXCLUDED: newFilmCategoryTableImpl("", "excluded"), + filmCategoryTable: newFilmCategoryTableImpl(schemaName, tableName, alias), + EXCLUDED: newFilmCategoryTableImpl("", "excluded", ""), } } -func newFilmCategoryTableImpl(schemaName, tableName string) filmCategoryTable { +func newFilmCategoryTableImpl(schemaName, tableName, alias string) filmCategoryTable { var ( FilmIDColumn = postgres.IntegerColumn("film_id") CategoryIDColumn = postgres.IntegerColumn("category_id") @@ -55,7 +58,7 @@ func newFilmCategoryTableImpl(schemaName, tableName string) filmCategoryTable { ) return filmCategoryTable{ - Table: postgres.NewTable(schemaName, tableName, allColumns...), + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns FilmID: FilmIDColumn, diff --git a/examples/quick-start/.gen/jetdb/dvds/table/language.go b/examples/quick-start/.gen/jetdb/dvds/table/language.go index 89470c2..5bddeeb 100644 --- a/examples/quick-start/.gen/jetdb/dvds/table/language.go +++ b/examples/quick-start/.gen/jetdb/dvds/table/language.go @@ -11,7 +11,7 @@ import ( "github.com/go-jet/jet/v2/postgres" ) -var Language = newLanguageTable() +var Language = newLanguageTable("dvds", "language", "") type languageTable struct { postgres.Table @@ -32,20 +32,23 @@ type LanguageTable struct { } // AS creates new LanguageTable with assigned alias -func (a *LanguageTable) AS(alias string) *LanguageTable { - aliasTable := newLanguageTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a LanguageTable) AS(alias string) *LanguageTable { + return newLanguageTable(a.SchemaName(), a.TableName(), alias) } -func newLanguageTable() *LanguageTable { +// Schema creates new LanguageTable with assigned schema name +func (a LanguageTable) FromSchema(schemaName string) *LanguageTable { + return newLanguageTable(schemaName, a.TableName(), a.Alias()) +} + +func newLanguageTable(schemaName, tableName, alias string) *LanguageTable { return &LanguageTable{ - languageTable: newLanguageTableImpl("dvds", "language"), - EXCLUDED: newLanguageTableImpl("", "excluded"), + languageTable: newLanguageTableImpl(schemaName, tableName, alias), + EXCLUDED: newLanguageTableImpl("", "excluded", ""), } } -func newLanguageTableImpl(schemaName, tableName string) languageTable { +func newLanguageTableImpl(schemaName, tableName, alias string) languageTable { var ( LanguageIDColumn = postgres.IntegerColumn("language_id") NameColumn = postgres.StringColumn("name") @@ -55,7 +58,7 @@ func newLanguageTableImpl(schemaName, tableName string) languageTable { ) return languageTable{ - Table: postgres.NewTable(schemaName, tableName, allColumns...), + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns LanguageID: LanguageIDColumn, diff --git a/examples/quick-start/.gen/jetdb/dvds/view/actor_info.go b/examples/quick-start/.gen/jetdb/dvds/view/actor_info.go index 65d2560..5bfa25d 100644 --- a/examples/quick-start/.gen/jetdb/dvds/view/actor_info.go +++ b/examples/quick-start/.gen/jetdb/dvds/view/actor_info.go @@ -11,7 +11,7 @@ import ( "github.com/go-jet/jet/v2/postgres" ) -var ActorInfo = newActorInfoTable() +var ActorInfo = newActorInfoTable("dvds", "actor_info", "") type actorInfoTable struct { postgres.Table @@ -33,20 +33,23 @@ type ActorInfoTable struct { } // AS creates new ActorInfoTable with assigned alias -func (a *ActorInfoTable) AS(alias string) *ActorInfoTable { - aliasTable := newActorInfoTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a ActorInfoTable) AS(alias string) *ActorInfoTable { + return newActorInfoTable(a.SchemaName(), a.TableName(), alias) } -func newActorInfoTable() *ActorInfoTable { +// Schema creates new ActorInfoTable with assigned schema name +func (a ActorInfoTable) FromSchema(schemaName string) *ActorInfoTable { + return newActorInfoTable(schemaName, a.TableName(), a.Alias()) +} + +func newActorInfoTable(schemaName, tableName, alias string) *ActorInfoTable { return &ActorInfoTable{ - actorInfoTable: newActorInfoTableImpl("dvds", "actor_info"), - EXCLUDED: newActorInfoTableImpl("", "excluded"), + actorInfoTable: newActorInfoTableImpl(schemaName, tableName, alias), + EXCLUDED: newActorInfoTableImpl("", "excluded", ""), } } -func newActorInfoTableImpl(schemaName, tableName string) actorInfoTable { +func newActorInfoTableImpl(schemaName, tableName, alias string) actorInfoTable { var ( ActorIDColumn = postgres.IntegerColumn("actor_id") FirstNameColumn = postgres.StringColumn("first_name") @@ -57,7 +60,7 @@ func newActorInfoTableImpl(schemaName, tableName string) actorInfoTable { ) return actorInfoTable{ - Table: postgres.NewTable(schemaName, tableName, allColumns...), + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns ActorID: ActorIDColumn, diff --git a/examples/quick-start/.gen/jetdb/dvds/view/customer_list.go b/examples/quick-start/.gen/jetdb/dvds/view/customer_list.go index b4a2c8f..c03a5ef 100644 --- a/examples/quick-start/.gen/jetdb/dvds/view/customer_list.go +++ b/examples/quick-start/.gen/jetdb/dvds/view/customer_list.go @@ -11,7 +11,7 @@ import ( "github.com/go-jet/jet/v2/postgres" ) -var CustomerList = newCustomerListTable() +var CustomerList = newCustomerListTable("dvds", "customer_list", "") type customerListTable struct { postgres.Table @@ -38,20 +38,23 @@ type CustomerListTable struct { } // AS creates new CustomerListTable with assigned alias -func (a *CustomerListTable) AS(alias string) *CustomerListTable { - aliasTable := newCustomerListTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a CustomerListTable) AS(alias string) *CustomerListTable { + return newCustomerListTable(a.SchemaName(), a.TableName(), alias) } -func newCustomerListTable() *CustomerListTable { +// Schema creates new CustomerListTable with assigned schema name +func (a CustomerListTable) FromSchema(schemaName string) *CustomerListTable { + return newCustomerListTable(schemaName, a.TableName(), a.Alias()) +} + +func newCustomerListTable(schemaName, tableName, alias string) *CustomerListTable { return &CustomerListTable{ - customerListTable: newCustomerListTableImpl("dvds", "customer_list"), - EXCLUDED: newCustomerListTableImpl("", "excluded"), + customerListTable: newCustomerListTableImpl(schemaName, tableName, alias), + EXCLUDED: newCustomerListTableImpl("", "excluded", ""), } } -func newCustomerListTableImpl(schemaName, tableName string) customerListTable { +func newCustomerListTableImpl(schemaName, tableName, alias string) customerListTable { var ( IDColumn = postgres.IntegerColumn("id") NameColumn = postgres.StringColumn("name") @@ -67,7 +70,7 @@ func newCustomerListTableImpl(schemaName, tableName string) customerListTable { ) return customerListTable{ - Table: postgres.NewTable(schemaName, tableName, allColumns...), + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns ID: IDColumn, diff --git a/examples/quick-start/quick-start.go b/examples/quick-start/quick-start.go index ba5a0d8..5bdc424 100644 --- a/examples/quick-start/quick-start.go +++ b/examples/quick-start/quick-start.go @@ -4,9 +4,10 @@ import ( "database/sql" "encoding/json" "fmt" - _ "github.com/lib/pq" "io/ioutil" + _ "github.com/lib/pq" + // dot import so that jet go code would resemble as much as native SQL // dot import is not mandatory . "github.com/go-jet/jet/v2/examples/quick-start/.gen/jetdb/dvds/table" diff --git a/generator/internal/template/templates.go b/generator/internal/template/templates.go index 56dba2c..186ade0 100644 --- a/generator/internal/template/templates.go +++ b/generator/internal/template/templates.go @@ -23,7 +23,7 @@ import ( "github.com/go-jet/jet/v2/{{dialect.PackageName}}" ) -var {{ToGoIdentifier .Name}} = new{{.GoStructName}}() +var {{ToGoIdentifier .Name}} = new{{.GoStructName}}("{{.SchemaName}}", "{{.Name}}", "") type {{.GoStructName}} struct { {{dialect.PackageName}}.Table @@ -38,13 +38,16 @@ type {{.GoStructName}} struct { } // AS creates new {{.GoStructName}} with assigned alias -func (a *{{.GoStructName}}) AS(alias string) {{.GoStructName}} { - aliasTable := new{{.GoStructName}}() - aliasTable.Table.AS(alias) - return aliasTable +func (a {{.GoStructName}}) AS(alias string) {{.GoStructName}} { + return new{{.GoStructName}}(a.SchemaName(), a.TableName(), alias) } -func new{{.GoStructName}}() {{.GoStructName}} { +// Schema creates new {{.GoStructName}} with assigned schema name +func (a {{.GoStructName}}) FromSchema(schemaName string) {{.GoStructName}} { + return new{{.GoStructName}}(schemaName, a.TableName(), a.Alias()) +} + +func new{{.GoStructName}}(schemaName, tableName, alias string) {{.GoStructName}} { var ( {{- range .Columns}} {{ToGoIdentifier .Name}}Column = {{dialect.PackageName}}.{{.SqlBuilderColumnType}}Column("{{.Name}}") @@ -54,7 +57,7 @@ func new{{.GoStructName}}() {{.GoStructName}} { ) return {{.GoStructName}}{ - Table: {{dialect.PackageName}}.NewTable("{{.SchemaName}}", "{{.Name}}", allColumns...), + Table: {{dialect.PackageName}}.NewTable(schemaName, tableName, alias, allColumns...), //Columns {{- range .Columns}} @@ -80,7 +83,7 @@ import ( "github.com/go-jet/jet/v2/{{dialect.PackageName}}" ) -var {{ToGoIdentifier .Name}} = new{{.GoStructName}}() +var {{ToGoIdentifier .Name}} = new{{.GoStructName}}("{{.SchemaName}}", "{{.Name}}", "") type {{.GoStructImplName}} struct { {{dialect.PackageName}}.Table @@ -101,20 +104,23 @@ type {{.GoStructName}} struct { } // AS creates new {{.GoStructName}} with assigned alias -func (a *{{.GoStructName}}) AS(alias string) *{{.GoStructName}} { - aliasTable := new{{.GoStructName}}() - aliasTable.Table.AS(alias) - return aliasTable +func (a {{.GoStructName}}) AS(alias string) *{{.GoStructName}} { + return new{{.GoStructName}}(a.SchemaName(), a.TableName(), alias) } -func new{{.GoStructName}}() *{{.GoStructName}} { +// Schema creates new {{.GoStructName}} with assigned schema name +func (a {{.GoStructName}}) FromSchema(schemaName string) *{{.GoStructName}} { + return new{{.GoStructName}}(schemaName, a.TableName(), a.Alias()) +} + +func new{{.GoStructName}}(schemaName, tableName, alias string) *{{.GoStructName}} { return &{{.GoStructName}}{ - {{.GoStructImplName}}: new{{.GoStructName}}Impl("{{.SchemaName}}", "{{.Name}}"), - EXCLUDED: new{{.GoStructName}}Impl("", "excluded"), + {{.GoStructImplName}}: new{{.GoStructName}}Impl(schemaName, tableName, alias), + EXCLUDED: new{{.GoStructName}}Impl("", "excluded", ""), } } -func new{{.GoStructName}}Impl(schemaName, tableName string) {{.GoStructImplName}} { +func new{{.GoStructName}}Impl(schemaName, tableName, alias string) {{.GoStructImplName}} { var ( {{- range .Columns}} {{ToGoIdentifier .Name}}Column = {{dialect.PackageName}}.{{.SqlBuilderColumnType}}Column("{{.Name}}") @@ -124,7 +130,7 @@ func new{{.GoStructName}}Impl(schemaName, tableName string) {{.GoStructImplName} ) return {{.GoStructImplName}}{ - Table: {{dialect.PackageName}}.NewTable(schemaName, tableName, allColumns...), + Table: {{dialect.PackageName}}.NewTable(schemaName, tableName, alias, allColumns...), //Columns {{- range .Columns}} diff --git a/go.mod b/go.mod index 665b9bd..9dd3e02 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,11 @@ go 1.11 require ( github.com/go-sql-driver/mysql v1.5.0 - github.com/google/go-cmp v0.5.0 + github.com/google/go-cmp v0.5.0 //tests github.com/google/uuid v1.1.1 + github.com/jackc/pgx/v4 v4.11.0 //tests github.com/lib/pq v1.7.0 - github.com/pkg/profile v1.5.0 - github.com/stretchr/testify v1.6.1 + github.com/pkg/profile v1.5.0 //tests + github.com/shopspring/decimal v1.2.0 // tests + github.com/stretchr/testify v1.6.1 // tests ) diff --git a/go.sum b/go.sum index 9321721..47af479 100644 --- a/go.sum +++ b/go.sum @@ -1,23 +1,482 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/go-jet/jet v1.0.0 h1:ENxUe/6lH82qLykIGAdZIlskZrpTeNfxjHz4VHtkVmA= -github.com/go-jet/jet v2.3.0+incompatible h1:Yg7JSERDC0f9x3dHUBMA2cxe9/qC6qlozDDO/s38USU= +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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= +github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.8.1 h1:ySBX7Q87vOMqKU2bbmKbUvtYhauDFclYbNDYIE1/h6s= +github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.0.6 h1:b1105ZGEMFe7aCvrT1Cca3VoVb4ZFMaFJLJcg/3zD+8= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= +github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= +github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= +github.com/jackc/pgtype v1.7.0 h1:6f4kVsW01QftE38ufBYxKciO6gyioXSC0ABIRLcZrGs= +github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= +github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= +github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= +github.com/jackc/pgx/v4 v4.11.0 h1:J86tSWd3Y7nKjwT/43xZBvpi04keQWx8gNC2YkdJhZI= +github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug= github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +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/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/internal/jet/clause.go b/internal/jet/clause.go index 8654547..a6a49d8 100644 --- a/internal/jet/clause.go +++ b/internal/jet/clause.go @@ -45,19 +45,25 @@ func (s *ClauseSelect) Serialize(statementType StatementType, out *SQLBuilder, o // ClauseFrom struct type ClauseFrom struct { - Table Serializer + Tables []Serializer } // Serialize serializes clause into SQLBuilder func (f *ClauseFrom) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) { - if f.Table == nil { + if len(f.Tables) == 0 { // SELECT statement does not have to have FROM clause return } out.NewLine() out.WriteString("FROM") out.IncreaseIdent() - f.Table.serialize(statementType, out, FallTrough(options)...) + for i, table := range f.Tables { + if i > 0 { + out.WriteString(",") + out.NewLine() + } + table.serialize(statementType, out, FallTrough(options)...) + } out.DecreaseIdent() } @@ -302,7 +308,7 @@ func (s *SetClause) Serialize(statementType StatementType, out *SQLBuilder, opti panic("jet: nil column in columns list for SET clause") } - out.WriteString(column.Name()) + out.WriteIdentifier(column.Name()) out.WriteString(" = ") diff --git a/internal/jet/func_expression.go b/internal/jet/func_expression.go index d4eaa57..606e7e1 100644 --- a/internal/jet/func_expression.go +++ b/internal/jet/func_expression.go @@ -801,3 +801,8 @@ func newTimestampzFunc(name string, expressions ...Expression) *timestampzFunc { return timestampzFunc } + +// Func can be used to call an custom or as of yet unsupported function in the database. +func Func(name string, expressions ...Expression) Expression { + return newFunc(name, expressions, nil) +} diff --git a/internal/jet/func_expression_test.go b/internal/jet/func_expression_test.go index c5a1e40..264be95 100644 --- a/internal/jet/func_expression_test.go +++ b/internal/jet/func_expression_test.go @@ -176,3 +176,7 @@ func TestTO_ASCII(t *testing.T) { assertClauseSerialize(t, TO_ASCII(String("Karel")), `TO_ASCII($1)`, "Karel") assertClauseSerialize(t, TO_ASCII(String("Karel")), `TO_ASCII($1)`, "Karel") } + +func TestFunc(t *testing.T) { + assertClauseSerialize(t, Func("FOO", String("test"), NULL, MAX(Int(1))), "FOO($1, NULL, MAX($2))", "test", int64(1)) +} diff --git a/internal/jet/literal_expression.go b/internal/jet/literal_expression.go index 8cdb3d7..d7cf47a 100644 --- a/internal/jet/literal_expression.go +++ b/internal/jet/literal_expression.go @@ -67,8 +67,7 @@ type integerLiteralExpression struct { integerInterfaceImpl } -// Int creates new integer literal -func Int(value int64) IntegerExpression { +func intLiteral(value interface{}) IntegerExpression { numLiteral := &integerLiteralExpression{} numLiteral.literalExpressionImpl = *literal(value) @@ -79,6 +78,46 @@ func Int(value int64) IntegerExpression { return numLiteral } +// Int creates a new 64 bit signed integer literal +func Int(value int64) IntegerExpression { + return intLiteral(value) +} + +// Int8 creates a new 8 bit signed integer literal +func Int8(value int8) IntegerExpression { + return intLiteral(value) +} + +// Int16 creates a new 16 bit signed integer literal +func Int16(value int16) IntegerExpression { + return intLiteral(value) +} + +// Int32 creates a new 32 bit signed integer literal +func Int32(value int32) IntegerExpression { + return intLiteral(value) +} + +// Uint8 creates a new 8 bit unsigned integer literal +func Uint8(value uint8) IntegerExpression { + return intLiteral(value) +} + +// Uint16 creates a new 16 bit unsigned integer literal +func Uint16(value uint16) IntegerExpression { + return intLiteral(value) +} + +// Uint32 creates a new 32 bit unsigned integer literal +func Uint32(value uint32) IntegerExpression { + return intLiteral(value) +} + +// Uint64 creates a new 64 bit unsigned integer literal +func Uint64(value uint64) IntegerExpression { + return intLiteral(value) +} + //---------------------------------------------------// type boolLiteralExpression struct { boolInterfaceImpl @@ -101,7 +140,7 @@ type floatLiteral struct { literalExpressionImpl } -// Float creates new float literal +// Float creates new float literal from float64 value func Float(value float64) FloatExpression { floatLiteral := floatLiteral{} floatLiteral.literalExpressionImpl = *literal(value) @@ -111,6 +150,16 @@ func Float(value float64) FloatExpression { return &floatLiteral } +// Decimal creates new float literal from string value +func Decimal(value string) FloatExpression { + floatLiteral := floatLiteral{} + floatLiteral.literalExpressionImpl = *literal(value) + + floatLiteral.floatInterfaceImpl.parent = &floatLiteral + + return &floatLiteral +} + //---------------------------------------------------// type stringLiteral struct { stringInterfaceImpl @@ -345,18 +394,94 @@ func WRAP(expression ...Expression) Expression { type rawExpression struct { ExpressionInterfaceImpl - Raw string + Raw string + NamedArgument map[string]interface{} + noWrap bool } func (n *rawExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { - out.WriteString(n.Raw) + if !n.noWrap && !contains(options, NoWrap) { + out.WriteByte('(') + } + + out.insertRawQuery(n.Raw, n.NamedArgument) + + if !n.noWrap && !contains(options, NoWrap) { + out.WriteByte(')') + } } // Raw can be used for any unsupported functions, operators or expressions. // For example: Raw("current_database()") -func Raw(raw string, parent ...Expression) Expression { - rawExp := &rawExpression{Raw: raw} +func Raw(raw string, namedArgs ...map[string]interface{}) Expression { + var namedArguments map[string]interface{} + + if len(namedArgs) > 0 { + namedArguments = namedArgs[0] + } + + rawExp := &rawExpression{ + Raw: raw, + NamedArgument: namedArguments, + } + rawExp.ExpressionInterfaceImpl.Parent = rawExp + + return rawExp +} + +// RawWithParent is a Raw constructor used for construction dialect specific expression +func RawWithParent(raw string, parent ...Expression) Expression { + rawExp := &rawExpression{ + Raw: raw, + noWrap: true, + } rawExp.ExpressionInterfaceImpl.Parent = OptionalOrDefaultExpression(rawExp, parent...) return rawExp } + +// RawInt helper that for integer expressions +func RawInt(raw string, namedArgs ...map[string]interface{}) IntegerExpression { + return IntExp(Raw(raw, namedArgs...)) +} + +// RawFloat helper that for float expressions +func RawFloat(raw string, namedArgs ...map[string]interface{}) FloatExpression { + return FloatExp(Raw(raw, namedArgs...)) +} + +// RawString helper that for string expressions +func RawString(raw string, namedArgs ...map[string]interface{}) StringExpression { + return StringExp(Raw(raw, namedArgs...)) +} + +// RawTime helper that for time expressions +func RawTime(raw string, namedArgs ...map[string]interface{}) TimeExpression { + return TimeExp(Raw(raw, namedArgs...)) +} + +// RawTimez helper that for time with time zone expressions +func RawTimez(raw string, namedArgs ...map[string]interface{}) TimezExpression { + return TimezExp(Raw(raw, namedArgs...)) +} + +// RawTimestamp helper that for timestamp expressions +func RawTimestamp(raw string, namedArgs ...map[string]interface{}) TimestampExpression { + return TimestampExp(Raw(raw, namedArgs...)) +} + +// RawTimestampz helper that for timestamp with time zone expressions +func RawTimestampz(raw string, namedArgs ...map[string]interface{}) TimestampzExpression { + return TimestampzExp(Raw(raw, namedArgs...)) +} + +// RawDate helper that for date expressions +func RawDate(raw string, namedArgs ...map[string]interface{}) DateExpression { + return DateExp(Raw(raw, namedArgs...)) +} + +// UUID is a helper function to create string literal expression from uuid object +// value can be any uuid type with a String method +func UUID(value fmt.Stringer) StringExpression { + return String(value.String()) +} diff --git a/internal/jet/literal_expression_test.go b/internal/jet/literal_expression_test.go index 5142f27..182aa25 100644 --- a/internal/jet/literal_expression_test.go +++ b/internal/jet/literal_expression_test.go @@ -6,7 +6,7 @@ import ( ) func TestRawExpression(t *testing.T) { - assertClauseSerialize(t, Raw("current_database()"), "current_database()") + assertClauseSerialize(t, Raw("current_database()"), "(current_database())") var timeT = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC) diff --git a/internal/jet/raw_statement.go b/internal/jet/raw_statement.go new file mode 100644 index 0000000..191c7b4 --- /dev/null +++ b/internal/jet/raw_statement.go @@ -0,0 +1,47 @@ +package jet + +type rawStatementImpl struct { + serializerStatementInterfaceImpl + + RawQuery string + NamedArguments map[string]interface{} +} + +// RawStatement creates new sql statements from raw query and optional map of named arguments +func RawStatement(dialect Dialect, rawQuery string, namedArgument ...map[string]interface{}) Statement { + newRawStatement := rawStatementImpl{ + serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{ + dialect: dialect, + statementType: "", + parent: nil, + }, + RawQuery: rawQuery, + } + + if len(namedArgument) > 0 { + newRawStatement.NamedArguments = namedArgument[0] + } + + newRawStatement.parent = &newRawStatement + + return &newRawStatement +} + +func (s *rawStatementImpl) projections() ProjectionList { + return nil +} + +func (s *rawStatementImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { + if !contains(options, NoWrap) { + out.WriteString("(") + out.IncreaseIdent() + } + + out.insertRawQuery(s.RawQuery, s.NamedArguments) + + if !contains(options, NoWrap) { + out.DecreaseIdent() + out.NewLine() + out.WriteString(")") + } +} diff --git a/internal/jet/select_table.go b/internal/jet/select_table.go index 52689d4..541992f 100644 --- a/internal/jet/select_table.go +++ b/internal/jet/select_table.go @@ -13,8 +13,8 @@ type selectTableImpl struct { } // NewSelectTable func -func NewSelectTable(selectStmt SerializerStatement, alias string) SelectTable { - selectTable := &selectTableImpl{selectStmt: selectStmt, alias: alias} +func NewSelectTable(selectStmt SerializerStatement, alias string) selectTableImpl { + selectTable := selectTableImpl{selectStmt: selectStmt, alias: alias} return selectTable } @@ -38,3 +38,22 @@ func (s selectTableImpl) serialize(statement StatementType, out *SQLBuilder, opt out.WriteString("AS") out.WriteIdentifier(s.alias) } + +// -------------------------------------- + +type lateralImpl struct { + selectTableImpl +} + +// NewLateral creates new lateral expression from select statement with alias +func NewLateral(selectStmt SerializerStatement, alias string) SelectTable { + return lateralImpl{selectTableImpl: NewSelectTable(selectStmt, alias)} +} + +func (s lateralImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { + out.WriteString("LATERAL") + s.selectStmt.serialize(statement, out) + + out.WriteString("AS") + out.WriteIdentifier(s.alias) +} diff --git a/internal/jet/sql_builder.go b/internal/jet/sql_builder.go index bca078d..6241fee 100644 --- a/internal/jet/sql_builder.go +++ b/internal/jet/sql_builder.go @@ -7,6 +7,7 @@ import ( "github.com/go-jet/jet/v2/internal/utils" "github.com/google/uuid" "reflect" + "sort" "strconv" "strings" "time" @@ -135,6 +136,73 @@ func (s *SQLBuilder) insertParametrizedArgument(arg interface{}) { s.WriteString(argPlaceholder) } +func (s *SQLBuilder) insertRawQuery(raw string, namedArg map[string]interface{}) { + type namedArgumentPosition struct { + Name string + Value interface{} + Position int + } + + var namedArgumentPositions []namedArgumentPosition + + for namedArg, value := range namedArg { + rawCopy := raw + rawIndex := 0 + exists := false + + // one named argument can occur multiple times inside raw string + for { + index := strings.Index(rawCopy, namedArg) + if index == -1 { + break + } + + exists = true + namedArgumentPositions = append(namedArgumentPositions, namedArgumentPosition{ + Name: namedArg, + Value: value, + Position: rawIndex + index, + }) + + rawCopy = rawCopy[index+len(namedArg):] + rawIndex += index + len(namedArg) + } + + if !exists { + panic("jet: named argument '" + namedArg + "' does not appear in raw query") + } + } + + sort.Slice(namedArgumentPositions, func(i, j int) bool { + return namedArgumentPositions[i].Position < namedArgumentPositions[j].Position + }) + + for _, namedArgumentPos := range namedArgumentPositions { + // if named argument does not exists in raw string do not add argument to the list of arguments + // It can happen if the same argument occurs multiple times in postgres query. + if !strings.Contains(raw, namedArgumentPos.Name) { + continue + } + s.Args = append(s.Args, namedArgumentPos.Value) + currentArgNum := len(s.Args) + + placeholder := s.Dialect.ArgumentPlaceholder()(currentArgNum) + // if placeholder is not unique identifier ($1, $2, etc..), we will replace just one occurrence of the argument + toReplace := -1 // all occurrences + if placeholder == "?" { + toReplace = 1 // just one occurrence + } + + if s.Debug { + placeholder = argToString(namedArgumentPos.Value) + } + + raw = strings.Replace(raw, namedArgumentPos.Name, placeholder, toReplace) + } + + s.WriteString(raw) +} + func argToString(value interface{}) string { if utils.IsNil(value) { return "NULL" diff --git a/internal/jet/statement.go b/internal/jet/statement.go index 6e87da8..da3650d 100644 --- a/internal/jet/statement.go +++ b/internal/jet/statement.go @@ -13,19 +13,30 @@ type Statement interface { // DebugSql returns debug query where every parametrized placeholder is replaced with its argument. // Do not use it in production. Use it only for debug purposes. DebugSql() (query string) - // Query executes statement over database connection db and stores row result in destination. + // Query executes statement over database connection/transaction db and stores row result in destination. // Destination can be either pointer to struct or pointer to a slice. // If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows. Query(db qrm.DB, destination interface{}) error - // QueryContext executes statement with a context over database connection db and stores row result in destination. + // QueryContext executes statement with a context over database connection/transaction db and stores row result in destination. // Destination can be either pointer to struct or pointer to a slice. // If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows. QueryContext(ctx context.Context, db qrm.DB, destination interface{}) error - - //Exec executes statement over db connection without returning any rows. + //Exec executes statement over db connection/transaction without returning any rows. Exec(db qrm.DB) (sql.Result, error) - //Exec executes statement with context over db connection without returning any rows. + //Exec executes statement with context over db connection/transaction without returning any rows. ExecContext(ctx context.Context, db qrm.DB) (sql.Result, error) + // Rows executes statements over db connection/transaction and returns rows + Rows(ctx context.Context, db qrm.DB) (*Rows, error) +} + +// Rows wraps sql.Rows type to add query result mapping for Scan method +type Rows struct { + *sql.Rows +} + +// Scan will map the Row values into struct destination +func (r *Rows) Scan(destination interface{}) error { + return qrm.ScanOneRowToDest(r.Rows, destination) } // SerializerStatement interface @@ -99,6 +110,20 @@ func (s *serializerStatementInterfaceImpl) ExecContext(ctx context.Context, db q return db.ExecContext(ctx, query, args...) } +func (s *serializerStatementInterfaceImpl) Rows(ctx context.Context, db qrm.DB) (*Rows, error) { + query, args := s.Sql() + + callLogger(ctx, s) + + rows, err := db.QueryContext(ctx, query, args...) + + if err != nil { + return nil, err + } + + return &Rows{rows}, nil +} + func callLogger(ctx context.Context, statement Statement) { if logger != nil { logger(ctx, statement) diff --git a/internal/jet/table.go b/internal/jet/table.go index 45d46e9..c69a1dc 100644 --- a/internal/jet/table.go +++ b/internal/jet/table.go @@ -15,20 +15,27 @@ type Table interface { columns() []Column SchemaName() string TableName() string - AS(alias string) + Alias() string } // NewTable creates new table with schema Name, table Name and list of columns -func NewTable(schemaName, name string, columns ...ColumnExpression) SerializerTable { +func NewTable(schemaName, name, alias string, columns ...ColumnExpression) SerializerTable { t := tableImpl{ schemaName: schemaName, name: name, + alias: alias, columnList: columns, } + columnTableName := name + + if alias != "" { + columnTableName = alias + } + for _, c := range columns { - c.setTableName(name) + c.setTableName(columnTableName) } return &t @@ -41,14 +48,6 @@ type tableImpl struct { columnList []ColumnExpression } -func (t *tableImpl) AS(alias string) { - t.alias = alias - - for _, c := range t.columnList { - c.setTableName(alias) - } -} - func (t *tableImpl) SchemaName() string { return t.schemaName } @@ -67,13 +66,21 @@ func (t *tableImpl) columns() []Column { return ret } +func (t *tableImpl) Alias() string { + return t.alias +} + func (t *tableImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { if t == nil { panic("jet: tableImpl is nil") } - out.WriteIdentifier(t.schemaName) - out.WriteString(".") + // Use default schema if the schema name is not set + if len(t.schemaName) > 0 { + out.WriteIdentifier(t.schemaName) + out.WriteString(".") + } + out.WriteIdentifier(t.name) if len(t.alias) > 0 { @@ -129,9 +136,6 @@ func (t *joinTableImpl) TableName() string { return "" } -func (t *joinTableImpl) AS(alias string) { -} - func (t *joinTableImpl) columns() []Column { var ret []Column @@ -145,6 +149,10 @@ func (t *joinTableImpl) columns() []Column { return ret } +func (t *joinTableImpl) Alias() string { + return "" +} + func (t *joinTableImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { if t == nil { panic("jet: Join table is nil. ") diff --git a/internal/jet/table_test.go b/internal/jet/table_test.go index 66646b2..b880784 100644 --- a/internal/jet/table_test.go +++ b/internal/jet/table_test.go @@ -6,7 +6,7 @@ import ( ) func TestNewTable(t *testing.T) { - newTable := NewTable("schema", "table", IntegerColumn("intCol")) + newTable := NewTable("schema", "table", "", IntegerColumn("intCol")) require.Equal(t, newTable.SchemaName(), "schema") require.Equal(t, newTable.TableName(), "table") @@ -16,8 +16,8 @@ func TestNewTable(t *testing.T) { } func TestNewJoinTable(t *testing.T) { - newTable1 := NewTable("schema", "table", IntegerColumn("intCol1")) - newTable2 := NewTable("schema", "table2", IntegerColumn("intCol2")) + newTable1 := NewTable("schema", "table", "", IntegerColumn("intCol1")) + newTable2 := NewTable("schema", "table2", "", IntegerColumn("intCol2")) joinTable := NewJoinTable(newTable1, newTable2, InnerJoin, IntegerColumn("intCol1").EQ(IntegerColumn("intCol2"))) diff --git a/internal/jet/testutils.go b/internal/jet/testutils.go index 268ae06..43f1f5d 100644 --- a/internal/jet/testutils.go +++ b/internal/jet/testutils.go @@ -26,7 +26,7 @@ var ( table1ColBool = BoolColumn("col_bool") table1ColDate = DateColumn("col_date") ) -var table1 = NewTable("db", "table1", table1Col1, table1ColInt, table1ColFloat, table1Col3, table1ColTime, table1ColTimez, table1ColBool, table1ColDate, table1ColTimestamp, table1ColTimestampz) +var table1 = NewTable("db", "table1", "", table1Col1, table1ColInt, table1ColFloat, table1Col3, table1ColTime, table1ColTimez, table1ColBool, table1ColDate, table1ColTimestamp, table1ColTimestampz) var ( table2Col3 = IntegerColumn("col3") @@ -41,14 +41,14 @@ var ( table2ColTimestampz = TimestampzColumn("col_timestampz") table2ColDate = DateColumn("col_date") ) -var table2 = NewTable("db", "table2", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColTimestamp, table2ColTimestampz) +var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColTimestamp, table2ColTimestampz) var ( table3Col1 = IntegerColumn("col1") table3ColInt = IntegerColumn("col_int") table3StrCol = StringColumn("col2") ) -var table3 = NewTable("db", "table3", table3Col1, table3ColInt, table3StrCol) +var table3 = NewTable("db", "table3", "", table3Col1, table3ColInt, table3StrCol) func assertClauseSerialize(t *testing.T, clause Serializer, query string, args ...interface{}) { out := SQLBuilder{Dialect: defaultDialect} diff --git a/internal/testutils/test_utils.go b/internal/testutils/test_utils.go index 556ed6c..dd5e790 100644 --- a/internal/testutils/test_utils.go +++ b/internal/testutils/test_utils.go @@ -8,6 +8,7 @@ import ( "github.com/go-jet/jet/v2/internal/utils" "github.com/go-jet/jet/v2/qrm" "github.com/google/uuid" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "io/ioutil" "os" @@ -89,7 +90,7 @@ func AssertJSONFile(t *testing.T, data interface{}, testRelativePath string) { // AssertStatementSql check if statement Sql() is the same as expectedQuery and expectedArgs func AssertStatementSql(t *testing.T, query jet.Statement, expectedQuery string, expectedArgs ...interface{}) { queryStr, args := query.Sql() - require.Equal(t, queryStr, expectedQuery) + assertQueryString(t, queryStr, expectedQuery) if len(expectedArgs) == 0 { return @@ -115,8 +116,8 @@ func AssertDebugStatementSql(t *testing.T, query jet.Statement, expectedQuery st AssertDeepEqual(t, args, expectedArgs, "arguments are not equal") } - debuqSql := query.DebugSql() - require.Equal(t, debuqSql, expectedQuery) + debugSql := query.DebugSql() + assertQueryString(t, debugSql, expectedQuery) } // AssertSerialize checks if clause serialize produces expected query and args @@ -133,18 +134,6 @@ func AssertSerialize(t *testing.T, dialect jet.Dialect, serializer jet.Serialize } } -// AssertClauseSerialize checks if clause serialize produces expected query and args -func AssertClauseSerialize(t *testing.T, dialect jet.Dialect, clause jet.Clause, query string, args ...interface{}) { - out := jet.SQLBuilder{Dialect: dialect} - clause.Serialize(jet.SelectStatementType, &out) - - require.Equal(t, out.Buff.String(), query) - - if len(args) > 0 { - AssertDeepEqual(t, out.Args, args) - } -} - // AssertDebugSerialize checks if clause serialize produces expected debug query and args func AssertDebugSerialize(t *testing.T, dialect jet.Dialect, clause jet.Serializer, query string, args ...interface{}) { out := jet.SQLBuilder{Dialect: dialect, Debug: true} @@ -157,6 +146,18 @@ func AssertDebugSerialize(t *testing.T, dialect jet.Dialect, clause jet.Serializ } } +// AssertClauseSerialize checks if clause serialize produces expected query and args +func AssertClauseSerialize(t *testing.T, dialect jet.Dialect, clause jet.Clause, query string, args ...interface{}) { + out := jet.SQLBuilder{Dialect: dialect} + clause.Serialize(jet.SelectStatementType, &out) + + require.Equal(t, out.Buff.String(), query) + + if len(args) > 0 { + AssertDeepEqual(t, out.Args, args) + } +} + // AssertPanicErr checks if running a function fun produces a panic with errorStr string func AssertPanicErr(t *testing.T, fun func(), errorStr string) { defer func() { @@ -194,7 +195,7 @@ func AssertQueryPanicErr(t *testing.T, stmt jet.Statement, db qrm.DB, dest inter require.Equal(t, r, errString) }() - stmt.Query(db, dest) + _ = stmt.Query(db, dest) } // AssertFileContent check if file content at filePath contains expectedContent text. @@ -223,7 +224,24 @@ func AssertFileNamesEqual(t *testing.T, fileInfos []os.FileInfo, fileNames ...st // AssertDeepEqual checks if actual and expected objects are deeply equal. func AssertDeepEqual(t *testing.T, actual, expected interface{}, msg ...string) { - require.True(t, cmp.Equal(actual, expected), msg) + if !assert.True(t, cmp.Equal(actual, expected), msg) { + printDiff(actual, expected) + t.FailNow() + } +} + +func assertQueryString(t *testing.T, actual, expected string) { + if !assert.Equal(t, actual, expected) { + printDiff(actual, expected) + t.FailNow() + } +} + +func printDiff(actual, expected interface{}) { + fmt.Println("Actual: ") + fmt.Println(actual) + fmt.Println("Expected: ") + fmt.Println(expected) } // BoolPtr returns address of bool parameter diff --git a/mysql/delete_statement.go b/mysql/delete_statement.go index 5857e16..7f0fe6f 100644 --- a/mysql/delete_statement.go +++ b/mysql/delete_statement.go @@ -7,7 +7,7 @@ type DeleteStatement interface { Statement WHERE(expression BoolExpression) DeleteStatement - ORDER_BY(orderByClauses ...jet.OrderByClause) DeleteStatement + ORDER_BY(orderByClauses ...OrderByClause) DeleteStatement LIMIT(limit int64) DeleteStatement } @@ -38,7 +38,7 @@ func (d *deleteStatementImpl) WHERE(expression BoolExpression) DeleteStatement { return d } -func (d *deleteStatementImpl) ORDER_BY(orderByClauses ...jet.OrderByClause) DeleteStatement { +func (d *deleteStatementImpl) ORDER_BY(orderByClauses ...OrderByClause) DeleteStatement { d.OrderBy.List = orderByClauses return d } diff --git a/mysql/expressions.go b/mysql/expressions.go index 2292108..b585719 100644 --- a/mysql/expressions.go +++ b/mysql/expressions.go @@ -3,7 +3,7 @@ package mysql import "github.com/go-jet/jet/v2/internal/jet" // Expression is common interface for all expressions. -// Can be Bool, Int, Float, String, Date, Time, Timez, Timestamp or Timestampz expressions. +// Can be Bool, Int, Float, String, Date, Time or Timestamp expressions. type Expression = jet.Expression // BoolExpression interface @@ -70,9 +70,25 @@ var DateTimeExp = jet.TimestampExp // Does not add sql cast to generated sql builder output. var TimestampExp = jet.TimestampExp +// RawArgs is type used to pass optional arguments to Raw method +type RawArgs = map[string]interface{} + // Raw can be used for any unsupported functions, operators or expressions. // For example: Raw("current_database()") -var Raw = jet.Raw +// Raw helper methods for each of the mysql types +var ( + Raw = jet.Raw + + RawInt = jet.RawInt + RawFloat = jet.RawFloat + RawString = jet.RawString + RawTime = jet.RawTime + RawTimestamp = jet.RawTimestamp + RawDate = jet.RawDate +) + +// Func can be used to call an custom or as of yet unsupported function in the database. +var Func = jet.Func // NewEnumValue creates new named enum value var NewEnumValue = jet.NewEnumValue diff --git a/mysql/expressions_test.go b/mysql/expressions_test.go new file mode 100644 index 0000000..2826c08 --- /dev/null +++ b/mysql/expressions_test.go @@ -0,0 +1,62 @@ +package mysql + +import ( + "testing" + time2 "time" + + "github.com/stretchr/testify/require" +) + +func TestRaw(t *testing.T) { + assertSerialize(t, Raw("current_database()"), "(current_database())") + assertDebugSerialize(t, Raw("current_database()"), "(current_database())") + + assertSerialize(t, Raw(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22}), + "(? + table.colInt + ?)", 11, 22) + assertDebugSerialize(t, Raw(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22}), + "(11 + table.colInt + 22)") + + assertSerialize(t, + Int(700).ADD(RawInt("#1 + table.colInt + #2", RawArgs{"#1": 11, "#2": 22})), + "(? + (? + table.colInt + ?))", + int64(700), 11, 22) + assertDebugSerialize(t, + Int(700).ADD(RawInt("#1 + table.colInt + #2", RawArgs{"#1": 11, "#2": 22})), + "(700 + (11 + table.colInt + 22))") +} + +func TestRawDuplicateArguments(t *testing.T) { + assertSerialize(t, Raw(":arg + table.colInt + :arg", RawArgs{":arg": 11}), + "(? + table.colInt + ?)", 11, 11) + + assertSerialize(t, Raw("#age + table.colInt + #year + #age + #year + 11", RawArgs{"#age": 11, "#year": 2000}), + "(? + table.colInt + ? + ? + ? + 11)", 11, 2000, 11, 2000) + + assertSerialize(t, Raw("#1 + all_types.integer + #2 + #1 + #2 + #3 + #4", + RawArgs{"#1": 11, "#2": 22, "#3": 33, "#4": 44}), + `(? + all_types.integer + ? + ? + ? + ? + ?)`, 11, 22, 11, 22, 33, 44) +} + +func TestRawInvalidArguments(t *testing.T) { + defer func() { + r := recover() + require.Equal(t, "jet: named argument 'first_arg' does not appear in raw query", r) + }() + + assertSerialize(t, Raw("table.colInt + :second_arg", RawArgs{"first_arg": 11}), "(table.colInt + ?)", 22) +} + +func TestRawType(t *testing.T) { + assertSerialize(t, RawFloat("table.colInt + &float", RawArgs{"&float": 11.22}).EQ(Float(3.14)), + "((table.colInt + ?) = ?)", 11.22, 3.14) + assertSerialize(t, RawString("table.colStr || str", RawArgs{"str": "doe"}).EQ(String("john doe")), + "((table.colStr || ?) = ?)", "doe", "john doe") + + time := time2.Now() + assertSerialize(t, RawTime("table.colTime").EQ(TimeT(time)), + "((table.colTime) = CAST(? AS TIME))", time) + assertSerialize(t, RawTimestamp("table.colTimestamp").EQ(TimestampT(time)), + "((table.colTimestamp) = TIMESTAMP(?))", time) + assertSerialize(t, RawDate("table.colDate").EQ(DateT(time)), + "((table.colDate) = CAST(? AS DATE))", time) +} diff --git a/mysql/functions.go b/mysql/functions.go index 347b433..6d8193d 100644 --- a/mysql/functions.go +++ b/mysql/functions.go @@ -253,8 +253,3 @@ var EXISTS = jet.EXISTS // CASE create CASE operator with optional list of expressions var CASE = jet.CASE - -//----------------- Bit operators ---------------// - -// BIT_NOT inverts every bit in integer expression -var BIT_NOT = jet.BIT_NOT diff --git a/mysql/interval.go b/mysql/interval.go index f325bb8..c563855 100644 --- a/mysql/interval.go +++ b/mysql/interval.go @@ -97,7 +97,7 @@ func INTERVAL(value interface{}, unitType unitType) Interval { // INTERVALe creates new temporal interval from expresion and unit type. func INTERVALe(expr Expression, unitType unitType) Interval { return jet.NewInterval(jet.ListSerializer{ - Serializers: []jet.Serializer{expr, jet.Raw(string(unitType))}, + Serializers: []jet.Serializer{expr, jet.RawWithParent(string(unitType))}, Separator: " ", }) } diff --git a/mysql/lateral.go b/mysql/lateral.go new file mode 100644 index 0000000..30ebb49 --- /dev/null +++ b/mysql/lateral.go @@ -0,0 +1,24 @@ +package mysql + +import "github.com/go-jet/jet/v2/internal/jet" + +// LATERAL derived tables constructor from select statement +func LATERAL(selectStmt SelectStatement) lateralImpl { + return lateralImpl{ + selectStmt: selectStmt, + } +} + +type lateralImpl struct { + selectStmt SelectStatement +} + +func (l lateralImpl) AS(alias string) SelectTable { + subQuery := &selectTableImpl{ + SelectTable: jet.NewLateral(l.selectStmt, alias), + } + + subQuery.readableTableInterfaceImpl.parent = subQuery + + return subQuery +} diff --git a/mysql/literal.go b/mysql/literal.go index 74a9f24..0a66eb3 100644 --- a/mysql/literal.go +++ b/mysql/literal.go @@ -15,15 +15,46 @@ var ( // Bool creates new bool literal expression var Bool = jet.Bool -// Int is constructor for integer expressions literals. +// Int is constructor for 64 bit signed integer expressions literals. var Int = jet.Int -// Float creates new float literal expression +// Int8 is constructor for 8 bit signed integer expressions literals. +var Int8 = jet.Int8 + +// Int16 is constructor for 16 bit signed integer expressions literals. +var Int16 = jet.Int16 + +// Int32 is constructor for 32 bit signed integer expressions literals. +var Int32 = jet.Int32 + +// Int64 is constructor for 64 bit signed integer expressions literals. +var Int64 = jet.Int + +// Uint8 is constructor for 8 bit unsigned integer expressions literals. +var Uint8 = jet.Uint8 + +// Uint16 is constructor for 16 bit unsigned integer expressions literals. +var Uint16 = jet.Uint16 + +// Uint32 is constructor for 32 bit unsigned integer expressions literals. +var Uint32 = jet.Uint32 + +// Uint64 is constructor for 64 bit unsigned integer expressions literals. +var Uint64 = jet.Uint64 + +// Float creates new float literal expression from float64 value var Float = jet.Float +// Decimal creates new float literal expression from string value +var Decimal = jet.Decimal + // String creates new string literal expression var String = jet.String +// UUID is a helper function to create string literal expression from uuid object +// value can be any uuid type with a String method +var UUID = jet.UUID + // Date creates new date literal var Date = func(year int, month time.Month, day int) DateExpression { return CAST(jet.Date(year, month, day)).AS_DATE() diff --git a/mysql/literal_test.go b/mysql/literal_test.go index 09d331e..fb96641 100644 --- a/mysql/literal_test.go +++ b/mysql/literal_test.go @@ -1,6 +1,7 @@ package mysql import ( + "math" "testing" "time" ) @@ -13,6 +14,46 @@ func TestInt(t *testing.T) { assertSerialize(t, Int(11), `?`, int64(11)) } +func TestInt8(t *testing.T) { + val := int8(math.MinInt8) + assertSerialize(t, Int8(val), `?`, val) +} + +func TestInt16(t *testing.T) { + val := int16(math.MinInt16) + assertSerialize(t, Int16(val), `?`, val) +} + +func TestInt32(t *testing.T) { + val := int32(math.MinInt32) + assertSerialize(t, Int32(val), `?`, val) +} + +func TestInt64(t *testing.T) { + val := int64(math.MinInt64) + assertSerialize(t, Int64(val), `?`, val) +} + +func TestUint8(t *testing.T) { + val := uint8(math.MaxUint8) + assertSerialize(t, Uint8(val), `?`, val) +} + +func TestUint16(t *testing.T) { + val := uint16(math.MaxUint16) + assertSerialize(t, Uint16(val), `?`, val) +} + +func TestUint32(t *testing.T) { + val := uint32(math.MaxUint32) + assertSerialize(t, Uint32(val), `?`, val) +} + +func TestUint64(t *testing.T) { + val := uint64(math.MaxUint64) + assertSerialize(t, Uint64(val), `?`, val) +} + func TestFloat(t *testing.T) { assertSerialize(t, Float(12.34), `?`, float64(12.34)) } diff --git a/mysql/operators.go b/mysql/operators.go new file mode 100644 index 0000000..5585511 --- /dev/null +++ b/mysql/operators.go @@ -0,0 +1,9 @@ +package mysql + +import "github.com/go-jet/jet/v2/internal/jet" + +// NOT returns negation of bool expression result +var NOT = jet.NOT + +// BIT_NOT inverts every bit in integer expression result +var BIT_NOT = jet.BIT_NOT diff --git a/mysql/select_statement.go b/mysql/select_statement.go index 8882077..8ebab03 100644 --- a/mysql/select_statement.go +++ b/mysql/select_statement.go @@ -41,12 +41,12 @@ type SelectStatement interface { Expression DISTINCT() SelectStatement - FROM(table ReadableTable) SelectStatement + FROM(tables ...ReadableTable) SelectStatement WHERE(expression BoolExpression) SelectStatement GROUP_BY(groupByClauses ...jet.GroupByClause) SelectStatement HAVING(boolExpression BoolExpression) SelectStatement WINDOW(name string) windowExpand - ORDER_BY(orderByClauses ...jet.OrderByClause) SelectStatement + ORDER_BY(orderByClauses ...OrderByClause) SelectStatement LIMIT(limit int64) SelectStatement OFFSET(offset int64) SelectStatement FOR(lock RowLock) SelectStatement @@ -70,7 +70,9 @@ func newSelectStatement(table ReadableTable, projections []Projection) SelectSta &newSelect.Limit, &newSelect.Offset, &newSelect.For, &newSelect.ShareLock) newSelect.Select.ProjectionList = projections - newSelect.From.Table = table + if table != nil { + newSelect.From.Tables = []jet.Serializer{table} + } newSelect.Limit.Count = -1 newSelect.Offset.Count = -1 newSelect.ShareLock.Name = "LOCK IN SHARE MODE" @@ -103,8 +105,11 @@ func (s *selectStatementImpl) DISTINCT() SelectStatement { return s } -func (s *selectStatementImpl) FROM(table ReadableTable) SelectStatement { - s.From.Table = table +func (s *selectStatementImpl) FROM(tables ...ReadableTable) SelectStatement { + s.From.Tables = nil + for _, table := range tables { + s.From.Tables = append(s.From.Tables, table) + } return s } @@ -128,7 +133,7 @@ func (s *selectStatementImpl) WINDOW(name string) windowExpand { return windowExpand{selectStatement: s} } -func (s *selectStatementImpl) ORDER_BY(orderByClauses ...jet.OrderByClause) SelectStatement { +func (s *selectStatementImpl) ORDER_BY(orderByClauses ...OrderByClause) SelectStatement { s.OrderBy.List = orderByClauses return s } diff --git a/mysql/select_statement_test.go b/mysql/select_statement_test.go index 261ac20..630f800 100644 --- a/mysql/select_statement_test.go +++ b/mysql/select_statement_test.go @@ -132,3 +132,25 @@ FROM db.table1 LOCK IN SHARE MODE; `) } + +func TestSelect_NOT_EXISTS(t *testing.T) { + testutils.AssertStatementSql(t, + SELECT(table1ColInt). + FROM(table1). + WHERE( + NOT(EXISTS( + SELECT(table2ColInt). + FROM(table2). + WHERE( + table1ColInt.EQ(table2ColInt), + ), + ))), ` +SELECT table1.col_int AS "table1.col_int" +FROM db.table1 +WHERE (NOT (EXISTS ( + SELECT table2.col_int AS "table2.col_int" + FROM db.table2 + WHERE table1.col_int = table2.col_int + ))); +`) +} diff --git a/mysql/set_statement.go b/mysql/set_statement.go index df1ed8a..ec9f8fa 100644 --- a/mysql/set_statement.go +++ b/mysql/set_statement.go @@ -17,7 +17,7 @@ func UNION_ALL(lhs, rhs jet.SerializerStatement, selects ...jet.SerializerStatem type setStatement interface { setOperators - ORDER_BY(orderByClauses ...jet.OrderByClause) setStatement + ORDER_BY(orderByClauses ...OrderByClause) setStatement LIMIT(limit int64) setStatement OFFSET(offset int64) setStatement @@ -70,7 +70,7 @@ func newSetStatementImpl(operator string, all bool, selects []jet.SerializerStat return newSetStatement } -func (s *setStatementImpl) ORDER_BY(orderByClauses ...jet.OrderByClause) setStatement { +func (s *setStatementImpl) ORDER_BY(orderByClauses ...OrderByClause) setStatement { s.setOperator.OrderBy.List = orderByClauses return s } diff --git a/mysql/statement.go b/mysql/statement.go new file mode 100644 index 0000000..073adce --- /dev/null +++ b/mysql/statement.go @@ -0,0 +1,8 @@ +package mysql + +import "github.com/go-jet/jet/v2/internal/jet" + +// RawStatement creates new sql statements from raw query and optional map of named arguments +func RawStatement(rawQuery string, namedArguments ...RawArgs) Statement { + return jet.RawStatement(Dialect, rawQuery, namedArguments...) +} diff --git a/mysql/table.go b/mysql/table.go index 5630966..0ae7ee8 100644 --- a/mysql/table.go +++ b/mysql/table.go @@ -77,9 +77,9 @@ func (r readableTableInterfaceImpl) CROSS_JOIN(table ReadableTable) joinSelectUp } // NewTable creates new table with schema Name, table Name and list of columns -func NewTable(schemaName, name string, columns ...jet.ColumnExpression) Table { +func NewTable(schemaName, name, alias string, columns ...jet.ColumnExpression) Table { t := &tableImpl{ - SerializerTable: jet.NewTable(schemaName, name, columns...), + SerializerTable: jet.NewTable(schemaName, name, alias, columns...), } t.readableTableInterfaceImpl.parent = t diff --git a/mysql/types.go b/mysql/types.go index 9ebaa27..8c6608f 100644 --- a/mysql/types.go +++ b/mysql/types.go @@ -17,5 +17,8 @@ type ColumnAssigment = jet.ColumnAssigment // PrintableStatement is a statement which sql query can be logged type PrintableStatement = jet.PrintableStatement +// OrderByClause is the combination of an expression and the wanted ordering to use as input for ORDER BY. +type OrderByClause = jet.OrderByClause + // SetLogger sets automatic statement logging var SetLogger = jet.SetLoggerFunc diff --git a/mysql/update_statement_test.go b/mysql/update_statement_test.go index fe3be01..f5284a7 100644 --- a/mysql/update_statement_test.go +++ b/mysql/update_statement_test.go @@ -2,6 +2,7 @@ package mysql import ( "fmt" + "strings" "testing" ) @@ -52,11 +53,29 @@ WHERE table1.col1 = ?; ). WHERE(table1Col1.EQ(Int(2))) - //fmt.Println(stmt.Sql()) - assertStatementSql(t, stmt, expectedSQL, int64(2)) } +func TestUpdateReservedWorldColumn(t *testing.T) { + type table struct { + Load string + } + + loadColumn := StringColumn("Load") + assertStatementSql(t, + table1.UPDATE(loadColumn). + MODEL( + table{ + Load: "foo", + }, + ). + WHERE(loadColumn.EQ(String("bar"))), strings.Replace(` +UPDATE db.table1 +SET ''Load'' = ? +WHERE ''Load'' = ?; +`, "''", "`", -1), "foo", "bar") +} + func TestInvalidInputs(t *testing.T) { assertStatementSqlErr(t, table1.UPDATE(table1ColInt).SET(1), "jet: WHERE clause not set") assertStatementSqlErr(t, table1.UPDATE(nil).SET(1), "jet: nil column in columns list for SET clause") diff --git a/mysql/utils_test.go b/mysql/utils_test.go index cc8b3c3..d95638a 100644 --- a/mysql/utils_test.go +++ b/mysql/utils_test.go @@ -16,19 +16,7 @@ var table1ColTimestamp = TimestampColumn("col_timestamp") var table1ColDate = DateColumn("col_date") var table1ColTime = TimeColumn("col_time") -var table1 = NewTable( - "db", - "table1", - table1Col1, - table1ColInt, - table1ColFloat, - table1ColString, - table1Col3, - table1ColBool, - table1ColDate, - table1ColTimestamp, - table1ColTime, -) +var table1 = NewTable("db", "table1", "", table1Col1, table1ColInt, table1ColFloat, table1ColString, table1Col3, table1ColBool, table1ColDate, table1ColTimestamp, table1ColTime) var table2Col3 = IntegerColumn("col3") var table2Col4 = IntegerColumn("col4") @@ -39,28 +27,12 @@ var table2ColBool = BoolColumn("col_bool") var table2ColTimestamp = TimestampColumn("col_timestamp") var table2ColDate = DateColumn("col_date") -var table2 = NewTable( - "db", - "table2", - table2Col3, - table2Col4, - table2ColInt, - table2ColFloat, - table2ColStr, - table2ColBool, - table2ColDate, - table2ColTimestamp, -) +var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColDate, table2ColTimestamp) var table3Col1 = IntegerColumn("col1") var table3ColInt = IntegerColumn("col_int") var table3StrCol = StringColumn("col2") -var table3 = NewTable( - "db", - "table3", - table3Col1, - table3ColInt, - table3StrCol) +var table3 = NewTable("db", "table3", "", table3Col1, table3ColInt, table3StrCol) func assertSerialize(t *testing.T, clause jet.Serializer, query string, args ...interface{}) { testutils.AssertSerialize(t, Dialect, clause, query, args...) diff --git a/postgres/dialect_test.go b/postgres/dialect_test.go index 7bf9242..9b7b3d1 100644 --- a/postgres/dialect_test.go +++ b/postgres/dialect_test.go @@ -80,13 +80,7 @@ func TestReservedWordEscaped(t *testing.T) { var table1ColVariadic = IntervalColumn("VARIADIC") var table1ColProcedure = IntervalColumn("procedure") - _ = NewTable( - "db", - "table1", - table1ColUser, - table1ColVariadic, - table1ColProcedure, - ) + _ = NewTable("db", "table1", "", table1ColUser, table1ColVariadic, table1ColProcedure) assertSerialize(t, table1ColUser, `table1."user"`) assertSerialize(t, table1ColVariadic, `table1."VARIADIC"`) diff --git a/postgres/expressions.go b/postgres/expressions.go index 61c0449..8d0be88 100644 --- a/postgres/expressions.go +++ b/postgres/expressions.go @@ -81,9 +81,27 @@ var TimestampExp = jet.TimestampExp // Does not add sql cast to generated sql builder output. var TimestampzExp = jet.TimestampzExp +// RawArgs is type used to pass optional arguments to Raw method +type RawArgs = map[string]interface{} + // Raw can be used for any unsupported functions, operators or expressions. // For example: Raw("current_database()") -var Raw = jet.Raw +// Raw helper methods for each of the postgres types +var ( + Raw = jet.Raw + + RawInt = jet.RawInt + RawFloat = jet.RawFloat + RawString = jet.RawString + RawTime = jet.RawTime + RawTimez = jet.RawTimez + RawTimestamp = jet.RawTimestamp + RawTimestampz = jet.RawTimestampz + RawDate = jet.RawDate +) + +// Func can be used to call an custom or as of yet unsupported function in the database. +var Func = jet.Func // NewEnumValue creates new named enum value var NewEnumValue = jet.NewEnumValue diff --git a/postgres/expressions_test.go b/postgres/expressions_test.go new file mode 100644 index 0000000..77c3dee --- /dev/null +++ b/postgres/expressions_test.go @@ -0,0 +1,76 @@ +package postgres + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestRaw(t *testing.T) { + assertSerialize(t, Raw("current_database()"), "(current_database())") + assertDebugSerialize(t, Raw("current_database()"), "(current_database())") + + assertSerialize(t, Raw(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22}), + "($1 + table.colInt + $2)", 11, 22) + assertDebugSerialize(t, Raw(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22}), + "(11 + table.colInt + 22)") + + assertSerialize(t, + Int(700).ADD(RawInt(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22})), + "($1 + ($2 + table.colInt + $3))", + int64(700), 11, 22) + assertDebugSerialize(t, + Int(700).ADD(RawInt(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22})), + "(700 + (11 + table.colInt + 22))") +} + +func TestDuplicateArguments(t *testing.T) { + assertSerialize(t, Raw(":arg + table.colInt + :arg", RawArgs{":arg": 11}), + "($1 + table.colInt + $1)", 11) + assertDebugSerialize(t, Raw(":arg + table.colInt + :arg", RawArgs{":arg": 11}), + "(11 + table.colInt + 11)") + + assertSerialize(t, Raw("#age + table.colInt + #year + #age + #year + 11", RawArgs{"#age": 11, "#year": 2000}), + "($1 + table.colInt + $2 + $1 + $2 + 11)", 11, 2000) + assertDebugSerialize(t, Raw("#age + table.colInt + #year + #age + #year + 11", RawArgs{"#age": 11, "#year": 2000}), + "(11 + table.colInt + 2000 + 11 + 2000 + 11)") + + assertSerialize(t, Raw("#1 + all_types.integer + #2 + #1 + #2 + #3 + #4", + RawArgs{"#1": 11, "#2": 22, "#3": 33, "#4": 44}), + `($1 + all_types.integer + $2 + $1 + $2 + $3 + $4)`, 11, 22, 33, 44) + assertDebugSerialize(t, Raw("#1 + all_types.integer + #2 + #1 + #2 + #3 + #4", + RawArgs{"#1": 11, "#2": 22, "#3": 33, "#4": 44}), + `(11 + all_types.integer + 22 + 11 + 22 + 33 + 44)`) +} + +func TestRawInvalidArguments(t *testing.T) { + defer func() { + r := recover() + require.Equal(t, "jet: named argument 'first_arg' does not appear in raw query", r) + }() + + assertSerialize(t, Raw("table.colInt + :second_arg", RawArgs{ + "first_arg": 11, + "second_arg": 22, + }), "(table.colInt + $1)", 22) +} + +func TestRawHelperMethods(t *testing.T) { + assertSerialize(t, RawFloat("table.colInt + :float", RawArgs{":float": 11.22}).EQ(Float(3.14)), + "((table.colInt + $1) = $2)", 11.22, 3.14) + assertSerialize(t, RawString("table.colStr || str", RawArgs{"str": "doe"}).EQ(String("john doe")), + "((table.colStr || $1) = $2)", "doe", "john doe") + + now := time.Now() + assertSerialize(t, RawTime("table.colTime").EQ(TimeT(now)), + "((table.colTime) = $1::time without time zone)", now) + assertSerialize(t, RawTimez("table.colTime").EQ(TimezT(now)), + "((table.colTime) = $1::time with time zone)", now) + assertSerialize(t, RawTimestamp("table.colTimestamp").EQ(TimestampT(now)), + "((table.colTimestamp) = $1::timestamp without time zone)", now) + assertSerialize(t, RawTimestampz("table.colTimestampz").EQ(TimestampzT(now)), + "((table.colTimestampz) = $1::timestamp with time zone)", now) + assertSerialize(t, RawDate("table.colDate").EQ(DateT(now)), + "((table.colDate) = $1::date)", now) +} diff --git a/postgres/interval_expression.go b/postgres/interval_expression.go index df2ed60..b8468cf 100644 --- a/postgres/interval_expression.go +++ b/postgres/interval_expression.go @@ -128,7 +128,7 @@ func INTERVAL(quantityAndUnit ...quantityAndUnit) IntervalExpression { newInterval := &intervalExpression{} - newInterval.Expression = jet.Raw(intervalStr, newInterval) + newInterval.Expression = jet.RawWithParent(intervalStr, newInterval) newInterval.intervalInterfaceImpl.parent = newInterval return newInterval diff --git a/postgres/lateral.go b/postgres/lateral.go new file mode 100644 index 0000000..7b0ded6 --- /dev/null +++ b/postgres/lateral.go @@ -0,0 +1,24 @@ +package postgres + +import "github.com/go-jet/jet/v2/internal/jet" + +// LATERAL derived tables constructor from select statement +func LATERAL(selectStmt SelectStatement) lateralImpl { + return lateralImpl{ + selectStmt: selectStmt, + } +} + +type lateralImpl struct { + selectStmt SelectStatement +} + +func (l lateralImpl) AS(alias string) SelectTable { + subQuery := &selectTableImpl{ + SelectTable: jet.NewLateral(l.selectStmt, alias), + } + + subQuery.readableTableInterfaceImpl.parent = subQuery + + return subQuery +} diff --git a/postgres/lateral_test.go b/postgres/lateral_test.go new file mode 100644 index 0000000..35a0429 --- /dev/null +++ b/postgres/lateral_test.go @@ -0,0 +1,14 @@ +package postgres + +import "testing" + +func TestLATERAL(t *testing.T) { + assertSerialize(t, + LATERAL( + SELECT(Int(1)), + ).AS("lat1"), + + `LATERAL ( + SELECT $1 +) AS lat1`) +} diff --git a/postgres/literal.go b/postgres/literal.go index 0063cf4..8ee3235 100644 --- a/postgres/literal.go +++ b/postgres/literal.go @@ -1,25 +1,62 @@ package postgres import ( - "github.com/go-jet/jet/v2/internal/jet" "time" + + "github.com/go-jet/jet/v2/internal/jet" ) // Bool creates new bool literal expression var Bool = jet.Bool -// Int creates new integer literal expression +// Int is constructor for 64 bit signed integer expressions literals. var Int = jet.Int +// Int8 is constructor for 8 bit signed integer expressions literals. +var Int8 = jet.Int8 + +// Int16 is constructor for 16 bit signed integer expressions literals. +var Int16 = jet.Int16 + +// Int32 is constructor for 32 bit signed integer expressions literals. +var Int32 = jet.Int32 + +// Int64 is constructor for 64 bit signed integer expressions literals. +var Int64 = jet.Int + +// Uint8 is constructor for 8 bit unsigned integer expressions literals. +var Uint8 = jet.Uint8 + +// Uint16 is constructor for 16 bit unsigned integer expressions literals. +var Uint16 = jet.Uint16 + +// Uint32 is constructor for 32 bit unsigned integer expressions literals. +var Uint32 = jet.Uint32 + +// Uint64 is constructor for 64 bit unsigned integer expressions literals. +var Uint64 = jet.Uint64 + // Float creates new float literal expression var Float = jet.Float +// Decimal creates new float literal expression +var Decimal = jet.Decimal + // String creates new string literal expression var String = jet.String -// Bytea craates new bytea literal expression -var Bytea = func(value string) StringExpression { - return CAST(jet.String(value)).AS_BYTEA() +// UUID is a helper function to create string literal expression from uuid object +// value can be any uuid type with a String method +var UUID = jet.UUID + +// Bytea creates new bytea literal expression +var Bytea = func(value interface{}) StringExpression { + switch value.(type) { + case string, []byte: + default: + panic("Bytea parameter value has to be of the type string or []byte") + } + return CAST(jet.Literal(value)).AS_BYTEA() } // Date creates new date literal expression diff --git a/postgres/literal_test.go b/postgres/literal_test.go index 5206aaa..52a15a0 100644 --- a/postgres/literal_test.go +++ b/postgres/literal_test.go @@ -1,6 +1,7 @@ package postgres import ( + "math" "testing" "time" ) @@ -13,6 +14,46 @@ func TestInt(t *testing.T) { assertSerialize(t, Int(11), `$1`, int64(11)) } +func TestInt8(t *testing.T) { + val := int8(math.MinInt8) + assertSerialize(t, Int8(val), `$1`, val) +} + +func TestInt16(t *testing.T) { + val := int16(math.MinInt16) + assertSerialize(t, Int16(val), `$1`, val) +} + +func TestInt32(t *testing.T) { + val := int32(math.MinInt32) + assertSerialize(t, Int32(val), `$1`, val) +} + +func TestInt64(t *testing.T) { + val := int64(math.MinInt64) + assertSerialize(t, Int64(val), `$1`, val) +} + +func TestUint8(t *testing.T) { + val := uint8(math.MaxUint8) + assertSerialize(t, Uint8(val), `$1`, val) +} + +func TestUint16(t *testing.T) { + val := uint16(math.MaxUint16) + assertSerialize(t, Uint16(val), `$1`, val) +} + +func TestUint32(t *testing.T) { + val := uint32(math.MaxUint32) + assertSerialize(t, Uint32(val), `$1`, val) +} + +func TestUint64(t *testing.T) { + val := uint64(math.MaxUint64) + assertSerialize(t, Uint64(val), `$1`, val) +} + func TestFloat(t *testing.T) { assertSerialize(t, Float(12.34), `$1`, float64(12.34)) } @@ -21,6 +62,11 @@ func TestString(t *testing.T) { assertSerialize(t, String("Some text"), `$1`, "Some text") } +func TestBytea(t *testing.T) { + assertSerialize(t, Bytea("Some text"), `$1::bytea`, "Some text") + assertSerialize(t, Bytea([]byte("Some byte array")), `$1::bytea`, []byte("Some byte array")) +} + func TestDate(t *testing.T) { assertSerialize(t, Date(2014, time.January, 2), `$1::date`, "2014-01-02") assertSerialize(t, DateT(time.Now()), `$1::date`) diff --git a/postgres/select_statement.go b/postgres/select_statement.go index 7b7e969..516ae25 100644 --- a/postgres/select_statement.go +++ b/postgres/select_statement.go @@ -44,12 +44,12 @@ type SelectStatement interface { Expression DISTINCT() SelectStatement - FROM(table ReadableTable) SelectStatement + FROM(tables ...ReadableTable) SelectStatement WHERE(expression BoolExpression) SelectStatement GROUP_BY(groupByClauses ...jet.GroupByClause) SelectStatement HAVING(boolExpression BoolExpression) SelectStatement WINDOW(name string) windowExpand - ORDER_BY(orderByClauses ...jet.OrderByClause) SelectStatement + ORDER_BY(orderByClauses ...OrderByClause) SelectStatement LIMIT(limit int64) SelectStatement OFFSET(offset int64) SelectStatement FOR(lock RowLock) SelectStatement @@ -76,7 +76,9 @@ func newSelectStatement(table ReadableTable, projections []Projection) SelectSta &newSelect.Limit, &newSelect.Offset, &newSelect.For) newSelect.Select.ProjectionList = projections - newSelect.From.Table = table + if table != nil { + newSelect.From.Tables = []jet.Serializer{table} + } newSelect.Limit.Count = -1 newSelect.Offset.Count = -1 @@ -106,8 +108,11 @@ func (s *selectStatementImpl) DISTINCT() SelectStatement { return s } -func (s *selectStatementImpl) FROM(table ReadableTable) SelectStatement { - s.From.Table = table +func (s *selectStatementImpl) FROM(tables ...ReadableTable) SelectStatement { + s.From.Tables = nil + for _, table := range tables { + s.From.Tables = append(s.From.Tables, table) + } return s } @@ -131,7 +136,7 @@ func (s *selectStatementImpl) WINDOW(name string) windowExpand { return windowExpand{selectStatement: s} } -func (s *selectStatementImpl) ORDER_BY(orderByClauses ...jet.OrderByClause) SelectStatement { +func (s *selectStatementImpl) ORDER_BY(orderByClauses ...OrderByClause) SelectStatement { s.OrderBy.List = orderByClauses return s } diff --git a/postgres/set_statement.go b/postgres/set_statement.go index 1f63f2e..236f8de 100644 --- a/postgres/set_statement.go +++ b/postgres/set_statement.go @@ -41,7 +41,7 @@ func EXCEPT_ALL(lhs, rhs jet.SerializerStatement) setStatement { type setStatement interface { setOperators - ORDER_BY(orderByClauses ...jet.OrderByClause) setStatement + ORDER_BY(orderByClauses ...OrderByClause) setStatement LIMIT(limit int64) setStatement OFFSET(offset int64) setStatement @@ -114,7 +114,7 @@ func newSetStatementImpl(operator string, all bool, selects []jet.SerializerStat return newSetStatement } -func (s *setStatementImpl) ORDER_BY(orderByClauses ...jet.OrderByClause) setStatement { +func (s *setStatementImpl) ORDER_BY(orderByClauses ...OrderByClause) setStatement { s.setOperator.OrderBy.List = orderByClauses return s } diff --git a/postgres/statement.go b/postgres/statement.go new file mode 100644 index 0000000..d10bd65 --- /dev/null +++ b/postgres/statement.go @@ -0,0 +1,8 @@ +package postgres + +import "github.com/go-jet/jet/v2/internal/jet" + +// RawStatement creates new sql statements from raw query and optional map of named arguments +func RawStatement(rawQuery string, namedArguments ...RawArgs) Statement { + return jet.RawStatement(Dialect, rawQuery, namedArguments...) +} diff --git a/postgres/table.go b/postgres/table.go index ac153d3..f90c114 100644 --- a/postgres/table.go +++ b/postgres/table.go @@ -109,10 +109,10 @@ type tableImpl struct { } // NewTable creates new table with schema Name, table Name and list of columns -func NewTable(schemaName, name string, columns ...jet.ColumnExpression) Table { +func NewTable(schemaName, name, alias string, columns ...jet.ColumnExpression) Table { t := &tableImpl{ - SerializerTable: jet.NewTable(schemaName, name, columns...), + SerializerTable: jet.NewTable(schemaName, name, alias, columns...), } t.readableTableInterfaceImpl.parent = t diff --git a/postgres/table_test.go b/postgres/table_test.go index 3b6f498..fa12228 100644 --- a/postgres/table_test.go +++ b/postgres/table_test.go @@ -99,3 +99,26 @@ CROSS JOIN db.table2`) CROSS JOIN db.table2 CROSS JOIN db.table3`) } + +func TestImplicitCROSS_JOIN(t *testing.T) { + assertDebugStatementSql(t, + SELECT(table1Col1, table2Col3). + FROM(table1, table2), + ` +SELECT table1.col1 AS "table1.col1", + table2.col3 AS "table2.col3" +FROM db.table1, + db.table2; +`) + assertDebugStatementSql(t, + SELECT( + table1Col1, table2Col3, + ).FROM(table1, table2, table3), + ` +SELECT table1.col1 AS "table1.col1", + table2.col3 AS "table2.col3" +FROM db.table1, + db.table2, + db.table3; +`) +} diff --git a/postgres/types.go b/postgres/types.go index e2a51eb..05354b7 100644 --- a/postgres/types.go +++ b/postgres/types.go @@ -17,5 +17,8 @@ type ColumnAssigment = jet.ColumnAssigment // PrintableStatement is a statement which sql query can be logged type PrintableStatement = jet.PrintableStatement +// OrderByClause is the combination of an expression and the wanted ordering to use as input for ORDER BY. +type OrderByClause = jet.OrderByClause + // SetLogger sets automatic statement logging var SetLogger = jet.SetLoggerFunc diff --git a/postgres/utils_test.go b/postgres/utils_test.go index ef5f72e..292d7e4 100644 --- a/postgres/utils_test.go +++ b/postgres/utils_test.go @@ -21,6 +21,7 @@ var table1ColInterval = IntervalColumn("col_interval") var table1 = NewTable( "db", "table1", + "", table1Col1, table1ColInt, table1ColFloat, @@ -46,37 +47,21 @@ var table2ColTimestampz = TimestampzColumn("col_timestampz") var table2ColDate = DateColumn("col_date") var table2ColInterval = IntervalColumn("col_interval") -var table2 = NewTable( - "db", - "table2", - table2Col3, - table2Col4, - table2ColInt, - table2ColFloat, - table2ColStr, - table2ColBool, - table2ColTime, - table2ColTimez, - table2ColDate, - table2ColTimestamp, - table2ColTimestampz, - table2ColInterval, -) +var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColTimestamp, table2ColTimestampz, table2ColInterval) var table3Col1 = IntegerColumn("col1") var table3ColInt = IntegerColumn("col_int") var table3StrCol = StringColumn("col2") -var table3 = NewTable( - "db", - "table3", - table3Col1, - table3ColInt, - table3StrCol) +var table3 = NewTable("db", "table3", "", table3Col1, table3ColInt, table3StrCol) func assertSerialize(t *testing.T, serializer jet.Serializer, query string, args ...interface{}) { testutils.AssertSerialize(t, Dialect, serializer, query, args...) } +func assertDebugSerialize(t *testing.T, serializer jet.Serializer, query string, args ...interface{}) { + testutils.AssertDebugSerialize(t, Dialect, serializer, query, args...) +} + func assertClauseSerialize(t *testing.T, clause jet.Clause, query string, args ...interface{}) { testutils.AssertClauseSerialize(t, Dialect, clause, query, args...) } diff --git a/qrm/db.go b/qrm/db.go index 564819a..6b319eb 100644 --- a/qrm/db.go +++ b/qrm/db.go @@ -5,7 +5,8 @@ import ( "database/sql" ) -// DB is common database interface used by jet execution +// DB is common database interface used by query result mapping +// Both *sql.DB and *sql.Tx implements DB interface type DB interface { Exec(query string, args ...interface{}) (sql.Result, error) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) diff --git a/qrm/qrm.go b/qrm/qrm.go index 7477569..51bbffa 100644 --- a/qrm/qrm.go +++ b/qrm/qrm.go @@ -2,9 +2,12 @@ package qrm import ( "context" + "database/sql" "errors" - "github.com/go-jet/jet/v2/internal/utils" + "fmt" "reflect" + + "github.com/go-jet/jet/v2/internal/utils" ) // ErrNoRows is returned by Query when query result set is empty @@ -56,6 +59,52 @@ func Query(ctx context.Context, db DB, query string, args []interface{}, destPtr } } +// ScanOneRowToDest will scan one row into struct destination +func ScanOneRowToDest(rows *sql.Rows, destPtr interface{}) error { + utils.MustBeInitializedPtr(destPtr, "jet: destination is nil") + utils.MustBe(destPtr, reflect.Ptr, "jet: destination has to be a pointer to slice or pointer to struct") + + scanContext, err := newScanContext(rows) + + if err != nil { + return fmt.Errorf("failed to create scan context, %w", err) + } + + if len(scanContext.row) == 0 { + return errors.New("empty row slice") + } + + err = rows.Scan(scanContext.row...) + + if err != nil { + return fmt.Errorf("rows scan error, %w", err) + } + + destinationPtrType := reflect.TypeOf(destPtr) + tempSlicePtrValue := reflect.New(reflect.SliceOf(destinationPtrType)) + tempSliceValue := tempSlicePtrValue.Elem() + + _, err = mapRowToSlice(scanContext, "", tempSlicePtrValue, nil) + + if err != nil { + return fmt.Errorf("failed to map a row, %w", err) + } + + // edge case when row result set contains only NULLs. + if tempSliceValue.Len() == 0 { + return nil + } + + destValue := reflect.ValueOf(destPtr).Elem() + firstTempSliceValue := tempSliceValue.Index(0).Elem() + + if destValue.Type().AssignableTo(firstTempSliceValue.Type()) { + destValue.Set(tempSliceValue.Index(0).Elem()) + } + + return nil +} + func queryToSlice(ctx context.Context, db DB, query string, args []interface{}, slicePtr interface{}) (rowsProcessed int64, err error) { if ctx == nil { ctx = context.Background() diff --git a/qrm/utill.go b/qrm/utill.go index 5d8455d..f485797 100644 --- a/qrm/utill.go +++ b/qrm/utill.go @@ -7,6 +7,7 @@ import ( "github.com/go-jet/jet/v2/qrm/internal" "github.com/google/uuid" "reflect" + "strconv" "strings" "time" ) @@ -182,12 +183,16 @@ func isIntegerType(value reflect.Type) bool { return false } -func tryAssign(source, destination reflect.Value) bool { - if source.Type().ConvertibleTo(destination.Type()) { - source = source.Convert(destination.Type()) - } +func isNumber(valueType reflect.Type) bool { + return isIntegerType(valueType) || valueType == float64Type || valueType == float32Type +} - if isIntegerType(source.Type()) && destination.Type() == boolType { +func tryAssign(source, destination reflect.Value) bool { + + switch { + case source.Type().ConvertibleTo(destination.Type()): + source = source.Convert(destination.Type()) + case isIntegerType(source.Type()) && destination.Type() == boolType: intValue := source.Int() if intValue == 1 { @@ -195,6 +200,18 @@ func tryAssign(source, destination reflect.Value) bool { } else if intValue == 0 { source = reflect.ValueOf(false) } + case source.Type() == stringType && isNumber(destination.Type()): + // if source is string and destination is a number(int8, int32, float32, ...), we first parse string to float64 number + // and then parsed number is converted into destination type + f, err := strconv.ParseFloat(source.String(), 64) + if err != nil { + return false + } + source = reflect.ValueOf(f) + + if source.Type().ConvertibleTo(destination.Type()) { + source = source.Convert(destination.Type()) + } } if source.Type().AssignableTo(destination.Type()) { @@ -281,6 +298,9 @@ var int32Type = reflect.TypeOf(int32(1)) var uint32Type = reflect.TypeOf(uint32(1)) var int64Type = reflect.TypeOf(int64(1)) var uint64Type = reflect.TypeOf(uint64(1)) +var float32Type = reflect.TypeOf(float32(1)) +var float64Type = reflect.TypeOf(float64(1)) +var stringType = reflect.TypeOf("") var nullBoolType = reflect.TypeOf(sql.NullBool{}) var nullInt8Type = reflect.TypeOf(internal.NullInt8{}) @@ -308,7 +328,7 @@ func newScanType(columnType *sql.ColumnType) reflect.Type { return nullStringType case "FLOAT4": return nullFloat32Type - case "FLOAT8", "NUMERIC", "DECIMAL", "FLOAT", "DOUBLE": + case "FLOAT8", "FLOAT", "DOUBLE": return nullFloat64Type case "BOOL": return nullBoolType diff --git a/qrm/utill_test.go b/qrm/utill_test.go index 897bb2c..e23fa15 100644 --- a/qrm/utill_test.go +++ b/qrm/utill_test.go @@ -35,3 +35,48 @@ func TestIsSimpleModelType(t *testing.T) { require.Equal(t, isSimpleModelType(reflect.TypeOf([]string{"str"})), false) require.Equal(t, isSimpleModelType(reflect.TypeOf([]int{1, 2})), false) } + +func TestTryAssign(t *testing.T) { + convertible := int16(16) + intBool1 := int32(1) + intBool0 := int32(0) + intBool2 := int32(2) + floatStr := "1.11" + floatErr := "1.abcd2" + str := "some string" + + destination := struct { + Convertible int64 + IntBool1 bool + IntBool0 bool + IntBool2 bool + FloatStr float64 + FloatErr float64 + Str string + }{} + + testValue := reflect.ValueOf(&destination).Elem() + + // convertible + require.True(t, tryAssign(reflect.ValueOf(convertible), testValue.FieldByName("Convertible"))) + require.Equal(t, int64(16), destination.Convertible) + + // 1/0 to bool + require.True(t, tryAssign(reflect.ValueOf(intBool1), testValue.FieldByName("IntBool1"))) + require.Equal(t, true, destination.IntBool1) + require.True(t, tryAssign(reflect.ValueOf(intBool0), testValue.FieldByName("IntBool0"))) + require.Equal(t, false, destination.IntBool0) + + require.False(t, tryAssign(reflect.ValueOf(intBool2), testValue.FieldByName("IntBool2"))) + require.Equal(t, false, destination.IntBool2) + + // string to float + require.True(t, tryAssign(reflect.ValueOf(floatStr), testValue.FieldByName("FloatStr"))) + require.Equal(t, 1.11, destination.FloatStr) + require.False(t, tryAssign(reflect.ValueOf(floatErr), testValue.FieldByName("FloatErr"))) + require.Equal(t, 0.00, destination.FloatErr) + + // string to string + require.True(t, tryAssign(reflect.ValueOf(str), testValue.FieldByName("Str"))) + require.Equal(t, str, destination.Str) +} diff --git a/tests/init/init.go b/tests/init/init.go index 356806d..a2f6eb3 100644 --- a/tests/init/init.go +++ b/tests/init/init.go @@ -46,6 +46,7 @@ func initMySQLDB() { mySQLDBs := []string{ "dvds", + "dvds2", "test_sample", } @@ -89,6 +90,7 @@ func initPostgresDB() { "dvds", "test_sample", "chinook", + "chinook2", "northwind", } diff --git a/tests/mysql/alltypes_test.go b/tests/mysql/alltypes_test.go index 5728725..d96c1d3 100644 --- a/tests/mysql/alltypes_test.go +++ b/tests/mysql/alltypes_test.go @@ -1,7 +1,7 @@ package mysql import ( - "fmt" + "github.com/shopspring/decimal" "github.com/stretchr/testify/require" "strings" "testing" @@ -78,7 +78,7 @@ func TestUUID(t *testing.T) { require.NoError(t, err) require.True(t, dest.StrUUID != nil) require.True(t, dest.UUID.String() != uuid.UUID{}.String()) - require.True(t, dest.StrUUID.String() != uuid.UUID{}.String()) + require.Equal(t, dest.StrUUID.String(), "dc8daae3-b83b-11e9-8eb4-98ded00c39c6") require.Equal(t, dest.StrUUID.String(), dest.BinUUID.String()) requireLogged(t, query) } @@ -89,14 +89,17 @@ func TestExpressionOperators(t *testing.T) { AllTypes.DatePtr.IS_NOT_NULL().AS("result.is_not_null"), AllTypes.SmallIntPtr.IN(Int(11), Int(22)).AS("result.in"), AllTypes.SmallIntPtr.IN(AllTypes.SELECT(AllTypes.Integer)).AS("result.in_select"), + + Raw("CURRENT_USER()").AS("result.raw"), + Raw(":first + COALESCE(all_types.small_int_ptr, 0) + :second", RawArgs{":first": 78, ":second": 56}). + AS("result.raw_arg"), + Raw("#1 + all_types.integer + #2 + #1 + #3 + #4", RawArgs{"#1": 11, "#2": 22, "#3": 33, "#4": 44}). + AS("result.raw_arg2"), + AllTypes.SmallIntPtr.NOT_IN(Int(11), Int(22), NULL).AS("result.not_in"), AllTypes.SmallIntPtr.NOT_IN(AllTypes.SELECT(AllTypes.Integer)).AS("result.not_in_select"), - - Raw("DATABASE()"), ).LIMIT(2) - //fmt.Println(query.Sql()) - testutils.AssertStatementSql(t, query, strings.Replace(` SELECT all_types.'integer' IS NULL AS "result.is_null", all_types.date_ptr IS NOT NULL AS "result.is_not_null", @@ -105,46 +108,34 @@ SELECT all_types.'integer' IS NULL AS "result.is_null", SELECT all_types.'integer' AS "all_types.integer" FROM test_sample.all_types ))) AS "result.in_select", + (CURRENT_USER()) AS "result.raw", + (? + COALESCE(all_types.small_int_ptr, 0) + ?) AS "result.raw_arg", + (? + all_types.integer + ? + ? + ? + ?) AS "result.raw_arg2", (all_types.small_int_ptr NOT IN (?, ?, NULL)) AS "result.not_in", (all_types.small_int_ptr NOT IN (( SELECT all_types.'integer' AS "all_types.integer" FROM test_sample.all_types - ))) AS "result.not_in_select", - DATABASE() + ))) AS "result.not_in_select" FROM test_sample.all_types LIMIT ?; -`, "'", "`", -1), int64(11), int64(22), int64(11), int64(22), int64(2)) +`, "'", "`", -1), int64(11), int64(22), 78, 56, 11, 22, 11, 33, 44, int64(11), int64(22), int64(2)) var dest []struct { common.ExpressionTestResult `alias:"result.*"` } err := query.Query(db, &dest) - require.NoError(t, err) - //testutils.PrintJson(dest) - - testutils.AssertJSON(t, dest, ` -[ - { - "IsNull": false, - "IsNotNull": true, - "In": false, - "InSelect": false, - "NotIn": null, - "NotInSelect": true - }, - { - "IsNull": false, - "IsNotNull": false, - "In": null, - "InSelect": null, - "NotIn": null, - "NotInSelect": null - } -] -`) + require.Equal(t, *dest[0].IsNull, false) + require.Equal(t, *dest[0].IsNotNull, true) + require.Equal(t, *dest[0].In, false) + require.Equal(t, *dest[0].InSelect, false) + require.True(t, strings.Contains(*dest[0].Raw, "jet")) + require.Equal(t, *dest[0].RawArg, int32(148)) + require.Equal(t, *dest[0].RawArg2, int32(-1479)) + require.Nil(t, dest[0].NotIn) + require.Equal(t, *dest[0].NotInSelect, true) } func TestBoolOperators(t *testing.T) { @@ -974,7 +965,7 @@ func TestAllTypesInsert(t *testing.T) { stmt := AllTypes.INSERT(AllTypes.AllColumns). MODEL(toInsert) - fmt.Println(stmt.DebugSql()) + //fmt.Println(stmt.DebugSql()) testutils.AssertExec(t, stmt, tx, 1) @@ -1028,7 +1019,7 @@ func TestAllTypesInsertOnDuplicateKeyUpdate(t *testing.T) { AllTypes.Date.SET(DateT(time.Now())), ) - fmt.Println(stmt.DebugSql()) + //fmt.Println(stmt.DebugSql()) _, err = stmt.Exec(tx) require.NoError(t, err) @@ -1257,7 +1248,7 @@ FROM test_sample.user; err := stmt.Query(db, &dest) require.NoError(t, err) - testutils.PrintJson(dest) + //testutils.PrintJson(dest) testutils.AssertJSON(t, dest, ` [ @@ -1279,3 +1270,99 @@ FROM test_sample.user; ] `) } + +func TestExactDecimals(t *testing.T) { + + type floats struct { + model.Floats + Numeric decimal.Decimal + NumericPtr decimal.Decimal + Decimal decimal.Decimal + DecimalPtr decimal.Decimal + } + + t.Run("should query decimal", func(t *testing.T) { + query := SELECT( + Floats.AllColumns, + ).FROM( + Floats, + ).WHERE(Floats.Decimal.EQ(Decimal("1.11111111111111111111"))) + + var result floats + + err := query.Query(db, &result) + require.NoError(t, err) + + require.Equal(t, "1.11111111111111111111", result.Decimal.String()) + require.Equal(t, "0", result.DecimalPtr.String()) // NULL + require.Equal(t, "2.22222222222222222222", result.Numeric.String()) + require.Equal(t, "0", result.NumericPtr.String()) // NULL + + require.Equal(t, 1.1111111111111112, result.Floats.Decimal) // precision loss + require.Equal(t, (*float64)(nil), result.Floats.DecimalPtr) + require.Equal(t, 2.2222222222222223, result.Floats.Numeric) // precision loss + require.Equal(t, (*float64)(nil), result.Floats.NumericPtr) + + // floating point + require.Equal(t, 3.3333333, result.Floats.Float) // precision loss + require.Equal(t, (*float64)(nil), result.Floats.FloatPtr) + require.Equal(t, 4.444444444444445, result.Floats.Double) // precision loss + require.Equal(t, (*float64)(nil), result.Floats.DoublePtr) + require.Equal(t, 5.555555555555555, result.Floats.Real) // precision loss + require.Equal(t, (*float64)(nil), result.Floats.RealPtr) + }) + + t.Run("should insert decimal", func(t *testing.T) { + + insertQuery := Floats.INSERT( + Floats.AllColumns, + ).MODEL( + floats{ + Floats: model.Floats{ + // overwritten by wrapped(floats) scope + Numeric: 0.1, + NumericPtr: testutils.Float64Ptr(0.1), + Decimal: 0.1, + DecimalPtr: testutils.Float64Ptr(0.1), + + // not overwritten + Float: 0.2, + FloatPtr: testutils.Float64Ptr(0.22), + Double: 0.3, + DoublePtr: testutils.Float64Ptr(0.33), + Real: 0.4, + RealPtr: testutils.Float64Ptr(0.44), + }, + Numeric: decimal.RequireFromString("12.35"), + NumericPtr: decimal.RequireFromString("56.79"), + Decimal: decimal.RequireFromString("91.23"), + DecimalPtr: decimal.RequireFromString("45.67"), + }, + ) + + testutils.AssertDebugStatementSql(t, insertQuery, strings.Replace(` +INSERT INTO test_sample.floats (''decimal'', decimal_ptr, ''numeric'', numeric_ptr, ''float'', float_ptr, ''double'', double_ptr, ''real'', real_ptr) +VALUES ('91.23', '45.67', '12.35', '56.79', 0.2, 0.22, 0.3, 0.33, 0.4, 0.44); +`, "''", "`", -1)) + _, err := insertQuery.Exec(db) + require.NoError(t, err) + + var result floats + + err = SELECT(Floats.AllColumns). + FROM(Floats). + WHERE(Floats.Numeric.EQ(Float(12.35))). + Query(db, &result) + require.NoError(t, err) + + require.Equal(t, "12.35", result.Numeric.String()) + require.Equal(t, "56.79", result.NumericPtr.String()) + require.Equal(t, "91.23", result.Decimal.String()) + require.Equal(t, "45.67", result.DecimalPtr.String()) + + require.Equal(t, 12.35, result.Floats.Numeric) + require.Equal(t, 56.79, *result.Floats.NumericPtr) + require.Equal(t, 91.23, result.Floats.Decimal) + require.Equal(t, 45.67, *result.Floats.DecimalPtr) + }) +} diff --git a/tests/mysql/generator_test.go b/tests/mysql/generator_test.go index 2cc471e..c9dcc1a 100644 --- a/tests/mysql/generator_test.go +++ b/tests/mysql/generator_test.go @@ -35,12 +35,7 @@ func TestGenerator(t *testing.T) { } func TestCmdGenerator(t *testing.T) { - goInstallJet := exec.Command("sh", "-c", "cd $GOPATH/src/ && GO111MODULE=off go get github.com/go-jet/jet/cmd/jet") - goInstallJet.Stderr = os.Stderr - err := goInstallJet.Run() - require.NoError(t, err) - - err = os.RemoveAll(genTestDir3) + err := os.RemoveAll(genTestDir3) require.NoError(t, err) cmd := exec.Command("jet", "-source=MySQL", "-dbname=dvds", "-host=localhost", "-port=3306", @@ -140,7 +135,7 @@ import ( "github.com/go-jet/jet/v2/mysql" ) -var Actor = newActorTable() +var Actor = newActorTable("dvds", "actor", "") type ActorTable struct { mysql.Table @@ -156,13 +151,16 @@ type ActorTable struct { } // AS creates new ActorTable with assigned alias -func (a *ActorTable) AS(alias string) ActorTable { - aliasTable := newActorTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a ActorTable) AS(alias string) ActorTable { + return newActorTable(a.SchemaName(), a.TableName(), alias) } -func newActorTable() ActorTable { +// Schema creates new ActorTable with assigned schema name +func (a ActorTable) FromSchema(schemaName string) ActorTable { + return newActorTable(schemaName, a.TableName(), a.Alias()) +} + +func newActorTable(schemaName, tableName, alias string) ActorTable { var ( ActorIDColumn = mysql.IntegerColumn("actor_id") FirstNameColumn = mysql.StringColumn("first_name") @@ -173,7 +171,7 @@ func newActorTable() ActorTable { ) return ActorTable{ - Table: mysql.NewTable("dvds", "actor", allColumns...), + Table: mysql.NewTable(schemaName, tableName, alias, allColumns...), //Columns ActorID: ActorIDColumn, @@ -223,7 +221,7 @@ import ( "github.com/go-jet/jet/v2/mysql" ) -var ActorInfo = newActorInfoTable() +var ActorInfo = newActorInfoTable("dvds", "actor_info", "") type ActorInfoTable struct { mysql.Table @@ -239,13 +237,16 @@ type ActorInfoTable struct { } // AS creates new ActorInfoTable with assigned alias -func (a *ActorInfoTable) AS(alias string) ActorInfoTable { - aliasTable := newActorInfoTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a ActorInfoTable) AS(alias string) ActorInfoTable { + return newActorInfoTable(a.SchemaName(), a.TableName(), alias) } -func newActorInfoTable() ActorInfoTable { +// Schema creates new ActorInfoTable with assigned schema name +func (a ActorInfoTable) FromSchema(schemaName string) ActorInfoTable { + return newActorInfoTable(schemaName, a.TableName(), a.Alias()) +} + +func newActorInfoTable(schemaName, tableName, alias string) ActorInfoTable { var ( ActorIDColumn = mysql.IntegerColumn("actor_id") FirstNameColumn = mysql.StringColumn("first_name") @@ -256,7 +257,7 @@ func newActorInfoTable() ActorInfoTable { ) return ActorInfoTable{ - Table: mysql.NewTable("dvds", "actor_info", allColumns...), + Table: mysql.NewTable(schemaName, tableName, alias, allColumns...), //Columns ActorID: ActorIDColumn, diff --git a/tests/mysql/main_test.go b/tests/mysql/main_test.go index 5790ef6..e2be933 100644 --- a/tests/mysql/main_test.go +++ b/tests/mysql/main_test.go @@ -3,7 +3,6 @@ package mysql import ( "context" "database/sql" - "flag" jetmysql "github.com/go-jet/jet/v2/mysql" "github.com/go-jet/jet/v2/postgres" "github.com/go-jet/jet/v2/tests/dbconfig" @@ -25,8 +24,7 @@ var source string const MariaDB = "MariaDB" func init() { - flag.StringVar(&source, "source", "", "MySQL or MariaDB") - flag.Parse() + source = os.Getenv("MY_SQL_SOURCE") } func sourceIsMariaDB() bool { @@ -66,3 +64,9 @@ func requireLogged(t *testing.T, statement postgres.Statement) { require.Equal(t, loggedSQLArgs, args) require.Equal(t, loggedDebugSQL, statement.DebugSql()) } + +func skipForMariaDB(t *testing.T) { + if sourceIsMariaDB() { + t.SkipNow() + } +} diff --git a/tests/mysql/raw_statement_test.go b/tests/mysql/raw_statement_test.go new file mode 100644 index 0000000..fd4531f --- /dev/null +++ b/tests/mysql/raw_statement_test.go @@ -0,0 +1,123 @@ +package mysql + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/go-jet/jet/v2/internal/testutils" + "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/model" + + . "github.com/go-jet/jet/v2/mysql" +) + +func TestRawStatementSelect(t *testing.T) { + stmt := RawStatement(` + SELECT actor.first_name AS "actor.first_name" + FROM dvds.actor + WHERE actor.actor_id = 2`) + + testutils.AssertStatementSql(t, stmt, ` + SELECT actor.first_name AS "actor.first_name" + FROM dvds.actor + WHERE actor.actor_id = 2; +`) + testutils.AssertDebugStatementSql(t, stmt, ` + SELECT actor.first_name AS "actor.first_name" + FROM dvds.actor + WHERE actor.actor_id = 2; +`) + var actor model.Actor + err := stmt.Query(db, &actor) + require.NoError(t, err) + require.Equal(t, actor.FirstName, "NICK") +} + +func TestRawStatementSelectWithArguments(t *testing.T) { + stmt := RawStatement(` + SELECT DISTINCT actor.actor_id AS "actor.actor_id", + actor.first_name AS "actor.first_name", + actor.last_name AS "actor.last_name", + actor.last_update AS "actor.last_update" + FROM dvds.actor + WHERE actor.actor_id IN (#actorID1, #actorID2, #actorID3) AND ((#actorID1 / #actorID2) <> (#actorID2 * #actorID3)) + ORDER BY actor.actor_id`, + RawArgs{ + "#actorID1": int64(1), + "#actorID2": int64(2), + "#actorID3": int64(3), + }, + ) + + testutils.AssertStatementSql(t, stmt, ` + SELECT DISTINCT actor.actor_id AS "actor.actor_id", + actor.first_name AS "actor.first_name", + actor.last_name AS "actor.last_name", + actor.last_update AS "actor.last_update" + FROM dvds.actor + WHERE actor.actor_id IN (?, ?, ?) AND ((? / ?) <> (? * ?)) + ORDER BY actor.actor_id; +`, int64(1), int64(2), int64(3), int64(1), int64(2), int64(2), int64(3)) + + testutils.AssertDebugStatementSql(t, stmt, ` + SELECT DISTINCT actor.actor_id AS "actor.actor_id", + actor.first_name AS "actor.first_name", + actor.last_name AS "actor.last_name", + actor.last_update AS "actor.last_update" + FROM dvds.actor + WHERE actor.actor_id IN (1, 2, 3) AND ((1 / 2) <> (2 * 3)) + ORDER BY actor.actor_id; +`) + + var actor []model.Actor + err := stmt.Query(db, &actor) + require.NoError(t, err) + + testutils.AssertDeepEqual(t, actor[1], model.Actor{ + ActorID: 2, + FirstName: "NICK", + LastName: "WAHLBERG", + LastUpdate: *testutils.TimestampWithoutTimeZone("2006-02-15 04:34:33", 2), + }) +} + +func TestRawStatementRows(t *testing.T) { + stmt := RawStatement(` + SELECT actor.actor_id AS "actor.actor_id", + actor.first_name AS "actor.first_name", + actor.last_name AS "actor.last_name", + actor.last_update AS "actor.last_update" + FROM dvds.actor + ORDER BY actor.actor_id`) + + rows, err := stmt.Rows(context.Background(), db) + require.NoError(t, err) + + for rows.Next() { + var actor model.Actor + err := rows.Scan(&actor) + require.NoError(t, err) + + require.NotEqual(t, actor.ActorID, int16(0)) + require.NotEqual(t, actor.FirstName, "") + require.NotEqual(t, actor.LastName, "") + require.NotEqual(t, actor.LastUpdate, time.Time{}) + + if actor.ActorID == 54 { + require.Equal(t, actor.ActorID, uint16(54)) + require.Equal(t, actor.FirstName, "PENELOPE") + require.Equal(t, actor.LastName, "PINKETT") + require.Equal(t, actor.LastUpdate.Format(time.RFC3339), "2006-02-15T04:34:33Z") + } + } + + err = rows.Close() + require.NoError(t, err) + + err = rows.Err() + require.NoError(t, err) + + requireLogged(t, stmt) +} diff --git a/tests/mysql/select_test.go b/tests/mysql/select_test.go index dc6af0d..5a88acf 100644 --- a/tests/mysql/select_test.go +++ b/tests/mysql/select_test.go @@ -1,15 +1,19 @@ package mysql import ( + "context" + "strings" + "testing" + "time" + "github.com/go-jet/jet/v2/internal/testutils" . "github.com/go-jet/jet/v2/mysql" "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/enum" "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/model" . "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table" "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/view" - "github.com/stretchr/testify/require" - "testing" + "github.com/stretchr/testify/require" ) func TestSelect_ScanToStruct(t *testing.T) { @@ -744,3 +748,227 @@ LIMIT 3; require.Equal(t, len(dest), 3) } + +func Test_SchemaRename(t *testing.T) { + Film := Film.FromSchema("dvds2") + Language := Language.FromSchema("dvds2") + + stmt := SELECT( + Film.FilmID, + Film.Title, + Language.LanguageID, + Language.Name, + ).FROM( + Language. + INNER_JOIN(Film, Film.LanguageID.EQ(Language.LanguageID)), + ).WHERE( + Language.LanguageID.EQ(Int(1)), + ).ORDER_BY( + Language.LanguageID, Film.FilmID, + ).LIMIT(5) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT film.film_id AS "film.film_id", + film.title AS "film.title", + language.language_id AS "language.language_id", + language.name AS "language.name" +FROM dvds2.language + INNER JOIN dvds2.film ON (film.language_id = language.language_id) +WHERE language.language_id = 1 +ORDER BY language.language_id, film.film_id +LIMIT 5; +`) + + dest := struct { + model.Language + Films []model.Film + }{} + + err := stmt.Query(db, &dest) + require.NoError(t, err) + require.Len(t, dest.Films, 5) + require.Equal(t, dest.Films[0].Title, "ACADEMY DINOSAUR") + require.Equal(t, dest.Films[1].Title, "ACE GOLDFINGER") + require.Equal(t, dest.Films[4].Title, "AFRICAN EGG") +} + +func TestLateral(t *testing.T) { + skipForMariaDB(t) // MariaDB does not implement LATERAL + + languages := LATERAL( + SELECT( + Language.AllColumns, + ).FROM( + Language, + ).WHERE( + Language.Name.NOT_IN(String("spanish")). + AND(Film.LanguageID.EQ(Language.LanguageID)), + ), + ).AS("films") + + stmt := SELECT( + Film.FilmID, + Film.Title, + languages.AllColumns(), + ).FROM( + Film.CROSS_JOIN(languages), + ).WHERE( + Film.FilmID.EQ(Int(1)), + ).ORDER_BY( + Film.FilmID, + ).LIMIT(1) + + testutils.AssertDebugStatementSql(t, stmt, strings.Replace(` +SELECT film.film_id AS "film.film_id", + film.title AS "film.title", + films.''language.language_id'' AS "language.language_id", + films.''language.name'' AS "language.name", + films.''language.last_update'' AS "language.last_update" +FROM dvds.film + CROSS JOIN LATERAL ( + SELECT language.language_id AS "language.language_id", + language.name AS "language.name", + language.last_update AS "language.last_update" + FROM dvds.language + WHERE (language.name NOT IN ('spanish')) AND (film.language_id = language.language_id) + ) AS films +WHERE film.film_id = 1 +ORDER BY film.film_id +LIMIT 1; +`, "''", "`", -1)) + + type FilmLanguage struct { + model.Film + model.Language + } + + var dest []FilmLanguage + + err := stmt.Query(db, &dest) + require.NoError(t, err) + require.Equal(t, dest[0].Film.Title, "ACADEMY DINOSAUR") + require.Equal(t, dest[0].Language.Name, "English") + + t.Run("implicit cross join", func(t *testing.T) { + stmt2 := SELECT( + Film.FilmID, + Film.Title, + languages.AllColumns(), + ).FROM( + Film, + languages, + ).WHERE( + Film.FilmID.EQ(Int(1)), + ).ORDER_BY( + Film.FilmID, + ).LIMIT(1) + + testutils.AssertDebugStatementSql(t, stmt2, strings.Replace(` +SELECT film.film_id AS "film.film_id", + film.title AS "film.title", + films.''language.language_id'' AS "language.language_id", + films.''language.name'' AS "language.name", + films.''language.last_update'' AS "language.last_update" +FROM dvds.film, + LATERAL ( + SELECT language.language_id AS "language.language_id", + language.name AS "language.name", + language.last_update AS "language.last_update" + FROM dvds.language + WHERE (language.name NOT IN ('spanish')) AND (film.language_id = language.language_id) + ) AS films +WHERE film.film_id = 1 +ORDER BY film.film_id +LIMIT 1; +`, "''", "`", -1)) + + var dest2 []FilmLanguage + + err2 := stmt2.Query(db, &dest2) + require.NoError(t, err2) + require.Equal(t, dest, dest2) + }) +} + +func TestRowsScan(t *testing.T) { + + stmt := SELECT( + Inventory.AllColumns, + ).FROM( + Inventory, + ).ORDER_BY( + Inventory.InventoryID.ASC(), + ) + + rows, err := stmt.Rows(context.Background(), db) + require.NoError(t, err) + + for rows.Next() { + var inventory model.Inventory + err = rows.Scan(&inventory) + require.NoError(t, err) + + require.NotEqual(t, inventory.InventoryID, uint32(0)) + require.NotEqual(t, inventory.FilmID, uint16(0)) + require.NotEqual(t, inventory.StoreID, uint16(0)) + require.NotEqual(t, inventory.LastUpdate, time.Time{}) + + if inventory.InventoryID == 2103 { + require.Equal(t, inventory.FilmID, uint16(456)) + require.Equal(t, inventory.StoreID, uint8(2)) + require.Equal(t, inventory.LastUpdate.Format(time.RFC3339), "2006-02-15T05:09:17Z") + } + } + + err = rows.Close() + require.NoError(t, err) + err = rows.Err() + require.NoError(t, err) + + requireLogged(t, stmt) +} + +func TestScanNumericToNumber(t *testing.T) { + type Number struct { + Int8 int8 + UInt8 uint8 + Int16 int16 + UInt16 uint16 + Int32 int32 + UInt32 uint32 + Int64 int64 + UInt64 uint64 + Float32 float32 + Float64 float64 + } + + numeric := CAST(Decimal("1234567890.111")).AS_DECIMAL() + + stmt := SELECT( + numeric.AS("number.int8"), + numeric.AS("number.uint8"), + numeric.AS("number.int16"), + numeric.AS("number.uint16"), + numeric.AS("number.int32"), + numeric.AS("number.uint32"), + numeric.AS("number.int64"), + numeric.AS("number.uint64"), + numeric.AS("number.float32"), + numeric.AS("number.float64"), + ) + + var number Number + err := stmt.Query(db, &number) + require.NoError(t, err) + + require.Equal(t, number.Int8, int8(-46)) // overflow + require.Equal(t, number.UInt8, uint8(210)) // overflow + require.Equal(t, number.Int16, int16(722)) // overflow + require.Equal(t, number.UInt16, uint16(722)) // overflow + require.Equal(t, number.Int32, int32(1234567890)) + require.Equal(t, number.UInt32, uint32(1234567890)) + require.Equal(t, number.Int64, int64(1234567890)) + require.Equal(t, number.UInt64, uint64(1234567890)) + require.Equal(t, number.Float32, float32(1.234568e+09)) + require.Equal(t, number.Float64, float64(1.23456789e+09)) +} diff --git a/tests/mysql/update_test.go b/tests/mysql/update_test.go index bef22f9..3b6aa75 100644 --- a/tests/mysql/update_test.go +++ b/tests/mysql/update_test.go @@ -2,7 +2,6 @@ package mysql import ( "context" - "fmt" "github.com/go-jet/jet/v2/internal/testutils" . "github.com/go-jet/jet/v2/mysql" "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table" @@ -193,7 +192,7 @@ SET url = 'http://www.duckduckgo.com', description = NULL WHERE link.id = 201; ` - fmt.Println(stmt.DebugSql()) + //fmt.Println(stmt.DebugSql()) testutils.AssertDebugStatementSql(t, stmt, expectedSQL, "http://www.duckduckgo.com", "DuckDuckGo", nil, int64(201)) testutils.AssertExec(t, stmt, db) diff --git a/tests/postgres/alltypes_test.go b/tests/postgres/alltypes_test.go index 4379e67..29986da 100644 --- a/tests/postgres/alltypes_test.go +++ b/tests/postgres/alltypes_test.go @@ -1,20 +1,24 @@ package postgres import ( - "github.com/stretchr/testify/require" "testing" "time" + "github.com/stretchr/testify/require" + + "github.com/google/uuid" + "github.com/go-jet/jet/v2/internal/testutils" . "github.com/go-jet/jet/v2/postgres" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/model" . "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/table" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/view" "github.com/go-jet/jet/v2/tests/testdata/results/common" - "github.com/google/uuid" ) func TestAllTypesSelect(t *testing.T) { + skipForPgxDriver(t) // pgx driver returns time with time zone as string + dest := []model.AllTypes{} err := AllTypes.SELECT(AllTypes.AllColumns).Query(db, &dest) @@ -25,6 +29,8 @@ func TestAllTypesSelect(t *testing.T) { } func TestAllTypesViewSelect(t *testing.T) { + skipForPgxDriver(t) // pgx driver returns time with time zone as string + type AllTypesView model.AllTypes dest := []AllTypesView{} @@ -37,6 +43,8 @@ func TestAllTypesViewSelect(t *testing.T) { } func TestAllTypesInsertModel(t *testing.T) { + skipForPgxDriver(t) // pgx driver does not handle well time with time zone + query := AllTypes.INSERT(AllTypes.AllColumns). MODEL(allTypesRow0). MODEL(&allTypesRow1). @@ -52,6 +60,8 @@ func TestAllTypesInsertModel(t *testing.T) { } func TestAllTypesInsertQuery(t *testing.T) { + skipForPgxDriver(t) // pgx driver does not handle well time with time zone + query := AllTypes.INSERT(AllTypes.AllColumns). QUERY( AllTypes. @@ -70,6 +80,7 @@ func TestAllTypesInsertQuery(t *testing.T) { } func TestAllTypesFromSubQuery(t *testing.T) { + skipForPgxDriver(t) subQuery := SELECT(AllTypes.AllColumns). FROM(AllTypes). @@ -221,12 +232,16 @@ func TestExpressionOperators(t *testing.T) { AllTypes.DatePtr.IS_NOT_NULL().AS("result.is_not_null"), AllTypes.SmallIntPtr.IN(Int(11), Int(22)).AS("result.in"), AllTypes.SmallIntPtr.IN(AllTypes.SELECT(AllTypes.Integer)).AS("result.in_select"), + + Raw("CURRENT_USER").AS("result.raw"), + Raw("#1 + COALESCE(all_types.small_int_ptr, 0) + #2", RawArgs{"#1": 78, "#2": 56}).AS("result.raw_arg"), + Raw("#1 + all_types.integer + #2 + #1 + #3 + #4", + RawArgs{"#1": 11, "#2": 22, "#3": 33, "#4": 44}).AS("result.raw_arg2"), + AllTypes.SmallIntPtr.NOT_IN(Int(11), Int(22), NULL).AS("result.not_in"), AllTypes.SmallIntPtr.NOT_IN(AllTypes.SELECT(AllTypes.Integer)).AS("result.not_in_select"), ).LIMIT(2) - //fmt.Println(query.Sql()) - testutils.AssertStatementSql(t, query, ` SELECT all_types.integer IS NULL AS "result.is_null", all_types.date_ptr IS NOT NULL AS "result.is_not_null", @@ -235,14 +250,17 @@ SELECT all_types.integer IS NULL AS "result.is_null", SELECT all_types.integer AS "all_types.integer" FROM test_sample.all_types ))) AS "result.in_select", - (all_types.small_int_ptr NOT IN ($3, $4, NULL)) AS "result.not_in", + (CURRENT_USER) AS "result.raw", + ($3 + COALESCE(all_types.small_int_ptr, 0) + $4) AS "result.raw_arg", + ($5 + all_types.integer + $6 + $5 + $7 + $8) AS "result.raw_arg2", + (all_types.small_int_ptr NOT IN ($9, $10, NULL)) AS "result.not_in", (all_types.small_int_ptr NOT IN (( SELECT all_types.integer AS "all_types.integer" FROM test_sample.all_types ))) AS "result.not_in_select" FROM test_sample.all_types -LIMIT $5; -`, int64(11), int64(22), int64(11), int64(22), int64(2)) +LIMIT $11; +`, int64(11), int64(22), 78, 56, 11, 22, 33, 44, int64(11), int64(22), int64(2)) var dest []struct { common.ExpressionTestResult `alias:"result.*"` @@ -261,6 +279,9 @@ LIMIT $5; "IsNotNull": true, "In": false, "InSelect": false, + "Raw": "jet", + "RawArg": 148, + "RawArg2": 421, "NotIn": null, "NotInSelect": true }, @@ -269,6 +290,9 @@ LIMIT $5; "IsNotNull": false, "In": null, "InSelect": null, + "Raw": "jet", + "RawArg": 134, + "RawArg2": 421, "NotIn": null, "NotInSelect": null } @@ -278,6 +302,8 @@ LIMIT $5; func TestExpressionCast(t *testing.T) { + skipForPgxDriver(t) // for some reason, pgx driver, 150:char(12) returns as int value + query := AllTypes.SELECT( CAST(Int(150)).AS_CHAR(12).AS("char12"), CAST(String("TRUE")).AS_BOOL(), @@ -323,6 +349,8 @@ func TestExpressionCast(t *testing.T) { } func TestStringOperators(t *testing.T) { + skipForPgxDriver(t) // pgx driver returns text column as int value + query := AllTypes.SELECT( AllTypes.Text.EQ(AllTypes.Char), AllTypes.Text.EQ(String("Text")), @@ -838,6 +866,7 @@ func TestInterval(t *testing.T) { } func TestSubQueryColumnReference(t *testing.T) { + skipForPgxDriver(t) // pgx driver returns time with time zone as string value type expected struct { sql string @@ -1015,6 +1044,7 @@ FROM` } func TestTimeLiterals(t *testing.T) { + skipForPgxDriver(t) // pgx driver returns time with time zone as string loc, err := time.LoadLocation("Europe/Berlin") require.NoError(t, err) diff --git a/tests/postgres/chinook_db_test.go b/tests/postgres/chinook_db_test.go index 50f5e60..553f920 100644 --- a/tests/postgres/chinook_db_test.go +++ b/tests/postgres/chinook_db_test.go @@ -2,7 +2,6 @@ package postgres import ( "context" - "fmt" "github.com/go-jet/jet/v2/internal/testutils" . "github.com/go-jet/jet/v2/postgres" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/chinook/model" @@ -17,7 +16,7 @@ func TestSelect(t *testing.T) { SELECT(Album.AllColumns). ORDER_BY(Album.AlbumId.ASC()) - fmt.Println(stmt.DebugSql()) + //fmt.Println(stmt.DebugSql()) testutils.AssertDebugStatementSql(t, stmt, ` SELECT "Album"."AlbumId" AS "Album.AlbumId", @@ -330,8 +329,71 @@ ORDER BY "first10Artist"."Artist.ArtistId"; err := stmt.Query(db, &dest) require.NoError(t, err) +} - //spew.Dump(dest) +func Test_SchemaRename(t *testing.T) { + + Artist2 := Artist.FromSchema("chinook2") + Album2 := Album.FromSchema("chinook2") + + first10Artist := Artist2. + SELECT(Artist2.AllColumns). + ORDER_BY(Artist2.ArtistId). + LIMIT(10). + AsTable("first10Artist") + + artistID := Artist2.ArtistId.From(first10Artist) + + first10Albums := Album2. + SELECT(Album2.AllColumns). + ORDER_BY(Album2.AlbumId). + LIMIT(10). + AsTable("first10Albums") + + albumArtistID := Album2.ArtistId.From(first10Albums) + + stmt := SELECT(first10Artist.AllColumns(), first10Albums.AllColumns()). + FROM(first10Artist. + INNER_JOIN(first10Albums, artistID.EQ(albumArtistID))). + ORDER_BY(artistID) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT "first10Artist"."Artist.ArtistId" AS "Artist.ArtistId", + "first10Artist"."Artist.Name" AS "Artist.Name", + "first10Albums"."Album.AlbumId" AS "Album.AlbumId", + "first10Albums"."Album.Title" AS "Album.Title", + "first10Albums"."Album.ArtistId" AS "Album.ArtistId" +FROM ( + SELECT "Artist"."ArtistId" AS "Artist.ArtistId", + "Artist"."Name" AS "Artist.Name" + FROM chinook2."Artist" + ORDER BY "Artist"."ArtistId" + LIMIT 10 + ) AS "first10Artist" + INNER JOIN ( + SELECT "Album"."AlbumId" AS "Album.AlbumId", + "Album"."Title" AS "Album.Title", + "Album"."ArtistId" AS "Album.ArtistId" + FROM chinook2."Album" + ORDER BY "Album"."AlbumId" + LIMIT 10 + ) AS "first10Albums" ON ("first10Artist"."Artist.ArtistId" = "first10Albums"."Album.ArtistId") +ORDER BY "first10Artist"."Artist.ArtistId"; +`) + + var dest []struct { + model.Artist + + Album []model.Album + } + + err := stmt.Query(db, &dest) + require.NoError(t, err) + + require.Len(t, dest, 2) + require.Equal(t, *dest[0].Artist.Name, "Apocalyptica") + require.Len(t, dest[0].Album, 1) + require.Equal(t, dest[0].Album[0].Title, "Plays Metallica By Four Cellos") } var album1 = model.Album{ diff --git a/tests/postgres/generator_test.go b/tests/postgres/generator_test.go index 50e7223..0571157 100644 --- a/tests/postgres/generator_test.go +++ b/tests/postgres/generator_test.go @@ -46,12 +46,7 @@ func TestGeneratedModel(t *testing.T) { const genTestDir2 = "./.gentestdata2" func TestCmdGenerator(t *testing.T) { - goInstallJet := exec.Command("sh", "-c", "cd $GOPATH/src/ && GO111MODULE=off go get github.com/go-jet/jet/cmd/jet") - goInstallJet.Stderr = os.Stderr - err := goInstallJet.Run() - require.NoError(t, err) - - err = os.RemoveAll(genTestDir2) + err := os.RemoveAll(genTestDir2) require.NoError(t, err) cmd := exec.Command("jet", "-source=PostgreSQL", "-dbname=jetdb", "-host=localhost", "-port=5432", @@ -173,7 +168,7 @@ import ( "github.com/go-jet/jet/v2/postgres" ) -var Actor = newActorTable() +var Actor = newActorTable("dvds", "actor", "") type actorTable struct { postgres.Table @@ -195,20 +190,23 @@ type ActorTable struct { } // AS creates new ActorTable with assigned alias -func (a *ActorTable) AS(alias string) *ActorTable { - aliasTable := newActorTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a ActorTable) AS(alias string) *ActorTable { + return newActorTable(a.SchemaName(), a.TableName(), alias) } -func newActorTable() *ActorTable { +// Schema creates new ActorTable with assigned schema name +func (a ActorTable) FromSchema(schemaName string) *ActorTable { + return newActorTable(schemaName, a.TableName(), a.Alias()) +} + +func newActorTable(schemaName, tableName, alias string) *ActorTable { return &ActorTable{ - actorTable: newActorTableImpl("dvds", "actor"), - EXCLUDED: newActorTableImpl("", "excluded"), + actorTable: newActorTableImpl(schemaName, tableName, alias), + EXCLUDED: newActorTableImpl("", "excluded", ""), } } -func newActorTableImpl(schemaName, tableName string) actorTable { +func newActorTableImpl(schemaName, tableName, alias string) actorTable { var ( ActorIDColumn = postgres.IntegerColumn("actor_id") FirstNameColumn = postgres.StringColumn("first_name") @@ -219,7 +217,7 @@ func newActorTableImpl(schemaName, tableName string) actorTable { ) return actorTable{ - Table: postgres.NewTable(schemaName, tableName, allColumns...), + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns ActorID: ActorIDColumn, @@ -269,7 +267,7 @@ import ( "github.com/go-jet/jet/v2/postgres" ) -var ActorInfo = newActorInfoTable() +var ActorInfo = newActorInfoTable("dvds", "actor_info", "") type actorInfoTable struct { postgres.Table @@ -291,20 +289,23 @@ type ActorInfoTable struct { } // AS creates new ActorInfoTable with assigned alias -func (a *ActorInfoTable) AS(alias string) *ActorInfoTable { - aliasTable := newActorInfoTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a ActorInfoTable) AS(alias string) *ActorInfoTable { + return newActorInfoTable(a.SchemaName(), a.TableName(), alias) } -func newActorInfoTable() *ActorInfoTable { +// Schema creates new ActorInfoTable with assigned schema name +func (a ActorInfoTable) FromSchema(schemaName string) *ActorInfoTable { + return newActorInfoTable(schemaName, a.TableName(), a.Alias()) +} + +func newActorInfoTable(schemaName, tableName, alias string) *ActorInfoTable { return &ActorInfoTable{ - actorInfoTable: newActorInfoTableImpl("dvds", "actor_info"), - EXCLUDED: newActorInfoTableImpl("", "excluded"), + actorInfoTable: newActorInfoTableImpl(schemaName, tableName, alias), + EXCLUDED: newActorInfoTableImpl("", "excluded", ""), } } -func newActorInfoTableImpl(schemaName, tableName string) actorInfoTable { +func newActorInfoTableImpl(schemaName, tableName, alias string) actorInfoTable { var ( ActorIDColumn = postgres.IntegerColumn("actor_id") FirstNameColumn = postgres.StringColumn("first_name") @@ -315,7 +316,7 @@ func newActorInfoTableImpl(schemaName, tableName string) actorInfoTable { ) return actorInfoTable{ - Table: postgres.NewTable(schemaName, tableName, allColumns...), + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns ActorID: ActorIDColumn, @@ -345,7 +346,7 @@ func TestGeneratedAllTypesSQLBuilderFiles(t *testing.T) { require.NoError(t, err) testutils.AssertFileNamesEqual(t, modelFiles, "all_types.go", "all_types_view.go", "employee.go", "link.go", - "mood.go", "person.go", "person_phone.go", "weird_names_table.go", "level.go", "user.go") + "mood.go", "person.go", "person_phone.go", "weird_names_table.go", "level.go", "user.go", "floats.go") testutils.AssertFileContent(t, modelDir+"all_types.go", allTypesModelContent) @@ -353,7 +354,7 @@ func TestGeneratedAllTypesSQLBuilderFiles(t *testing.T) { require.NoError(t, err) testutils.AssertFileNamesEqual(t, tableFiles, "all_types.go", "employee.go", "link.go", - "person.go", "person_phone.go", "weird_names_table.go", "user.go") + "person.go", "person_phone.go", "weird_names_table.go", "user.go", "floats.go") testutils.AssertFileContent(t, tableDir+"all_types.go", allTypesTableContent) } @@ -502,7 +503,7 @@ import ( "github.com/go-jet/jet/v2/postgres" ) -var AllTypes = newAllTypesTable() +var AllTypes = newAllTypesTable("test_sample", "all_types", "") type allTypesTable struct { postgres.Table @@ -581,20 +582,23 @@ type AllTypesTable struct { } // AS creates new AllTypesTable with assigned alias -func (a *AllTypesTable) AS(alias string) *AllTypesTable { - aliasTable := newAllTypesTable() - aliasTable.Table.AS(alias) - return aliasTable +func (a AllTypesTable) AS(alias string) *AllTypesTable { + return newAllTypesTable(a.SchemaName(), a.TableName(), alias) } -func newAllTypesTable() *AllTypesTable { +// Schema creates new AllTypesTable with assigned schema name +func (a AllTypesTable) FromSchema(schemaName string) *AllTypesTable { + return newAllTypesTable(schemaName, a.TableName(), a.Alias()) +} + +func newAllTypesTable(schemaName, tableName, alias string) *AllTypesTable { return &AllTypesTable{ - allTypesTable: newAllTypesTableImpl("test_sample", "all_types"), - EXCLUDED: newAllTypesTableImpl("", "excluded"), + allTypesTable: newAllTypesTableImpl(schemaName, tableName, alias), + EXCLUDED: newAllTypesTableImpl("", "excluded", ""), } } -func newAllTypesTableImpl(schemaName, tableName string) allTypesTable { +func newAllTypesTableImpl(schemaName, tableName, alias string) allTypesTable { var ( SmallIntPtrColumn = postgres.IntegerColumn("small_int_ptr") SmallIntColumn = postgres.IntegerColumn("small_int") @@ -662,7 +666,7 @@ func newAllTypesTableImpl(schemaName, tableName string) allTypesTable { ) return allTypesTable{ - Table: postgres.NewTable(schemaName, tableName, allColumns...), + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns SmallIntPtr: SmallIntPtrColumn, diff --git a/tests/postgres/main_test.go b/tests/postgres/main_test.go index 56d0363..ef9337c 100644 --- a/tests/postgres/main_test.go +++ b/tests/postgres/main_test.go @@ -3,17 +3,23 @@ package postgres import ( "context" "database/sql" - "github.com/go-jet/jet/v2/postgres" - "github.com/go-jet/jet/v2/tests/dbconfig" - _ "github.com/lib/pq" - "github.com/pkg/profile" - "github.com/stretchr/testify/require" + "fmt" "math/rand" "os" "os/exec" "strings" "testing" "time" + + "github.com/jackc/pgx/v4/stdlib" + + "github.com/go-jet/jet/v2/postgres" + "github.com/go-jet/jet/v2/tests/dbconfig" + _ "github.com/lib/pq" + "github.com/pkg/profile" + "github.com/stretchr/testify/require" + + _ "github.com/jackc/pgx/v4/stdlib" ) var db *sql.DB @@ -25,16 +31,23 @@ func TestMain(m *testing.M) { setTestRoot() - var err error - db, err = sql.Open("postgres", dbconfig.PostgresConnectString) - if err != nil { - panic("Failed to connect to test db") + for _, driverName := range []string{"postgres", "pgx"} { + func() { + var err error + db, err = sql.Open(driverName, dbconfig.PostgresConnectString) + if err != nil { + fmt.Println(err.Error()) + panic("Failed to connect to test db") + } + defer db.Close() + + ret := m.Run() + + if ret != 0 { + os.Exit(ret) + } + }() } - defer db.Close() - - ret := m.Run() - - os.Exit(ret) } func setTestRoot() { @@ -64,3 +77,10 @@ func requireLogged(t *testing.T, statement postgres.Statement) { require.Equal(t, loggedSQLArgs, args) require.Equal(t, loggedDebugSQL, statement.DebugSql()) } + +func skipForPgxDriver(t *testing.T) { + switch db.Driver().(type) { + case *stdlib.Driver: + t.SkipNow() + } +} diff --git a/tests/postgres/raw_statements_test.go b/tests/postgres/raw_statements_test.go new file mode 100644 index 0000000..a193258 --- /dev/null +++ b/tests/postgres/raw_statements_test.go @@ -0,0 +1,179 @@ +package postgres + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/go-jet/jet/v2/internal/testutils" + "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model" + model2 "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/model" + + . "github.com/go-jet/jet/v2/postgres" +) + +func TestRawStatementSelect(t *testing.T) { + stmt := RawStatement(` + SELECT actor.first_name AS "actor.first_name" + FROM dvds.actor + WHERE actor.actor_id = 2`) + + testutils.AssertStatementSql(t, stmt, ` + SELECT actor.first_name AS "actor.first_name" + FROM dvds.actor + WHERE actor.actor_id = 2; +`) + testutils.AssertDebugStatementSql(t, stmt, ` + SELECT actor.first_name AS "actor.first_name" + FROM dvds.actor + WHERE actor.actor_id = 2; +`) + var actor model.Actor + err := stmt.Query(db, &actor) + require.NoError(t, err) + require.Equal(t, actor.FirstName, "Nick") +} + +func TestRawStatementSelectWithArguments(t *testing.T) { + stmt := RawStatement(` + SELECT DISTINCT actor.actor_id AS "actor.actor_id", + actor.first_name AS "actor.first_name", + actor.last_name AS "actor.last_name", + actor.last_update AS "actor.last_update" + FROM dvds.actor + WHERE actor.actor_id IN (#actorID1, #actorID2, #actorID3) AND ((#actorID1 / #actorID2) <> (#actorID2 * #actorID3)) + ORDER BY actor.actor_id`, + RawArgs{ + "#actorID1": int64(1), + "#actorID2": int64(2), + "#actorID3": int64(3), + }, + ) + + testutils.AssertStatementSql(t, stmt, ` + SELECT DISTINCT actor.actor_id AS "actor.actor_id", + actor.first_name AS "actor.first_name", + actor.last_name AS "actor.last_name", + actor.last_update AS "actor.last_update" + FROM dvds.actor + WHERE actor.actor_id IN ($1, $2, $3) AND (($1 / $2) <> ($2 * $3)) + ORDER BY actor.actor_id; +`, int64(1), int64(2), int64(3)) + + testutils.AssertDebugStatementSql(t, stmt, ` + SELECT DISTINCT actor.actor_id AS "actor.actor_id", + actor.first_name AS "actor.first_name", + actor.last_name AS "actor.last_name", + actor.last_update AS "actor.last_update" + FROM dvds.actor + WHERE actor.actor_id IN (1, 2, 3) AND ((1 / 2) <> (2 * 3)) + ORDER BY actor.actor_id; +`) + + var actor []model.Actor + err := stmt.Query(db, &actor) + require.NoError(t, err) + + testutils.AssertDeepEqual(t, actor[1], model.Actor{ + ActorID: 2, + FirstName: "Nick", + LastName: "Wahlberg", + LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:47:57.62", 2), + }) +} + +func TestRawInsert(t *testing.T) { + cleanUpLinkTable(t) + + stmt := RawStatement(` +INSERT INTO test_sample.link (id, url, name, description) +VALUES (@id1, @url1, @name1, DEFAULT), + (200, @url1, @name1, NULL), + (@id2, @url2, @name2, DEFAULT), + (@id3, @url3, @name3, NULL) +RETURNING link.id AS "link.id", + link.url AS "link.url", + link.name AS "link.name", + link.description AS "link.description"`, + RawArgs{ + "@id1": 100, "@url1": "http://www.postgresqltutorial.com", "@name1": "PostgreSQL Tutorial", + "@id2": 101, "@url2": "http://www.google.com", "@name2": "Google", + "@id3": 102, "@url3": "http://www.yahoo.com", "@name3": "Yahoo", + }) + + testutils.AssertStatementSql(t, stmt, ` +INSERT INTO test_sample.link (id, url, name, description) +VALUES ($1, $2, $3, DEFAULT), + (200, $2, $3, NULL), + ($4, $5, $6, DEFAULT), + ($7, $8, $9, NULL) +RETURNING link.id AS "link.id", + link.url AS "link.url", + link.name AS "link.name", + link.description AS "link.description"; +`, 100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", + 101, "http://www.google.com", "Google", + 102, "http://www.yahoo.com", "Yahoo") + + testutils.AssertDebugStatementSql(t, stmt, ` +INSERT INTO test_sample.link (id, url, name, description) +VALUES (100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT), + (200, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', NULL), + (101, 'http://www.google.com', 'Google', DEFAULT), + (102, 'http://www.yahoo.com', 'Yahoo', NULL) +RETURNING link.id AS "link.id", + link.url AS "link.url", + link.name AS "link.name", + link.description AS "link.description"; +`) + + var links []model2.Link + err := stmt.Query(db, &links) + require.NoError(t, err) + require.Len(t, links, 4) + require.Equal(t, links[0].ID, int32(100)) + require.Equal(t, links[1].URL, "http://www.postgresqltutorial.com") + require.Equal(t, links[2].Name, "Google") + require.Nil(t, links[2].Description) +} + +func TestRawStatementRows(t *testing.T) { + stmt := RawStatement(` + SELECT actor.actor_id AS "actor.actor_id", + actor.first_name AS "actor.first_name", + actor.last_name AS "actor.last_name", + actor.last_update AS "actor.last_update" + FROM dvds.actor + ORDER BY actor.actor_id`) + + rows, err := stmt.Rows(context.Background(), db) + require.NoError(t, err) + + for rows.Next() { + var actor model.Actor + err := rows.Scan(&actor) + require.NoError(t, err) + + require.NotEqual(t, actor.ActorID, int32(0)) + require.NotEqual(t, actor.FirstName, "") + require.NotEqual(t, actor.LastName, "") + require.NotEqual(t, actor.LastUpdate, time.Time{}) + + if actor.ActorID == 54 { + require.Equal(t, actor.ActorID, int32(54)) + require.Equal(t, actor.FirstName, "Penelope") + require.Equal(t, actor.LastName, "Pinkett") + require.Equal(t, actor.LastUpdate.Format(time.RFC3339), "2013-05-26T14:47:57Z") + } + } + + err = rows.Close() + require.NoError(t, err) + + err = rows.Err() + require.NoError(t, err) + + requireLogged(t, stmt) +} diff --git a/tests/postgres/sample_test.go b/tests/postgres/sample_test.go index bcb7361..f1d9999 100644 --- a/tests/postgres/sample_test.go +++ b/tests/postgres/sample_test.go @@ -1,19 +1,26 @@ package postgres import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + "github.com/go-jet/jet/v2/internal/testutils" . "github.com/go-jet/jet/v2/postgres" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/model" . "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/table" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "testing" + + "github.com/shopspring/decimal" ) func TestUUIDType(t *testing.T) { + + id := uuid.MustParse("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") + query := AllTypes. SELECT(AllTypes.UUID, AllTypes.UUIDPtr). - WHERE(AllTypes.UUID.EQ(String("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"))) + WHERE(AllTypes.UUID.EQ(UUID(id))) testutils.AssertDebugStatementSql(t, query, ` SELECT all_types.uuid AS "all_types.uuid", @@ -31,6 +38,110 @@ WHERE all_types.uuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'; requireLogged(t, query) } +func TestExactDecimals(t *testing.T) { + + type floats struct { + model.Floats + Numeric decimal.Decimal + NumericPtr decimal.Decimal + Decimal decimal.Decimal + DecimalPtr decimal.Decimal + } + + t.Run("should query decimal", func(t *testing.T) { + query := SELECT( + Floats.AllColumns, + ).FROM( + Floats, + ).WHERE(Floats.Decimal.EQ(Decimal("1.11111111111111111111"))) + + var result floats + + err := query.Query(db, &result) + require.NoError(t, err) + + require.Equal(t, "1.11111111111111111111", result.Decimal.String()) + require.Equal(t, "0", result.DecimalPtr.String()) // NULL + require.Equal(t, "2.22222222222222222222", result.Numeric.String()) + require.Equal(t, "0", result.NumericPtr.String()) // NULL + + require.Equal(t, 1.1111111111111112, result.Floats.Decimal) // precision loss + require.Equal(t, (*float64)(nil), result.Floats.DecimalPtr) + require.Equal(t, 2.2222222222222223, result.Floats.Numeric) // precision loss + require.Equal(t, (*float64)(nil), result.Floats.NumericPtr) + + // floating point + require.Equal(t, float32(3.3333333), result.Floats.Real) // precision loss + require.Equal(t, (*float32)(nil), result.Floats.RealPtr) + require.Equal(t, 4.444444444444445, result.Floats.Double) // precision loss + require.Equal(t, (*float64)(nil), result.Floats.DoublePtr) + }) + + t.Run("should insert decimal", func(t *testing.T) { + + insertQuery := Floats.INSERT( + Floats.AllColumns, + ).MODEL( + floats{ + Floats: model.Floats{ + // overwritten by wrapped(floats) scope + Numeric: 0.1, + NumericPtr: testutils.Float64Ptr(0.1), + Decimal: 0.1, + DecimalPtr: testutils.Float64Ptr(0.1), + + // not overwritten + Real: 0.4, + RealPtr: testutils.Float32Ptr(0.44), + Double: 0.3, + DoublePtr: testutils.Float64Ptr(0.33), + }, + Numeric: decimal.RequireFromString("0.1234567890123456789"), + NumericPtr: decimal.RequireFromString("1.1111111111111111111"), + Decimal: decimal.RequireFromString("2.2222222222222222222"), + DecimalPtr: decimal.RequireFromString("3.3333333333333333333"), + }, + ).RETURNING( + Floats.AllColumns, + ) + + testutils.AssertDebugStatementSql(t, insertQuery, ` +INSERT INTO test_sample.floats (decimal_ptr, decimal, numeric_ptr, numeric, real_ptr, real, double_ptr, double) +VALUES ('3.3333333333333333333', '2.2222222222222222222', '1.1111111111111111111', '0.1234567890123456789', 0.4399999976158142, 0.4000000059604645, 0.33, 0.3) +RETURNING floats.decimal_ptr AS "floats.decimal_ptr", + floats.decimal AS "floats.decimal", + floats.numeric_ptr AS "floats.numeric_ptr", + floats.numeric AS "floats.numeric", + floats.real_ptr AS "floats.real_ptr", + floats.real AS "floats.real", + floats.double_ptr AS "floats.double_ptr", + floats.double AS "floats.double"; +`) + + var result floats + err := insertQuery.Query(db, &result) + require.NoError(t, err) + + // exact decimal + require.Equal(t, "0.1234567890123456789", result.Numeric.String()) + require.Equal(t, "1.1111111111111111111", result.NumericPtr.String()) + require.Equal(t, "2.2222222222222222222", result.Decimal.String()) + require.Equal(t, "3.3333333333333333333", result.DecimalPtr.String()) + + // precision loss + require.Equal(t, 0.12345678901234568, result.Floats.Numeric) + require.Equal(t, 1.1111111111111112, *result.Floats.NumericPtr) + require.Equal(t, 2.2222222222222223, result.Floats.Decimal) + require.Equal(t, 3.3333333333333335, *result.Floats.DecimalPtr) + + // floating points numbers + require.Equal(t, float32(0.4), result.Floats.Real) + require.Equal(t, float32(0.44), *result.Floats.RealPtr) + require.Equal(t, 0.3, result.Floats.Double) + require.Equal(t, 0.33, *result.Floats.DoublePtr) + }) +} + func TestUUIDComplex(t *testing.T) { query := Person.INNER_JOIN(PersonPhone, PersonPhone.PersonID.EQ(Person.PersonID)). SELECT(Person.AllColumns, PersonPhone.AllColumns). @@ -364,7 +475,7 @@ FROM test_sample."User"; err := stmt.Query(db, &dest) require.NoError(t, err) - testutils.PrintJson(dest) + //testutils.PrintJson(dest) testutils.AssertJSON(t, dest, ` [ @@ -386,3 +497,54 @@ FROM test_sample."User"; ] `) } + +func TestBytea(t *testing.T) { + byteArrHex := "\\x48656c6c6f20476f7068657221" + byteArrBin := []byte("\x48\x65\x6c\x6c\x6f\x20\x47\x6f\x70\x68\x65\x72\x21") + + insertStmt := AllTypes.INSERT(AllTypes.Bytea, AllTypes.ByteaPtr). + VALUES(byteArrHex, byteArrBin). + RETURNING(AllTypes.Bytea, AllTypes.ByteaPtr) + + testutils.AssertStatementSql(t, insertStmt, ` +INSERT INTO test_sample.all_types (bytea, bytea_ptr) +VALUES ($1, $2) +RETURNING all_types.bytea AS "all_types.bytea", + all_types.bytea_ptr AS "all_types.bytea_ptr"; +`, byteArrHex, byteArrBin) + + var inserted model.AllTypes + err := insertStmt.Query(db, &inserted) + require.NoError(t, err) + + require.Equal(t, string(*inserted.ByteaPtr), "Hello Gopher!") + // It is not possible to initiate bytea column using hex format '\xDEADBEEF' with pq driver. + // pq driver always encodes parameter string if destination column is of type bytea. + // Probably pq driver error. + // require.Equal(t, string(inserted.Bytea), "Hello Gopher!") + + stmt := SELECT( + AllTypes.Bytea, + AllTypes.ByteaPtr, + ).FROM( + AllTypes, + ).WHERE( + AllTypes.ByteaPtr.EQ(Bytea(byteArrBin)), + ) + + testutils.AssertStatementSql(t, stmt, ` +SELECT all_types.bytea AS "all_types.bytea", + all_types.bytea_ptr AS "all_types.bytea_ptr" +FROM test_sample.all_types +WHERE all_types.bytea_ptr = $1::bytea; +`, byteArrBin) + + var dest model.AllTypes + + err = stmt.Query(db, &dest) + require.NoError(t, err) + + require.Equal(t, string(*dest.ByteaPtr), "Hello Gopher!") + // Probably pq driver error. + // require.Equal(t, string(dest.Bytea), "Hello Gopher!") +} diff --git a/tests/postgres/scan_test.go b/tests/postgres/scan_test.go index 8eb3276..4a80ba0 100644 --- a/tests/postgres/scan_test.go +++ b/tests/postgres/scan_test.go @@ -1,15 +1,18 @@ package postgres import ( - "fmt" + "context" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + "github.com/go-jet/jet/v2/internal/testutils" . "github.com/go-jet/jet/v2/postgres" "github.com/go-jet/jet/v2/qrm" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model" . "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/table" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "testing" ) var oneInventoryQuery = Inventory. @@ -93,7 +96,7 @@ func TestScanToStruct(t *testing.T) { SELECT(Inventory.AllColumns). ORDER_BY(Inventory.InventoryID) - fmt.Println(query.DebugSql()) + //fmt.Println(query.DebugSql()) t.Run("one struct", func(t *testing.T) { dest := model.Inventory{} @@ -723,6 +726,89 @@ func TestStructScanAllNull(t *testing.T) { }{}) } +func TestRowsScan(t *testing.T) { + + stmt := SELECT( + Inventory.AllColumns, + ).FROM( + Inventory, + ).ORDER_BY( + Inventory.InventoryID.ASC(), + ) + + rows, err := stmt.Rows(context.Background(), db) + require.NoError(t, err) + + for rows.Next() { + var inventory model.Inventory + err = rows.Scan(&inventory) + require.NoError(t, err) + + require.NotEqual(t, inventory.InventoryID, int32(0)) + require.NotEqual(t, inventory.FilmID, int16(0)) + require.NotEqual(t, inventory.StoreID, int16(0)) + require.NotEqual(t, inventory.LastUpdate, time.Time{}) + + if inventory.InventoryID == 2103 { + require.Equal(t, inventory.FilmID, int16(456)) + require.Equal(t, inventory.StoreID, int16(2)) + require.Equal(t, inventory.LastUpdate.Format(time.RFC3339), "2006-02-15T10:09:17Z") + } + } + + err = rows.Close() + require.NoError(t, err) + err = rows.Err() + require.NoError(t, err) + + requireLogged(t, stmt) +} + +func TestScanNumericToNumber(t *testing.T) { + type Number struct { + Int8 int8 + UInt8 uint8 + Int16 int16 + UInt16 uint16 + Int32 int32 + UInt32 uint32 + Int64 int64 + UInt64 uint64 + Float32 float32 + Float64 float64 + } + + numeric := CAST(Decimal("1234567890.111")).AS_NUMERIC() + + stmt := SELECT( + numeric.AS("number.int8"), + numeric.AS("number.uint8"), + numeric.AS("number.int16"), + numeric.AS("number.uint16"), + numeric.AS("number.int32"), + numeric.AS("number.uint32"), + numeric.AS("number.int64"), + numeric.AS("number.uint64"), + numeric.AS("number.float32"), + numeric.AS("number.float64"), + ) + + var number Number + err := stmt.Query(db, &number) + require.NoError(t, err) + + require.Equal(t, number.Int8, int8(-46)) // overflow + require.Equal(t, number.UInt8, uint8(210)) // overflow + require.Equal(t, number.Int16, int16(722)) // overflow + require.Equal(t, number.UInt16, uint16(722)) // overflow + require.Equal(t, number.Int32, int32(1234567890)) + require.Equal(t, number.UInt32, uint32(1234567890)) + require.Equal(t, number.Int64, int64(1234567890)) + require.Equal(t, number.UInt64, uint64(1234567890)) + require.Equal(t, number.Float32, float32(1.234568e+09)) + require.Equal(t, number.Float64, float64(1.234567890111e+09)) +} + var address256 = model.Address{ AddressID: 256, Address: "1497 Yuzhou Drive", diff --git a/tests/postgres/select_test.go b/tests/postgres/select_test.go index e9dd7d2..59fc44a 100644 --- a/tests/postgres/select_test.go +++ b/tests/postgres/select_test.go @@ -1,16 +1,17 @@ package postgres import ( - "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/go-jet/jet/v2/internal/testutils" . "github.com/go-jet/jet/v2/postgres" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/enum" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model" . "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/table" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/view" - "github.com/stretchr/testify/require" - "testing" - "time" ) func TestSelect_ScanToStruct(t *testing.T) { @@ -1255,7 +1256,7 @@ OFFSET 20; LIMIT(10). OFFSET(20) - fmt.Println(query.DebugSql()) + //fmt.Println(query.DebugSql()) testutils.AssertDebugStatementSql(t, query, expectedQuery, float64(100), float64(200), int64(10), int64(20)) @@ -1788,7 +1789,7 @@ func TestJoinViewWithTable(t *testing.T) { Rentals []model.Rental } - fmt.Println(query.DebugSql()) + //fmt.Println(query.DebugSql()) err := query.Query(db, &dest) require.NoError(t, err) @@ -1894,3 +1895,100 @@ WHERE ($1 AND (customer.customer_id = $2)) AND (customer.activebool = $3); require.Len(t, dest, 1) testutils.AssertDeepEqual(t, dest[0], customer0) } + +func TestLateral(t *testing.T) { + + languages := LATERAL( + SELECT( + Language.AllColumns, + ).FROM( + Language, + ).WHERE( + Language.Name.NOT_IN(String("spanish")). + AND(Film.LanguageID.EQ(Language.LanguageID)), + ), + ).AS("films") + + stmt := SELECT( + Film.FilmID, + Film.Title, + languages.AllColumns(), + ).FROM( + Film.CROSS_JOIN(languages), + ).WHERE( + Film.FilmID.EQ(Int(1)), + ).ORDER_BY( + Film.FilmID, + ).LIMIT(1) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT film.film_id AS "film.film_id", + film.title AS "film.title", + films."language.language_id" AS "language.language_id", + films."language.name" AS "language.name", + films."language.last_update" AS "language.last_update" +FROM dvds.film + CROSS JOIN LATERAL ( + SELECT language.language_id AS "language.language_id", + language.name AS "language.name", + language.last_update AS "language.last_update" + FROM dvds.language + WHERE (language.name NOT IN ('spanish')) AND (film.language_id = language.language_id) + ) AS films +WHERE film.film_id = 1 +ORDER BY film.film_id +LIMIT 1; +`) + + type FilmLanguage struct { + model.Film + model.Language + } + + var dest []FilmLanguage + + err := stmt.Query(db, &dest) + require.NoError(t, err) + require.Equal(t, dest[0].Film.Title, "Academy Dinosaur") + require.Equal(t, dest[0].Language.Name, "English ") + + t.Run("implicit cross join", func(t *testing.T) { + stmt2 := SELECT( + Film.FilmID, + Film.Title, + languages.AllColumns(), + ).FROM( + Film, + languages, + ).WHERE( + Film.FilmID.EQ(Int(1)), + ).ORDER_BY( + Film.FilmID, + ).LIMIT(1) + + testutils.AssertDebugStatementSql(t, stmt2, ` +SELECT film.film_id AS "film.film_id", + film.title AS "film.title", + films."language.language_id" AS "language.language_id", + films."language.name" AS "language.name", + films."language.last_update" AS "language.last_update" +FROM dvds.film, + LATERAL ( + SELECT language.language_id AS "language.language_id", + language.name AS "language.name", + language.last_update AS "language.last_update" + FROM dvds.language + WHERE (language.name NOT IN ('spanish')) AND (film.language_id = language.language_id) + ) AS films +WHERE film.film_id = 1 +ORDER BY film.film_id +LIMIT 1; +`) + + var dest2 []FilmLanguage + + err2 := stmt2.Query(db, &dest2) + require.NoError(t, err2) + require.Equal(t, dest, dest2) + }) +} diff --git a/tests/testdata b/tests/testdata index ed53a50..a6c1975 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit ed53a505eb738d1be457877eee251f9ba0418df1 +Subproject commit a6c1975a167645f913496131ae81d4cabc070046