Merge pull request #153 from go-jet/develop

Release 2.8.0
This commit is contained in:
go-jet 2022-05-17 11:45:06 +02:00 committed by GitHub
commit 38b6caf41e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 1999 additions and 1249 deletions

View file

@ -33,6 +33,13 @@ jobs:
MYSQL_USER: jet MYSQL_USER: jet
MYSQL_PASSWORD: jet MYSQL_PASSWORD: jet
- image: cockroachdb/cockroach-unstable:v22.1.0-beta.4
command: ['start-single-node', '--insecure']
environment:
COCKROACH_USER: jet
COCKROACH_PASSWORD: jet
COCKROACH_DATABASE: jetdb
environment: # environment variables for the build itself environment: # environment variables for the build itself
TEST_RESULTS: /tmp/test-results # path to where test results will be saved TEST_RESULTS: /tmp/test-results # path to where test results will be saved
@ -82,7 +89,18 @@ jobs:
echo -n . echo -n .
sleep 1 sleep 1
done done
echo Failed waiting for MySQL && exit 1 echo Failed waiting for MySQL && exit 1
- run:
name: Waiting for Cockroach to be ready
command: |
for i in `seq 1 10`;
do
nc -z localhost 26257 && echo Success && exit 0
echo -n .
sleep 1
done
echo Failed waiting for Cockroach && exit 1
- run: - run:
name: Install MySQL CLI; name: Install MySQL CLI;
@ -122,8 +140,9 @@ jobs:
-coverpkg=github.com/go-jet/jet/v2/postgres/...,github.com/go-jet/jet/v2/mysql/...,github.com/go-jet/jet/v2/sqlite/...,github.com/go-jet/jet/v2/qrm/...,github.com/go-jet/jet/v2/generator/...,github.com/go-jet/jet/v2/internal/... \ -coverpkg=github.com/go-jet/jet/v2/postgres/...,github.com/go-jet/jet/v2/mysql/...,github.com/go-jet/jet/v2/sqlite/...,github.com/go-jet/jet/v2/qrm/...,github.com/go-jet/jet/v2/generator/...,github.com/go-jet/jet/v2/internal/... \
-coverprofile=cover.out 2>&1 | go-junit-report > $TEST_RESULTS/results.xml -coverprofile=cover.out 2>&1 | go-junit-report > $TEST_RESULTS/results.xml
# run mariaDB tests. No need to collect coverage, because coverage is already included with mysql tests # run mariaDB and cockroachdb tests. No need to collect coverage, because coverage is already included with mysql and postgres tests
- run: MY_SQL_SOURCE=MariaDB go test -v ./tests/mysql/ - run: MY_SQL_SOURCE=MariaDB go test -v ./tests/mysql/
- run: PG_SOURCE=COCKROACH_DB go test -v ./tests/postgres/
- save_cache: - save_cache:
key: go-mod-v4-{{ checksum "go.sum" }} key: go-mod-v4-{{ checksum "go.sum" }}

View file

@ -9,7 +9,7 @@
Jet is a complete solution for efficient and high performance database access, consisting of type-safe SQL builder Jet is a complete solution for efficient and high performance database access, consisting of type-safe SQL builder
with code generation and automatic query result data mapping. with code generation and automatic query result data mapping.
Jet currently supports `PostgreSQL`, `MySQL`, `MariaDB` and `SQLite`. Future releases will add support for additional databases. Jet currently supports `PostgreSQL`, `MySQL`, `CockroachDB`, `MariaDB` and `SQLite`. Future releases will add support for additional databases.
![jet](https://github.com/go-jet/jet/wiki/image/jet.png) ![jet](https://github.com/go-jet/jet/wiki/image/jet.png)
Jet is the easiest, and the fastest way to write complex type-safe SQL queries as a Go code and map database query result Jet is the easiest, and the fastest way to write complex type-safe SQL queries as a Go code and map database query result
@ -62,40 +62,37 @@ $ go get -u github.com/go-jet/jet/v2
Jet generator can be installed in one of the following ways: Jet generator can be installed in one of the following ways:
1) (Go1.16+) Install jet generator using go install: - (Go1.16+) Install jet generator using go install:
```sh ```sh
go install github.com/go-jet/jet/v2/cmd/jet@latest 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.*
2) Install jet generator to GOPATH/bin folder: - Install jet generator to specific folder:
```sh ```sh
cd $GOPATH/src/ && GO111MODULE=off go get -u github.com/go-jet/jet/cmd/jet git clone https://github.com/go-jet/jet.git
``` cd jet && go build -o dir_path ./cmd/jet
```
3) Install jet generator into specific folder: *Make sure `dir_path` folder is added to the PATH environment variable.*
```sh
git clone https://github.com/go-jet/jet.git
cd jet && go build -o dir_path ./cmd/jet
```
*Make sure that the destination folder is added to the PATH environment variable.*
### Quick Start ### Quick Start
For this quick start example we will use PostgreSQL sample _'dvd rental'_ database. Full database dump can be found in 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](https://github.com/go-jet/jet-test-data/blob/master/init/postgres/dvds.sql). [./tests/testdata/init/postgres/dvds.sql](https://github.com/go-jet/jet-test-data/blob/master/init/postgres/dvds.sql).
Schema diagram of interest for example can be found [here](./examples/quick-start/diagram.png). Schema diagram of interest can be found [here](./examples/quick-start/diagram.png).
#### Generate SQL Builder and Model types #### Generate SQL Builder and Model types
To generate jet SQL Builder and Data Model types from postgres database, we need to call `jet` generator with postgres To generate jet SQL Builder and Data Model types from running postgres database, we need to call `jet` generator with postgres
connection parameters and root destination folder path for generated files. connection parameters and destination folder path.
Assuming we are running local postgres database, with user `user`, user password `pass`, database `jetdb` and Assuming we are running local postgres database, with user `user`, user password `pass`, database `jetdb` and
schema `dvds` we will use this command: schema `dvds` we will use this command:
```sh ```sh
jet -dsn=postgresql://user:pass@localhost:5432/jetdb -schema=dvds -path=./.gen jet -dsn=postgresql://user:pass@localhost:5432/jetdb?sslmode=disable -schema=dvds -path=./.gen
``` ```
```sh ```sh
Connecting to postgres database: postgresql://user:pass@localhost:5432/jetdb Connecting to postgres database: postgresql://user:pass@localhost:5432/jetdb?sslmode=disable
Retrieving schema information... Retrieving schema information...
FOUND 15 table(s), 7 view(s), 1 enum(s) FOUND 15 table(s), 7 view(s), 1 enum(s)
Cleaning up destination directory... Cleaning up destination directory...
@ -107,9 +104,10 @@ Generating view model files...
Generating enum model files... Generating enum model files...
Done Done
``` ```
Procedure is similar for MySQL, MariaDB and SQLite. For instance: Procedure is similar for MySQL, CockroachDB, MariaDB and SQLite. For example:
```sh ```sh
jet -source=mysql -dsn="user:pass@tcp(localhost:3306)/dbname" -path=./gen jet -source=mysql -dsn="user:pass@tcp(localhost:3306)/dbname" -path=./gen
jet -dsn=postgres://user:pass@localhost:26257/jetdb?sslmode=disable -schema=dvds -path=./.gen #cockroachdb
jet -dsn="mariadb://user:pass@tcp(localhost:3306)/dvds" -path=./gen # source flag can be omitted if data source appears in dsn jet -dsn="mariadb://user:pass@tcp(localhost:3306)/dvds" -path=./gen # source flag can be omitted if data source appears in dsn
jet -source=sqlite -dsn="/path/to/sqlite/database/file" -schema=dvds -path=./gen jet -source=sqlite -dsn="/path/to/sqlite/database/file" -schema=dvds -path=./gen
jet -dsn="file:///path/to/sqlite/database/file" -schema=dvds -path=./gen # sqlite database assumed for 'file' data sources jet -dsn="file:///path/to/sqlite/database/file" -schema=dvds -path=./gen # sqlite database assumed for 'file' data sources
@ -168,7 +166,7 @@ and _film category_ is not 'Action'.
stmt := SELECT( stmt := SELECT(
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate, // or just Actor.AllColumns Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate, // or just Actor.AllColumns
Film.AllColumns, Film.AllColumns,
Language.AllColumns.Except(Language.LastUpdate), Language.AllColumns.Except(Language.LastUpdate), // all language columns except last_update
Category.AllColumns, Category.AllColumns,
).FROM( ).FROM(
Actor. Actor.
@ -186,7 +184,7 @@ stmt := SELECT(
Film.FilmID.ASC(), Film.FilmID.ASC(),
) )
``` ```
_Package(dot) import is used, so the statements would resemble as much as possible as native SQL._ _Package(dot) import is used, so the statements look as close as possible to the native SQL._
Note that every column has a type. String column `Language.Name` and `Category.Name` can be compared only with Note that every column has a type. String column `Language.Name` and `Category.Name` can be compared only with
string columns and expressions. `Actor.ActorID`, `FilmActor.ActorID`, `Film.Length` are integer columns string columns and expressions. `Actor.ActorID`, `FilmActor.ActorID`, `Film.Length` are integer columns
and can be compared only with integer columns and expressions. and can be compared only with integer columns and expressions.
@ -245,7 +243,7 @@ __How to get debug SQL from statement?__
```go ```go
debugSql := stmt.DebugSql() debugSql := stmt.DebugSql()
``` ```
debugSql - this query string can be copy-pasted to sql editor and executed. __It is not intended to be used in production, only for the purpose of debugging!!!__ debugSql - this query string can be copy-pasted to sql editor and executed. __It is not intended to be used in production. For debug purposes only!!!__
<details> <details>
<summary>Click to see debug sql</summary> <summary>Click to see debug sql</summary>
@ -295,8 +293,8 @@ First we have to create desired structure to store query result.
This is done be combining autogenerated model types, or it can be done This is done be combining autogenerated model types, or it can be done
by combining custom model types(see [wiki](https://github.com/go-jet/jet/wiki/Query-Result-Mapping-(QRM)#custom-model-types) for more information). by combining custom model types(see [wiki](https://github.com/go-jet/jet/wiki/Query-Result-Mapping-(QRM)#custom-model-types) for more information).
It's possible to overwrite default jet generator behavior, and all the aspects of generated model and SQLBuilder types can be _Note that it's possible to overwrite default jet generator behavior. All the aspects of generated model and SQLBuilder types can be
tailor-made([wiki](https://github.com/go-jet/jet/wiki/Generator#generator-customization)). tailor-made([wiki](https://github.com/go-jet/jet/wiki/Generator#generator-customization))._
Let's say this is our desired structure made of autogenerated types: Let's say this is our desired structure made of autogenerated types:
```go ```go
@ -315,14 +313,14 @@ var dest []struct {
`Langauge` field is just a single model struct. `Film` can belong to multiple categories. `Langauge` field is just a single model struct. `Film` can belong to multiple categories.
_*There is no limitation of how big or nested destination can be._ _*There is no limitation of how big or nested destination can be._
Now lets execute above statement on open database connection (or transaction) db and store result into `dest`. Now let's execute above statement on open database connection (or transaction) db and store result into `dest`.
```go ```go
err := stmt.Query(db, &dest) err := stmt.Query(db, &dest)
handleError(err) handleError(err)
``` ```
__And thats it.__ __And that's it.__
`dest` now contains the list of all actors(with list of films acted, where each film has information about language and list of belonging categories) that acted in films longer than 180 minutes, film language is 'English' `dest` now contains the list of all actors(with list of films acted, where each film has information about language and list of belonging categories) that acted in films longer than 180 minutes, film language is 'English'
and film category is not 'Action'. and film category is not 'Action'.
@ -528,7 +526,7 @@ The biggest benefit is speed. Speed is being improved in 3 major areas:
##### Speed of development ##### Speed of development
Writing SQL queries is faster and easier as the developers have help of SQL code completion and SQL type safety directly from Go. Writing SQL queries is faster and easier, as developers will have help of SQL code completion and SQL type safety directly from Go code.
Automatic scan to arbitrary structure removes a lot of headache and boilerplate code needed to structure database query result. Automatic scan to arbitrary structure removes a lot of headache and boilerplate code needed to structure database query result.
##### Speed of execution ##### Speed of execution
@ -539,14 +537,14 @@ Thus handler time lost on latency between server and database can be constant. H
only to the query complexity and the number of rows returned from database. only to the query complexity and the number of rows returned from database.
With Jet, it is even possible to 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). This is exactly what is being done in one of the tests: [TestJoinEverything](https://github.com/go-jet/jet/blob/6706f4b228f51cf810129f57ba90bbdb60b85fe7/tests/postgres/chinook_db_test.go#L187).
The whole test database is joined and query result(~10,000 rows) is stored in a structured variable in less than 0.7s. The whole test database is joined and query result(~10,000 rows) is stored in a structured variable in less than 0.5s.
##### How quickly bugs are found ##### How quickly bugs are found
The most expensive bugs are the one discovered on the production, and the least expensive are those found during development. The most expensive bugs are the one discovered on the production, and the least expensive are those found during development.
With automatically generated type safe SQL, not only queries are written faster but bugs are found sooner. With automatically generated type safe SQL, not only queries are written faster but bugs are found sooner.
Lets return to quick start example, and take closer look at a line: Let's return to quick start example, and take closer look at a line:
```go ```go
AND(Film.Length.GT(Int(180))), AND(Film.Length.GT(Int(180))),
``` ```
@ -573,6 +571,8 @@ To run the tests, additional dependencies are required:
- `github.com/stretchr/testify` - `github.com/stretchr/testify`
- `github.com/google/go-cmp` - `github.com/google/go-cmp`
- `github.com/jackc/pgx/v4` - `github.com/jackc/pgx/v4`
- `github.com/shopspring/decimal`
- `github.com/volatiletech/null/v8`
## Versioning ## Versioning

View file

@ -3,6 +3,9 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"os"
"strings"
"github.com/go-jet/jet/v2/generator/metadata" "github.com/go-jet/jet/v2/generator/metadata"
sqlitegen "github.com/go-jet/jet/v2/generator/sqlite" sqlitegen "github.com/go-jet/jet/v2/generator/sqlite"
"github.com/go-jet/jet/v2/generator/template" "github.com/go-jet/jet/v2/generator/template"
@ -11,8 +14,6 @@ import (
"github.com/go-jet/jet/v2/mysql" "github.com/go-jet/jet/v2/mysql"
postgres2 "github.com/go-jet/jet/v2/postgres" postgres2 "github.com/go-jet/jet/v2/postgres"
"github.com/go-jet/jet/v2/sqlite" "github.com/go-jet/jet/v2/sqlite"
"os"
"strings"
mysqlgen "github.com/go-jet/jet/v2/generator/mysql" mysqlgen "github.com/go-jet/jet/v2/generator/mysql"
postgresgen "github.com/go-jet/jet/v2/generator/postgres" postgresgen "github.com/go-jet/jet/v2/generator/postgres"
@ -42,7 +43,7 @@ var (
) )
func init() { func init() {
flag.StringVar(&source, "source", "", "Database system name (postgres, mysql, mariadb or sqlite)") flag.StringVar(&source, "source", "", "Database system name (postgres, mysql, cockroachdb, mariadb or sqlite)")
flag.StringVar(&dsn, "dsn", "", `Data source name. Unified format for connecting to database. flag.StringVar(&dsn, "dsn", "", `Data source name. Unified format for connecting to database.
PostgreSQL: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING PostgreSQL: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
@ -59,7 +60,7 @@ func init() {
flag.StringVar(&user, "user", "", "Database user. Used only if dsn is not set.") flag.StringVar(&user, "user", "", "Database user. Used only if dsn is not set.")
flag.StringVar(&password, "password", "", "The users password. Used only if dsn is not set.") flag.StringVar(&password, "password", "", "The users password. Used only if dsn is not set.")
flag.StringVar(&dbName, "dbname", "", "Database name. Used only if dsn is not set.") flag.StringVar(&dbName, "dbname", "", "Database name. Used only if dsn is not set.")
flag.StringVar(&schemaName, "schema", "public", `Database schema name. Used only if dsn is not set. (default "public")(PostgreSQL only)`) flag.StringVar(&schemaName, "schema", "public", `Database schema name. (default "public")(PostgreSQL only)`)
flag.StringVar(&params, "params", "", "Additional connection string parameters(optional). Used only if dsn is not set.") flag.StringVar(&params, "params", "", "Additional connection string parameters(optional). Used only if dsn is not set.")
flag.StringVar(&sslmode, "sslmode", "disable", `Whether or not to use SSL. Used only if dsn is not set. (optional)(default "disable")(PostgreSQL only)`) flag.StringVar(&sslmode, "sslmode", "disable", `Whether or not to use SSL. Used only if dsn is not set. (optional)(default "disable")(PostgreSQL only)`)
flag.StringVar(&ignoreTables, "ignore-tables", "", `Comma-separated list of tables to ignore`) flag.StringVar(&ignoreTables, "ignore-tables", "", `Comma-separated list of tables to ignore`)
@ -70,33 +71,7 @@ func init() {
} }
func main() { func main() {
flag.Usage = usage
flag.Usage = func() {
fmt.Println("Jet generator 2.7.0")
fmt.Println()
fmt.Println("Usage:")
order := []string{
"source", "dsn", "host", "port", "user", "password", "dbname", "schema", "params", "sslmode",
"path",
"ignore-tables", "ignore-views", "ignore-enums",
}
for _, name := range order {
flagEntry := flag.CommandLine.Lookup(name)
fmt.Printf(" -%s\n", flagEntry.Name)
fmt.Printf("\t%s\n", flagEntry.Usage)
}
fmt.Println()
fmt.Println(`Example command:
$ jet -dsn=postgresql://jet:jet@localhost:5432/jetdb -schema=dvds -path=./gen
$ jet -source=postgres -dsn="user=jet password=jet host=localhost port=5432 dbname=jetdb" -schema=dvds -path=./gen
$ jet -source=mysql -host=localhost -port=3306 -user=jet -password=jet -dbname=jetdb -path=./gen
$ jet -source=sqlite -dsn="file://path/to/sqlite/database/file" -path=./gen
`)
}
flag.Parse() flag.Parse()
if dsn == "" && (source == "" || host == "" || port == 0 || user == "" || dbName == "") { if dsn == "" && (source == "" || host == "" || port == 0 || user == "" || dbName == "") {
@ -111,11 +86,14 @@ func main() {
var err error var err error
switch source { switch source {
case "postgresql", "postgres": case "postgresql", "postgres", "cockroachdb", "cockroach":
generatorTemplate := genTemplate(postgres2.Dialect, ignoreTablesList, ignoreViewsList, ignoreEnumsList)
if dsn != "" { if dsn != "" {
err = postgresgen.GenerateDSN(dsn, schemaName, destDir) err = postgresgen.GenerateDSN(dsn, schemaName, destDir, generatorTemplate)
break break
} }
dbConn := postgresgen.DBConnection{ dbConn := postgresgen.DBConnection{
Host: host, Host: host,
Port: port, Port: port,
@ -131,14 +109,17 @@ func main() {
err = postgresgen.Generate( err = postgresgen.Generate(
destDir, destDir,
dbConn, dbConn,
genTemplate(postgres2.Dialect, ignoreTablesList, ignoreViewsList, ignoreEnumsList), generatorTemplate,
) )
case "mysql", "mysqlx", "mariadb": case "mysql", "mysqlx", "mariadb":
generatorTemplate := genTemplate(mysql.Dialect, ignoreTablesList, ignoreViewsList, ignoreEnumsList)
if dsn != "" { if dsn != "" {
err = mysqlgen.GenerateDSN(dsn, destDir) err = mysqlgen.GenerateDSN(dsn, destDir, generatorTemplate)
break break
} }
dbConn := mysqlgen.DBConnection{ dbConn := mysqlgen.DBConnection{
Host: host, Host: host,
Port: port, Port: port,
@ -151,12 +132,13 @@ func main() {
err = mysqlgen.Generate( err = mysqlgen.Generate(
destDir, destDir,
dbConn, dbConn,
genTemplate(mysql.Dialect, ignoreTablesList, ignoreViewsList, ignoreEnumsList), generatorTemplate,
) )
case "sqlite": case "sqlite":
if dsn == "" { if dsn == "" {
printErrorAndExit("ERROR: required -dsn flag missing.") printErrorAndExit("ERROR: required -dsn flag missing.")
} }
err = sqlitegen.GenerateDSN( err = sqlitegen.GenerateDSN(
dsn, dsn,
destDir, destDir,
@ -176,6 +158,34 @@ func main() {
} }
} }
func usage() {
fmt.Println("Jet generator 2.8.0")
fmt.Println()
fmt.Println("Usage:")
order := []string{
"source", "dsn", "host", "port", "user", "password", "dbname", "schema", "params", "sslmode",
"path",
"ignore-tables", "ignore-views", "ignore-enums",
}
for _, name := range order {
flagEntry := flag.CommandLine.Lookup(name)
fmt.Printf(" -%s\n", flagEntry.Name)
fmt.Printf("\t%s\n", flagEntry.Usage)
}
fmt.Println()
fmt.Println(`Example command:
$ jet -dsn=postgresql://jet:jet@localhost:5432/jetdb?sslmode=disable -schema=dvds -path=./gen
$ jet -dsn=postgres://jet:jet@localhost:26257/jetdb?sslmode=disable -schema=dvds -path=./gen #cockroachdb
$ jet -source=postgres -dsn="user=jet password=jet host=localhost port=5432 dbname=jetdb" -schema=dvds -path=./gen
$ jet -source=mysql -host=localhost -port=3306 -user=jet -password=jet -dbname=jetdb -path=./gen
$ jet -source=sqlite -dsn="file://path/to/sqlite/database/file" -path=./gen
`)
}
func printErrorAndExit(error string) { func printErrorAndExit(error string) {
fmt.Println("\n", error) fmt.Println("\n", error)
fmt.Println() fmt.Println()

View file

@ -42,6 +42,16 @@ func (a ActorTable) FromSchema(schemaName string) *ActorTable {
return newActorTable(schemaName, a.TableName(), a.Alias()) return newActorTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new ActorTable with assigned table prefix
func (a ActorTable) WithPrefix(prefix string) *ActorTable {
return newActorTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new ActorTable with assigned table suffix
func (a ActorTable) WithSuffix(suffix string) *ActorTable {
return newActorTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newActorTable(schemaName, tableName, alias string) *ActorTable { func newActorTable(schemaName, tableName, alias string) *ActorTable {
return &ActorTable{ return &ActorTable{
actorTable: newActorTableImpl(schemaName, tableName, alias), actorTable: newActorTableImpl(schemaName, tableName, alias),

View file

@ -41,6 +41,16 @@ func (a CategoryTable) FromSchema(schemaName string) *CategoryTable {
return newCategoryTable(schemaName, a.TableName(), a.Alias()) return newCategoryTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new CategoryTable with assigned table prefix
func (a CategoryTable) WithPrefix(prefix string) *CategoryTable {
return newCategoryTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new CategoryTable with assigned table suffix
func (a CategoryTable) WithSuffix(suffix string) *CategoryTable {
return newCategoryTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newCategoryTable(schemaName, tableName, alias string) *CategoryTable { func newCategoryTable(schemaName, tableName, alias string) *CategoryTable {
return &CategoryTable{ return &CategoryTable{
categoryTable: newCategoryTableImpl(schemaName, tableName, alias), categoryTable: newCategoryTableImpl(schemaName, tableName, alias),

View file

@ -51,6 +51,16 @@ func (a FilmTable) FromSchema(schemaName string) *FilmTable {
return newFilmTable(schemaName, a.TableName(), a.Alias()) return newFilmTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new FilmTable with assigned table prefix
func (a FilmTable) WithPrefix(prefix string) *FilmTable {
return newFilmTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new FilmTable with assigned table suffix
func (a FilmTable) WithSuffix(suffix string) *FilmTable {
return newFilmTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newFilmTable(schemaName, tableName, alias string) *FilmTable { func newFilmTable(schemaName, tableName, alias string) *FilmTable {
return &FilmTable{ return &FilmTable{
filmTable: newFilmTableImpl(schemaName, tableName, alias), filmTable: newFilmTableImpl(schemaName, tableName, alias),

View file

@ -41,6 +41,16 @@ func (a FilmActorTable) FromSchema(schemaName string) *FilmActorTable {
return newFilmActorTable(schemaName, a.TableName(), a.Alias()) return newFilmActorTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new FilmActorTable with assigned table prefix
func (a FilmActorTable) WithPrefix(prefix string) *FilmActorTable {
return newFilmActorTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new FilmActorTable with assigned table suffix
func (a FilmActorTable) WithSuffix(suffix string) *FilmActorTable {
return newFilmActorTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newFilmActorTable(schemaName, tableName, alias string) *FilmActorTable { func newFilmActorTable(schemaName, tableName, alias string) *FilmActorTable {
return &FilmActorTable{ return &FilmActorTable{
filmActorTable: newFilmActorTableImpl(schemaName, tableName, alias), filmActorTable: newFilmActorTableImpl(schemaName, tableName, alias),

View file

@ -41,6 +41,16 @@ func (a FilmCategoryTable) FromSchema(schemaName string) *FilmCategoryTable {
return newFilmCategoryTable(schemaName, a.TableName(), a.Alias()) return newFilmCategoryTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new FilmCategoryTable with assigned table prefix
func (a FilmCategoryTable) WithPrefix(prefix string) *FilmCategoryTable {
return newFilmCategoryTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new FilmCategoryTable with assigned table suffix
func (a FilmCategoryTable) WithSuffix(suffix string) *FilmCategoryTable {
return newFilmCategoryTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newFilmCategoryTable(schemaName, tableName, alias string) *FilmCategoryTable { func newFilmCategoryTable(schemaName, tableName, alias string) *FilmCategoryTable {
return &FilmCategoryTable{ return &FilmCategoryTable{
filmCategoryTable: newFilmCategoryTableImpl(schemaName, tableName, alias), filmCategoryTable: newFilmCategoryTableImpl(schemaName, tableName, alias),

View file

@ -41,6 +41,16 @@ func (a LanguageTable) FromSchema(schemaName string) *LanguageTable {
return newLanguageTable(schemaName, a.TableName(), a.Alias()) return newLanguageTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new LanguageTable with assigned table prefix
func (a LanguageTable) WithPrefix(prefix string) *LanguageTable {
return newLanguageTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new LanguageTable with assigned table suffix
func (a LanguageTable) WithSuffix(suffix string) *LanguageTable {
return newLanguageTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newLanguageTable(schemaName, tableName, alias string) *LanguageTable { func newLanguageTable(schemaName, tableName, alias string) *LanguageTable {
return &LanguageTable{ return &LanguageTable{
languageTable: newLanguageTableImpl(schemaName, tableName, alias), languageTable: newLanguageTableImpl(schemaName, tableName, alias),

View file

@ -42,6 +42,16 @@ func (a ActorInfoTable) FromSchema(schemaName string) *ActorInfoTable {
return newActorInfoTable(schemaName, a.TableName(), a.Alias()) return newActorInfoTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new ActorInfoTable with assigned table prefix
func (a ActorInfoTable) WithPrefix(prefix string) *ActorInfoTable {
return newActorInfoTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new ActorInfoTable with assigned table suffix
func (a ActorInfoTable) WithSuffix(suffix string) *ActorInfoTable {
return newActorInfoTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newActorInfoTable(schemaName, tableName, alias string) *ActorInfoTable { func newActorInfoTable(schemaName, tableName, alias string) *ActorInfoTable {
return &ActorInfoTable{ return &ActorInfoTable{
actorInfoTable: newActorInfoTableImpl(schemaName, tableName, alias), actorInfoTable: newActorInfoTableImpl(schemaName, tableName, alias),

View file

@ -47,6 +47,16 @@ func (a CustomerListTable) FromSchema(schemaName string) *CustomerListTable {
return newCustomerListTable(schemaName, a.TableName(), a.Alias()) return newCustomerListTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new CustomerListTable with assigned table prefix
func (a CustomerListTable) WithPrefix(prefix string) *CustomerListTable {
return newCustomerListTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new CustomerListTable with assigned table suffix
func (a CustomerListTable) WithSuffix(suffix string) *CustomerListTable {
return newCustomerListTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newCustomerListTable(schemaName, tableName, alias string) *CustomerListTable { func newCustomerListTable(schemaName, tableName, alias string) *CustomerListTable {
return &CustomerListTable{ return &CustomerListTable{
customerListTable: newCustomerListTableImpl(schemaName, tableName, alias), customerListTable: newCustomerListTableImpl(schemaName, tableName, alias),

View file

@ -35,7 +35,9 @@ WITH primaryKeys AS (
SELECT column_name SELECT column_name
FROM information_schema.key_column_usage AS c FROM information_schema.key_column_usage AS c
LEFT JOIN information_schema.table_constraints AS t LEFT JOIN information_schema.table_constraints AS t
ON t.constraint_name = c.constraint_name ON t.constraint_name = c.constraint_name AND
c.table_schema = t.table_schema AND
c.table_name = t.table_name
WHERE t.table_schema = $1 AND t.table_name = $2 AND t.constraint_type = 'PRIMARY KEY' WHERE t.table_schema = $1 AND t.table_name = $2 AND t.constraint_type = 'PRIMARY KEY'
) )
SELECT column_name as "column.Name", SELECT column_name as "column.Name",

View file

@ -49,6 +49,16 @@ func (a {{tableTemplate.TypeName}}) FromSchema(schemaName string) {{tableTemplat
return new{{tableTemplate.TypeName}}(schemaName, a.TableName(), a.Alias()) return new{{tableTemplate.TypeName}}(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new {{tableTemplate.TypeName}} with assigned table prefix
func (a {{tableTemplate.TypeName}}) WithPrefix(prefix string) {{tableTemplate.TypeName}} {
return new{{tableTemplate.TypeName}}(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new {{tableTemplate.TypeName}} with assigned table suffix
func (a {{tableTemplate.TypeName}}) WithSuffix(suffix string) {{tableTemplate.TypeName}} {
return new{{tableTemplate.TypeName}}(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func new{{tableTemplate.TypeName}}(schemaName, tableName, alias string) {{tableTemplate.TypeName}} { func new{{tableTemplate.TypeName}}(schemaName, tableName, alias string) {{tableTemplate.TypeName}} {
var ( var (
{{- range $i, $c := .Columns}} {{- range $i, $c := .Columns}}
@ -119,6 +129,16 @@ func (a {{tableTemplate.TypeName}}) FromSchema(schemaName string) *{{tableTempla
return new{{tableTemplate.TypeName}}(schemaName, a.TableName(), a.Alias()) return new{{tableTemplate.TypeName}}(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new {{tableTemplate.TypeName}} with assigned table prefix
func (a {{tableTemplate.TypeName}}) WithPrefix(prefix string) *{{tableTemplate.TypeName}} {
return new{{tableTemplate.TypeName}}(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new {{tableTemplate.TypeName}} with assigned table suffix
func (a {{tableTemplate.TypeName}}) WithSuffix(suffix string) *{{tableTemplate.TypeName}} {
return new{{tableTemplate.TypeName}}(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func new{{tableTemplate.TypeName}}(schemaName, tableName, alias string) *{{tableTemplate.TypeName}} { func new{{tableTemplate.TypeName}}(schemaName, tableName, alias string) *{{tableTemplate.TypeName}} {
return &{{tableTemplate.TypeName}}{ return &{{tableTemplate.TypeName}}{
{{structImplName}}: new{{tableTemplate.TypeName}}Impl(schemaName, tableName, alias), {{structImplName}}: new{{tableTemplate.TypeName}}Impl(schemaName, tableName, alias),

15
go.mod
View file

@ -4,13 +4,18 @@ go 1.11
require ( require (
github.com/go-sql-driver/mysql v1.5.0 github.com/go-sql-driver/mysql v1.5.0
github.com/google/go-cmp v0.5.7 //tests
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/jackc/pgconn v1.12.0 github.com/jackc/pgconn v1.12.0
github.com/jackc/pgx/v4 v4.16.0 //tests
github.com/lib/pq v1.10.5 github.com/lib/pq v1.10.5
github.com/mattn/go-sqlite3 v1.14.8 github.com/mattn/go-sqlite3 v1.14.8
github.com/pkg/profile v1.5.0 //tests )
github.com/shopspring/decimal v1.2.0 // tests
github.com/stretchr/testify v1.7.0 // tests // test dependencies
require (
github.com/google/go-cmp v0.5.8
github.com/jackc/pgx/v4 v4.16.0
github.com/pkg/profile v1.6.0
github.com/shopspring/decimal v1.3.1
github.com/stretchr/testify v1.7.0
github.com/volatiletech/null/v8 v8.1.2
) )

23
go.sum
View file

@ -9,15 +9,18 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/friendsofgo/errors v0.9.2 h1:X6NYxef4efCBdwI7BgS820zFaN7Cphrmb+Pljdzjtgk=
github.com/friendsofgo/errors v0.9.2/go.mod h1:yCvFW5AkDIL9qn7suHVLiI/gH228n7PC4Pn44IGoTOI=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 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-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/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 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/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -94,8 +97,8 @@ github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxz
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 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/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug= github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM=
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -104,8 +107,9 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
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/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 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/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -117,6 +121,14 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/volatiletech/inflect v0.0.1 h1:2a6FcMQyhmPZcLa+uet3VJ8gLn/9svWhJxJYwvE8KsU=
github.com/volatiletech/inflect v0.0.1/go.mod h1:IBti31tG6phkHitLlr5j7shC5SOo//x0AjDzaJU1PLA=
github.com/volatiletech/null/v8 v8.1.2 h1:kiTiX1PpwvuugKwfvUNX/SU/5A2KGZMXfGD0DUHdKEI=
github.com/volatiletech/null/v8 v8.1.2/go.mod h1:98DbwNoKEpRrYtGjWFctievIfm4n4MxG0A6EBUcoS5g=
github.com/volatiletech/randomize v0.0.1 h1:eE5yajattWqTB2/eN8df4dw+8jwAzBtbdo5sbWC4nMk=
github.com/volatiletech/randomize v0.0.1/go.mod h1:GN3U0QYqfZ9FOJ67bzax1cqZ5q2xuj2mXrXBjWaRTlY=
github.com/volatiletech/strmangle v0.0.1 h1:UKQoHmY6be/R3tSvD2nQYrH41k43OJkidwEiC74KIzk=
github.com/volatiletech/strmangle v0.0.1/go.mod h1:F6RA6IkB5vq0yTG4GQ0UsbbRcl3ni9P76i+JrTBKFFg=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 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.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@ -181,7 +193,6 @@ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View file

@ -4,6 +4,10 @@ package jet
type ColumnList []ColumnExpression type ColumnList []ColumnExpression
// SET creates column assigment for each column in column list. expression should be created by ROW function // SET creates column assigment for each column in column list. expression should be created by ROW function
// Link.UPDATE().
// SET(Link.MutableColumns.SET(ROW(String("github.com"), Bool(false))).
// WHERE(Link.ID.EQ(Int(0)))
//
func (cl ColumnList) SET(expression Expression) ColumnAssigment { func (cl ColumnList) SET(expression Expression) ColumnAssigment {
return columnAssigmentImpl{ return columnAssigmentImpl{
column: cl, column: cl,
@ -11,7 +15,9 @@ func (cl ColumnList) SET(expression Expression) ColumnAssigment {
} }
} }
// Except will create new column list in which columns contained in excluded column names are removed // Except will create new column list in which columns contained in list of excluded column names are removed
// Address.AllColumns.Except(Address.PostalCode, Address.Phone)
//
func (cl ColumnList) Except(excludedColumns ...Column) ColumnList { func (cl ColumnList) Except(excludedColumns ...Column) ColumnList {
excludedColumnList := UnwidColumnList(excludedColumns) excludedColumnList := UnwidColumnList(excludedColumns)
excludedColumnNames := map[string]bool{} excludedColumnNames := map[string]bool{}

View file

@ -263,6 +263,25 @@ func (p *betweenOperatorExpression) serialize(statement StatementType, out *SQLB
p.max.serialize(statement, out, FallTrough(options)...) p.max.serialize(statement, out, FallTrough(options)...)
} }
type customExpression struct {
ExpressionInterfaceImpl
parts []Serializer
}
func newCustomExpression(parts ...Serializer) Expression {
ret := customExpression{
parts: parts,
}
ret.ExpressionInterfaceImpl.Parent = &ret
return &ret
}
func (c *customExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
for _, expression := range c.parts {
expression.serialize(statement, out, options...)
}
}
type complexExpression struct { type complexExpression struct {
ExpressionInterfaceImpl ExpressionInterfaceImpl
expressions Expression expressions Expression

View file

@ -492,6 +492,11 @@ func TO_TIMESTAMP(timestampzStr, format StringExpression) TimestampzExpression {
//----------------- Date/Time Functions and Operators ---------------// //----------------- Date/Time Functions and Operators ---------------//
// EXTRACT extracts time component from time expression
func EXTRACT(field string, from Expression) Expression {
return newCustomExpression(Token("EXTRACT("), Token(field), Token("FROM"), from, Token(")"))
}
// CURRENT_DATE returns current date // CURRENT_DATE returns current date
func CURRENT_DATE() DateExpression { func CURRENT_DATE() DateExpression {
dateFunc := NewDateFunc("CURRENT_DATE") dateFunc := NewDateFunc("CURRENT_DATE")

View file

@ -96,3 +96,10 @@ func (s serializerImpl) serialize(statement StatementType, out *SQLBuilder, opti
clause.Serialize(statement, out, FallTrough(options)...) clause.Serialize(statement, out, FallTrough(options)...)
} }
} }
// Token can be used to construct complex custom expressions
type Token string
func (t Token) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString(string(t))
}

View file

@ -2,6 +2,7 @@ package jet
import ( import (
"bytes" "bytes"
"database/sql/driver"
"fmt" "fmt"
"github.com/go-jet/jet/v2/internal/3rdparty/pq" "github.com/go-jet/jet/v2/internal/3rdparty/pq"
"github.com/go-jet/jet/v2/internal/utils" "github.com/go-jet/jet/v2/internal/utils"
@ -232,17 +233,26 @@ func argToString(value interface{}) string {
case time.Time: case time.Time:
return stringQuote(string(pq.FormatTimestamp(bindVal))) return stringQuote(string(pq.FormatTimestamp(bindVal)))
default: default:
if strBindValue, ok := bindVal.(toStringInterface); ok { if strBindValue, ok := bindVal.(fmt.Stringer); ok {
return stringQuote(strBindValue.String()) return stringQuote(strBindValue.String())
} }
if valuer, ok := bindVal.(driver.Valuer); ok {
val, err := valuer.Value()
if err != nil {
// If valuer for some reason returns an error, we return error string representation.
// This is fine because argToString is called only from DebugSQL, and DebugSQL shouldn't be used in production.
return err.Error()
}
return argToString(val)
}
panic(fmt.Sprintf("jet: %s type can not be used as SQL query parameter", reflect.TypeOf(value).String())) panic(fmt.Sprintf("jet: %s type can not be used as SQL query parameter", reflect.TypeOf(value).String()))
} }
} }
type toStringInterface interface {
String() string
}
func integerTypesToString(value interface{}) string { func integerTypesToString(value interface{}) string {
switch bindVal := value.(type) { switch bindVal := value.(type) {
case int: case int:

View file

@ -11,23 +11,23 @@ import (
type Statement interface { type Statement interface {
// Sql returns parametrized sql query with list of arguments. // Sql returns parametrized sql query with list of arguments.
Sql() (query string, args []interface{}) Sql() (query string, args []interface{})
// DebugSql returns debug query where every parametrized placeholder is replaced with its argument. // DebugSql returns debug query where every parametrized placeholder is replaced with its argument string representation.
// Do not use it in production. Use it only for debug purposes. // Do not use it in production. Use it only for debug purposes.
DebugSql() (query string) DebugSql() (query string)
// Query executes statement over database connection/transaction db and stores row result in destination. // Query executes statement over database connection/transaction db and stores row results in destination.
// Destination can be either pointer to struct or pointer to a slice. // 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. // If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows.
Query(db qrm.DB, destination interface{}) error Query(db qrm.Queryable, destination interface{}) error
// QueryContext executes statement with a context over database connection/transaction 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. // 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. // 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 QueryContext(ctx context.Context, db qrm.Queryable, destination interface{}) error
// Exec executes statement over db connection/transaction without returning any rows. // Exec executes statement over db connection/transaction without returning any rows.
Exec(db qrm.DB) (sql.Result, error) Exec(db qrm.Executable) (sql.Result, error)
// ExecContext executes statement with context over db connection/transaction without returning any rows. // ExecContext executes statement with context over db connection/transaction without returning any rows.
ExecContext(ctx context.Context, db qrm.DB) (sql.Result, error) ExecContext(ctx context.Context, db qrm.Executable) (sql.Result, error)
// Rows executes statements over db connection/transaction and returns rows // Rows executes statements over db connection/transaction and returns rows
Rows(ctx context.Context, db qrm.DB) (*Rows, error) Rows(ctx context.Context, db qrm.Queryable) (*Rows, error)
} }
// Rows wraps sql.Rows type to add query result mapping for Scan method // Rows wraps sql.Rows type to add query result mapping for Scan method
@ -86,11 +86,11 @@ func (s *serializerStatementInterfaceImpl) DebugSql() (query string) {
return return
} }
func (s *serializerStatementInterfaceImpl) Query(db qrm.DB, destination interface{}) error { func (s *serializerStatementInterfaceImpl) Query(db qrm.Queryable, destination interface{}) error {
return s.QueryContext(context.Background(), db, destination) return s.QueryContext(context.Background(), db, destination)
} }
func (s *serializerStatementInterfaceImpl) QueryContext(ctx context.Context, db qrm.DB, destination interface{}) error { func (s *serializerStatementInterfaceImpl) QueryContext(ctx context.Context, db qrm.Queryable, destination interface{}) error {
query, args := s.Sql() query, args := s.Sql()
callLogger(ctx, s) callLogger(ctx, s)
@ -112,11 +112,11 @@ func (s *serializerStatementInterfaceImpl) QueryContext(ctx context.Context, db
return err return err
} }
func (s *serializerStatementInterfaceImpl) Exec(db qrm.DB) (res sql.Result, err error) { func (s *serializerStatementInterfaceImpl) Exec(db qrm.Executable) (res sql.Result, err error) {
return s.ExecContext(context.Background(), db) return s.ExecContext(context.Background(), db)
} }
func (s *serializerStatementInterfaceImpl) ExecContext(ctx context.Context, db qrm.DB) (res sql.Result, err error) { func (s *serializerStatementInterfaceImpl) ExecContext(ctx context.Context, db qrm.Executable) (res sql.Result, err error) {
query, args := s.Sql() query, args := s.Sql()
callLogger(ctx, s) callLogger(ctx, s)
@ -141,7 +141,7 @@ func (s *serializerStatementInterfaceImpl) ExecContext(ctx context.Context, db q
return res, err return res, err
} }
func (s *serializerStatementInterfaceImpl) Rows(ctx context.Context, db qrm.DB) (*Rows, error) { func (s *serializerStatementInterfaceImpl) Rows(ctx context.Context, db qrm.Queryable) (*Rows, error) {
query, args := s.Sql() query, args := s.Sql()
callLogger(ctx, s) callLogger(ctx, s)

View file

@ -2,6 +2,8 @@ package testutils
import ( import (
"bytes" "bytes"
"context"
"database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/go-jet/jet/v2/internal/jet" "github.com/go-jet/jet/v2/internal/jet"
@ -25,6 +27,18 @@ var UnixTimeComparer = cmp.Comparer(func(t1, t2 time.Time) bool {
return t1.Unix() == t2.Unix() return t1.Unix() == t2.Unix()
}) })
// AssertExecAndRollback will execute and rollback statement in sql transaction
func AssertExecAndRollback(t *testing.T, stmt jet.Statement, db *sql.DB, rowsAffected ...int64) {
tx, err := db.Begin()
require.NoError(t, err)
defer func() {
err := tx.Rollback()
require.NoError(t, err)
}()
AssertExec(t, stmt, tx, rowsAffected...)
}
// AssertExec assert statement execution for successful execution and number of rows affected // AssertExec assert statement execution for successful execution and number of rows affected
func AssertExec(t *testing.T, stmt jet.Statement, db qrm.DB, rowsAffected ...int64) { func AssertExec(t *testing.T, stmt jet.Statement, db qrm.DB, rowsAffected ...int64) {
res, err := stmt.Exec(db) res, err := stmt.Exec(db)
@ -38,6 +52,18 @@ func AssertExec(t *testing.T, stmt jet.Statement, db qrm.DB, rowsAffected ...int
} }
} }
// ExecuteInTxAndRollback will execute function in sql transaction and then rollback transaction
func ExecuteInTxAndRollback(t *testing.T, db *sql.DB, f func(tx *sql.Tx)) {
tx, err := db.Begin()
require.NoError(t, err)
defer func() {
err := tx.Rollback()
require.NoError(t, err)
}()
f(tx)
}
// AssertExecErr assert statement execution for failed execution with error string errorStr // AssertExecErr assert statement execution for failed execution with error string errorStr
func AssertExecErr(t *testing.T, stmt jet.Statement, db qrm.DB, errorStr string) { func AssertExecErr(t *testing.T, stmt jet.Statement, db qrm.DB, errorStr string) {
_, err := stmt.Exec(db) _, err := stmt.Exec(db)
@ -45,6 +71,13 @@ func AssertExecErr(t *testing.T, stmt jet.Statement, db qrm.DB, errorStr string)
require.Error(t, err, errorStr) require.Error(t, err, errorStr)
} }
// AssertExecContextErr assert statement execution for failed execution with error string errorStr
func AssertExecContextErr(ctx context.Context, t *testing.T, stmt jet.Statement, db qrm.DB, errorStr string) {
_, err := stmt.ExecContext(ctx, db)
require.Error(t, err, errorStr)
}
func getFullPath(relativePath string) string { func getFullPath(relativePath string) string {
path, _ := os.Getwd() path, _ := os.Getwd()
return filepath.Join(path, "../", relativePath) return filepath.Join(path, "../", relativePath)

View file

@ -224,6 +224,12 @@ var REGEXP_LIKE = jet.REGEXP_LIKE
//----------------- Date/Time Functions and Operators ------------// //----------------- Date/Time Functions and Operators ------------//
// EXTRACT function retrieves subfields such as year or hour from date/time values
// EXTRACT(DAY, User.CreatedAt)
func EXTRACT(field unitType, from Expression) IntegerExpression {
return IntExp(jet.EXTRACT(string(field), from))
}
// CURRENT_DATE returns current date // CURRENT_DATE returns current date
var CURRENT_DATE = jet.CURRENT_DATE var CURRENT_DATE = jet.CURRENT_DATE

View file

@ -39,10 +39,11 @@ const (
type Interval = jet.Interval type Interval = jet.Interval
// INTERVAL creates new temporal interval. // INTERVAL creates new temporal interval.
// In a case of MICROSECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR unit type // In a case of MICROSECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR unit type
// value parameter should be number. For example: INTERVAL(1, DAY) // value parameter has to be a number.
// In a case of other unit types, value should be string with appropriate format. // INTERVAL(1, DAY)
// For example: INTERVAL("10:08:50", HOUR_SECOND) // In a case of other unit types, value should be string with appropriate format.
// INTERVAL("10:08:50", HOUR_SECOND)
func INTERVAL(value interface{}, unitType unitType) Interval { func INTERVAL(value interface{}, unitType unitType) Interval {
switch unitType { switch unitType {
case MICROSECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR: case MICROSECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR:

View file

@ -56,41 +56,41 @@ var String = jet.String
var UUID = jet.UUID var UUID = jet.UUID
// Date creates new date literal // Date creates new date literal
var Date = func(year int, month time.Month, day int) DateExpression { func Date(year int, month time.Month, day int) DateExpression {
return CAST(jet.Date(year, month, day)).AS_DATE() return CAST(jet.Date(year, month, day)).AS_DATE()
} }
// DateT creates new date literal from time.Time // DateT creates new date literal from time.Time
var DateT = func(t time.Time) DateExpression { func DateT(t time.Time) DateExpression {
return CAST(jet.DateT(t)).AS_DATE() return CAST(jet.DateT(t)).AS_DATE()
} }
// Time creates new time literal // Time creates new time literal
var Time = func(hour, minute, second int, nanoseconds ...time.Duration) TimeExpression { func Time(hour, minute, second int, nanoseconds ...time.Duration) TimeExpression {
return CAST(jet.Time(hour, minute, second, nanoseconds...)).AS_TIME() return CAST(jet.Time(hour, minute, second, nanoseconds...)).AS_TIME()
} }
// TimeT creates new time literal from time.Time // TimeT creates new time literal from time.Time
var TimeT = func(t time.Time) TimeExpression { func TimeT(t time.Time) TimeExpression {
return CAST(jet.TimeT(t)).AS_TIME() return CAST(jet.TimeT(t)).AS_TIME()
} }
// DateTime creates new datetime literal // DateTime creates new datetime literal
var DateTime = func(year int, month time.Month, day, hour, minute, second int, nanoseconds ...time.Duration) DateTimeExpression { func DateTime(year int, month time.Month, day, hour, minute, second int, nanoseconds ...time.Duration) DateTimeExpression {
return CAST(jet.Timestamp(year, month, day, hour, minute, second, nanoseconds...)).AS_DATETIME() return CAST(jet.Timestamp(year, month, day, hour, minute, second, nanoseconds...)).AS_DATETIME()
} }
// DateTimeT creates new datetime literal from time.Time // DateTimeT creates new datetime literal from time.Time
var DateTimeT = func(t time.Time) DateTimeExpression { func DateTimeT(t time.Time) DateTimeExpression {
return CAST(jet.TimestampT(t)).AS_DATETIME() return CAST(jet.TimestampT(t)).AS_DATETIME()
} }
// Timestamp creates new timestamp literal // Timestamp creates new timestamp literal
var Timestamp = func(year int, month time.Month, day, hour, minute, second int, nanoseconds ...time.Duration) TimestampExpression { func Timestamp(year int, month time.Month, day, hour, minute, second int, nanoseconds ...time.Duration) TimestampExpression {
return TIMESTAMP(StringExp(jet.Timestamp(year, month, day, hour, minute, second, nanoseconds...))) return TIMESTAMP(StringExp(jet.Timestamp(year, month, day, hour, minute, second, nanoseconds...)))
} }
// TimestampT creates new timestamp literal from time.Time // TimestampT creates new timestamp literal from time.Time
var TimestampT = func(t time.Time) TimestampExpression { func TimestampT(t time.Time) TimestampExpression {
return TIMESTAMP(StringExp(jet.TimestampT(t))) return TIMESTAMP(StringExp(jet.TimestampT(t)))
} }

View file

@ -5,7 +5,7 @@ import (
) )
func TestExpressionCAST_AS(t *testing.T) { func TestExpressionCAST_AS(t *testing.T) {
assertSerialize(t, CAST(String("test")).AS("text"), `$1::text`, "test") assertSerialize(t, CAST(Int(11)).AS("text"), `$1::text`, int64(11))
} }
func TestExpressionCAST_AS_BOOL(t *testing.T) { func TestExpressionCAST_AS_BOOL(t *testing.T) {

View file

@ -4,16 +4,16 @@ import "testing"
func TestString_REGEXP_LIKE_operator(t *testing.T) { func TestString_REGEXP_LIKE_operator(t *testing.T) {
assertSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "(table3.col2 ~* table2.col_str)") assertSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "(table3.col2 ~* table2.col_str)")
assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN")), "(table3.col2 ~* $1)", "JOHN") assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN")), "(table3.col2 ~* $1::text)", "JOHN")
assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), false), "(table3.col2 ~* $1)", "JOHN") assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), false), "(table3.col2 ~* $1::text)", "JOHN")
assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), true), "(table3.col2 ~ $1)", "JOHN") assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), true), "(table3.col2 ~ $1::text)", "JOHN")
} }
func TestString_NOT_REGEXP_LIKE_operator(t *testing.T) { func TestString_NOT_REGEXP_LIKE_operator(t *testing.T) {
assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(table2ColStr), "(table3.col2 !~* table2.col_str)") assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(table2ColStr), "(table3.col2 !~* table2.col_str)")
assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN")), "(table3.col2 !~* $1)", "JOHN") assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN")), "(table3.col2 !~* $1::text)", "JOHN")
assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), false), "(table3.col2 !~* $1)", "JOHN") assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), false), "(table3.col2 !~* $1::text)", "JOHN")
assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 !~ $1)", "JOHN") assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 !~ $1::text)", "JOHN")
} }
func TestExists(t *testing.T) { func TestExists(t *testing.T) {

View file

@ -60,7 +60,7 @@ func TestRawHelperMethods(t *testing.T) {
assertSerialize(t, RawFloat("table.colInt + :float", RawArgs{":float": 11.22}).EQ(Float(3.14)), assertSerialize(t, RawFloat("table.colInt + :float", RawArgs{":float": 11.22}).EQ(Float(3.14)),
"((table.colInt + $1) = $2)", 11.22, 3.14) "((table.colInt + $1) = $2)", 11.22, 3.14)
assertSerialize(t, RawString("table.colStr || str", RawArgs{"str": "doe"}).EQ(String("john doe")), assertSerialize(t, RawString("table.colStr || str", RawArgs{"str": "doe"}).EQ(String("john doe")),
"((table.colStr || $1) = $2)", "doe", "john doe") "((table.colStr || $1) = $2::text)", "doe", "john doe")
now := time.Now() now := time.Now()
assertSerialize(t, RawTime("table.colTime").EQ(TimeT(now)), assertSerialize(t, RawTime("table.colTime").EQ(TimeT(now)),

View file

@ -1,6 +1,8 @@
package postgres package postgres
import "github.com/go-jet/jet/v2/internal/jet" import (
"github.com/go-jet/jet/v2/internal/jet"
)
// This functions can be used, instead of its method counterparts, to have a better indentation of a complex condition // This functions can be used, instead of its method counterparts, to have a better indentation of a complex condition
// in the Go code and in the generated SQL. // in the Go code and in the generated SQL.
@ -279,6 +281,26 @@ var TO_TIMESTAMP = jet.TO_TIMESTAMP
//----------------- Date/Time Functions and Operators ------------// //----------------- Date/Time Functions and Operators ------------//
// Additional time unit types for EXTRACT function
const (
DOW unit = MILLENNIUM + 1 + iota
DOY
EPOCH
ISODOW
ISOYEAR
JULIAN
QUARTER
TIMEZONE
TIMEZONE_HOUR
TIMEZONE_MINUTE
)
// EXTRACT function retrieves subfields such as year or hour from date/time values
// EXTRACT(DAY, User.CreatedAt)
func EXTRACT(field unit, from Expression) FloatExpression {
return FloatExp(jet.EXTRACT(unitToString(field), from))
}
// CURRENT_DATE returns current date // CURRENT_DATE returns current date
var CURRENT_DATE = jet.CURRENT_DATE var CURRENT_DATE = jet.CURRENT_DATE

View file

@ -167,7 +167,7 @@ VALUES ('one', 'two'),
ON CONFLICT (col_bool) WHERE col_bool IS NOT FALSE DO UPDATE ON CONFLICT (col_bool) WHERE col_bool IS NOT FALSE DO UPDATE
SET col_bool = TRUE::boolean, SET col_bool = TRUE::boolean,
col_int = 1, col_int = 1,
(col1, col_bool) = ROW(2, 'two') (col1, col_bool) = ROW(2, 'two'::text)
WHERE table1.col1 > 2 WHERE table1.col1 > 2
RETURNING table1.col1 AS "table1.col1", RETURNING table1.col1 AS "table1.col1",
table1.col_bool AS "table1.col_bool"; table1.col_bool AS "table1.col_bool";
@ -193,7 +193,7 @@ VALUES ('one', 'two'),
ON CONFLICT ON CONSTRAINT idk_primary_key DO UPDATE ON CONFLICT ON CONSTRAINT idk_primary_key DO UPDATE
SET col_bool = FALSE::boolean, SET col_bool = FALSE::boolean,
col_int = 1, col_int = 1,
(col1, col_bool) = ROW(2, 'two') (col1, col_bool) = ROW(2, 'two'::text)
WHERE table1.col1 > 2 WHERE table1.col1 > 2
RETURNING table1.col1 AS "table1.col1", RETURNING table1.col1 AS "table1.col1",
table1.col_bool AS "table1.col_bool"; table1.col_bool AS "table1.col_bool";

View file

@ -10,10 +10,11 @@ import (
) )
type quantityAndUnit = float64 type quantityAndUnit = float64
type unit = float64
// Interval unit types // Interval unit types
const ( const (
YEAR quantityAndUnit = 123456789 + iota YEAR unit = 123456789 + iota
MONTH MONTH
WEEK WEEK
DAY DAY
@ -119,7 +120,7 @@ type intervalExpression struct {
} }
// INTERVAL creates new interval expression from the list of quantity-unit pairs. // INTERVAL creates new interval expression from the list of quantity-unit pairs.
// For example: INTERVAL(1, DAY, 3, MINUTE) // INTERVAL(1, DAY, 3, MINUTE)
func INTERVAL(quantityAndUnit ...quantityAndUnit) IntervalExpression { func INTERVAL(quantityAndUnit ...quantityAndUnit) IntervalExpression {
quantityAndUnitLen := len(quantityAndUnit) quantityAndUnitLen := len(quantityAndUnit)
if quantityAndUnitLen == 0 || quantityAndUnitLen%2 != 0 { if quantityAndUnitLen == 0 || quantityAndUnitLen%2 != 0 {
@ -208,6 +209,27 @@ func unitToString(unit quantityAndUnit) string {
return "CENTURY" return "CENTURY"
case MILLENNIUM: case MILLENNIUM:
return "MILLENNIUM" return "MILLENNIUM"
// additional field units for EXTRACT function
case DOW:
return "DOW"
case DOY:
return "DOY"
case EPOCH:
return "EPOCH"
case ISODOW:
return "ISODOW"
case ISOYEAR:
return "ISOYEAR"
case JULIAN:
return "JULIAN"
case QUARTER:
return "QUARTER"
case TIMEZONE:
return "TIMEZONE"
case TIMEZONE_HOUR:
return "TIMEZONE_HOUR"
case TIMEZONE_MINUTE:
return "TIMEZONE_MINUTE"
default: default:
panic("jet: invalid INTERVAL unit type") panic("jet: invalid INTERVAL unit type")
} }

View file

@ -61,14 +61,16 @@ var Float = jet.Float
var Decimal = jet.Decimal var Decimal = jet.Decimal
// String creates new string literal expression // String creates new string literal expression
var String = jet.String func String(value string) StringExpression {
return CAST(jet.String(value)).AS_TEXT()
}
// UUID is a helper function to create string literal expression from uuid object // UUID is a helper function to create string literal expression from uuid object
// value can be any uuid type with a String method // value can be any uuid type with a String method
var UUID = jet.UUID var UUID = jet.UUID
// Bytea creates new bytea literal expression // Bytea creates new bytea literal expression
var Bytea = func(value interface{}) StringExpression { func Bytea(value interface{}) StringExpression {
switch value.(type) { switch value.(type) {
case string, []byte: case string, []byte:
default: default:
@ -78,51 +80,51 @@ var Bytea = func(value interface{}) StringExpression {
} }
// Date creates new date literal expression // Date creates new date literal expression
var Date = func(year int, month time.Month, day int) DateExpression { func Date(year int, month time.Month, day int) DateExpression {
return CAST(jet.Date(year, month, day)).AS_DATE() return CAST(jet.Date(year, month, day)).AS_DATE()
} }
// DateT creates new date literal expression from time.Time object // DateT creates new date literal expression from time.Time object
var DateT = func(t time.Time) DateExpression { func DateT(t time.Time) DateExpression {
return CAST(jet.DateT(t)).AS_DATE() return CAST(jet.DateT(t)).AS_DATE()
} }
// Time creates new time literal expression // Time creates new time literal expression
var Time = func(hour, minute, second int, nanoseconds ...time.Duration) TimeExpression { func Time(hour, minute, second int, nanoseconds ...time.Duration) TimeExpression {
return CAST(jet.Time(hour, minute, second, nanoseconds...)).AS_TIME() return CAST(jet.Time(hour, minute, second, nanoseconds...)).AS_TIME()
} }
// TimeT creates new time literal expression from time.Time object // TimeT creates new time literal expression from time.Time object
var TimeT = func(t time.Time) TimeExpression { func TimeT(t time.Time) TimeExpression {
return CAST(jet.TimeT(t)).AS_TIME() return CAST(jet.TimeT(t)).AS_TIME()
} }
// Timez creates new time with time zone literal expression // Timez creates new time with time zone literal expression
var Timez = func(hour, minute, second int, milliseconds time.Duration, timezone string) TimezExpression { func Timez(hour, minute, second int, milliseconds time.Duration, timezone string) TimezExpression {
return CAST(jet.Timez(hour, minute, second, milliseconds, timezone)).AS_TIMEZ() return CAST(jet.Timez(hour, minute, second, milliseconds, timezone)).AS_TIMEZ()
} }
// TimezT creates new time with time zone literal expression from time.Time object // TimezT creates new time with time zone literal expression from time.Time object
var TimezT = func(t time.Time) TimezExpression { func TimezT(t time.Time) TimezExpression {
return CAST(jet.TimezT(t)).AS_TIMEZ() return CAST(jet.TimezT(t)).AS_TIMEZ()
} }
// Timestamp creates new timestamp literal expression // Timestamp creates new timestamp literal expression
var Timestamp = func(year int, month time.Month, day, hour, minute, second int, milliseconds ...time.Duration) TimestampExpression { func Timestamp(year int, month time.Month, day, hour, minute, second int, milliseconds ...time.Duration) TimestampExpression {
return CAST(jet.Timestamp(year, month, day, hour, minute, second, milliseconds...)).AS_TIMESTAMP() return CAST(jet.Timestamp(year, month, day, hour, minute, second, milliseconds...)).AS_TIMESTAMP()
} }
// TimestampT creates new timestamp literal expression from time.Time object // TimestampT creates new timestamp literal expression from time.Time object
var TimestampT = func(t time.Time) TimestampExpression { func TimestampT(t time.Time) TimestampExpression {
return CAST(jet.TimestampT(t)).AS_TIMESTAMP() return CAST(jet.TimestampT(t)).AS_TIMESTAMP()
} }
// Timestampz creates new timestamp with time zone literal expression // Timestampz creates new timestamp with time zone literal expression
var Timestampz = func(year int, month time.Month, day, hour, minute, second int, milliseconds time.Duration, timezone string) TimestampzExpression { func Timestampz(year int, month time.Month, day, hour, minute, second int, milliseconds time.Duration, timezone string) TimestampzExpression {
return CAST(jet.Timestampz(year, month, day, hour, minute, second, milliseconds, timezone)).AS_TIMESTAMPZ() return CAST(jet.Timestampz(year, month, day, hour, minute, second, milliseconds, timezone)).AS_TIMESTAMPZ()
} }
// TimestampzT creates new timestamp literal expression from time.Time object // TimestampzT creates new timestamp literal expression from time.Time object
var TimestampzT = func(t time.Time) TimestampzExpression { func TimestampzT(t time.Time) TimestampzExpression {
return CAST(jet.TimestampzT(t)).AS_TIMESTAMPZ() return CAST(jet.TimestampzT(t)).AS_TIMESTAMPZ()
} }

View file

@ -59,7 +59,7 @@ func TestFloat(t *testing.T) {
} }
func TestString(t *testing.T) { func TestString(t *testing.T) {
assertSerialize(t, String("Some text"), `$1`, "Some text") assertSerialize(t, String("Some text"), `$1::text`, "Some text")
} }
func TestBytea(t *testing.T) { func TestBytea(t *testing.T) {

View file

@ -13,3 +13,13 @@ type DB interface {
Query(query string, args ...interface{}) (*sql.Rows, error) Query(query string, args ...interface{}) (*sql.Rows, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
} }
// Queryable interface for sql QueryContext method
type Queryable interface {
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
}
// Executable interface for sql ExecContext method
type Executable interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
}

View file

@ -17,7 +17,7 @@ var ErrNoRows = errors.New("qrm: no rows in result set")
// using context `ctx` into destination `destPtr`. // using context `ctx` into destination `destPtr`.
// Destination can be either pointer to struct or pointer to slice of structs. // Destination can be either pointer to struct or pointer to slice of structs.
// If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows. // If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows.
func Query(ctx context.Context, db DB, query string, args []interface{}, destPtr interface{}) (rowsProcessed int64, err error) { func Query(ctx context.Context, db Queryable, query string, args []interface{}, destPtr interface{}) (rowsProcessed int64, err error) {
utils.MustBeInitializedPtr(db, "jet: db is nil") utils.MustBeInitializedPtr(db, "jet: db is nil")
utils.MustBeInitializedPtr(destPtr, "jet: destination is nil") utils.MustBeInitializedPtr(destPtr, "jet: destination is nil")
@ -88,7 +88,7 @@ func ScanOneRowToDest(scanContext *ScanContext, rows *sql.Rows, destPtr interfac
return nil return nil
} }
func queryToSlice(ctx context.Context, db DB, query string, args []interface{}, slicePtr interface{}) (rowsProcessed int64, err error) { func queryToSlice(ctx context.Context, db Queryable, query string, args []interface{}, slicePtr interface{}) (rowsProcessed int64, err error) {
if ctx == nil { if ctx == nil {
ctx = context.Background() ctx = context.Background()
} }

View file

@ -32,13 +32,20 @@ init-sqlite:
# jet-gen will call generator on each of the test databases to generate sql builder and model files need to run the tests. # jet-gen will call generator on each of the test databases to generate sql builder and model files need to run the tests.
jet-gen-all: install-jet-gen jet-gen-postgres jet-gen-mysql jet-gen-mariadb jet-gen-sqlite jet-gen-all: install-jet-gen jet-gen-postgres jet-gen-mysql jet-gen-mariadb jet-gen-sqlite
ifeq ($(OS),Windows_NT)
target := jet.exe
else
target := jet
endif
install-jet-gen: install-jet-gen:
go build -o ${GOPATH}/bin/jet ../cmd/jet/ go build -o ${GOPATH}/bin/${target} ../cmd/jet/
jet-gen-postgres: jet-gen-postgres:
jet -dsn=postgres://jet:jet@localhost:50901/jetdb?sslmode=disable -schema=dvds -path=./.gentestdata/ jet -dsn=postgres://jet:jet@localhost:50901/jetdb?sslmode=disable -schema=dvds -path=./.gentestdata/
jet -dsn=postgres://jet:jet@localhost:50901/jetdb?sslmode=disable -schema=chinook -path=./.gentestdata/ jet -dsn=postgres://jet:jet@localhost:50901/jetdb?sslmode=disable -schema=chinook -path=./.gentestdata/
jet -dsn=postgres://jet:jet@localhost:50901/jetdb?sslmode=disable -schema=chinook2 -path=./.gentestdata/ jet -dsn=postgres://jet:jet@localhost:50901/jetdb?sslmode=disable -schema=chinook2 -path=./.gentestdata/
jet -dsn=postgres://jet:jet@localhost:50901/jetdb?sslmode=disable -schema=northwind -path=./.gentestdata/
jet -dsn=postgres://jet:jet@localhost:50901/jetdb?sslmode=disable -schema=test_sample -path=./.gentestdata/ jet -dsn=postgres://jet:jet@localhost:50901/jetdb?sslmode=disable -schema=test_sample -path=./.gentestdata/
jet-gen-mysql: jet-gen-mysql:
@ -56,6 +63,12 @@ jet-gen-sqlite:
jet -source=sqlite -dsn="./testdata/init/sqlite/sakila.db" -schema=dvds -path=./.gentestdata/sqlite/sakila jet -source=sqlite -dsn="./testdata/init/sqlite/sakila.db" -schema=dvds -path=./.gentestdata/sqlite/sakila
jet -source=sqlite -dsn="./testdata/init/sqlite/test_sample.db" -schema=dvds -path=./.gentestdata/sqlite/test_sample jet -source=sqlite -dsn="./testdata/init/sqlite/test_sample.db" -schema=dvds -path=./.gentestdata/sqlite/test_sample
jet-gen-cockroach:
jet -dsn=postgres://jet:jet@127.0.0.1:26257/jetdb?sslmode=disable -schema=dvds -path=./.gentestdata/
jet -dsn=postgres://jet:jet@localhost:26257/jetdb?sslmode=disable -schema=chinook -path=./.gentestdata/
jet -dsn=postgres://jet:jet@localhost:26257/jetdb?sslmode=disable -schema=chinook2 -path=./.gentestdata/
jet -dsn=postgres://jet:jet@localhost:26257/jetdb?sslmode=disable -schema=northwind -path=./.gentestdata/
jet -dsn=postgres://jet:jet@localhost:26257/jetdb?sslmode=disable -schema=test_sample -path=./.gentestdata/
# docker-compose-cleanup will stop and remove test containers, volumes, and images. # docker-compose-cleanup will stop and remove test containers, volumes, and images.
cleanup: cleanup:

View file

@ -15,7 +15,24 @@ const (
) )
// PostgresConnectString is PostgreSQL test database connection string // PostgresConnectString is PostgreSQL test database connection string
var PostgresConnectString = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", PgHost, PgPort, PgUser, PgPassword, PgDBName) var PostgresConnectString = pgConnectionString(PgHost, PgPort, PgUser, PgPassword, PgDBName)
// Postgres test database connection parameters
const (
CockroachHost = "localhost"
CockroachPort = 26257
CockroachUser = "jet"
CockroachPassword = "jet"
CockroachDBName = "jetdb"
)
// CockroachConnectString is Cockroach test database connection string
var CockroachConnectString = pgConnectionString(CockroachHost, CockroachPort, CockroachUser, CockroachPassword, CockroachDBName)
func pgConnectionString(host string, port int, user, password, dbName string) string {
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
host, port, user, password, dbName)
}
// MySQL test database connection parameters // MySQL test database connection parameters
const ( const (

View file

@ -37,3 +37,16 @@ services:
- '50903:3306' - '50903:3306'
volumes: volumes:
- ./testdata/init/mysql:/docker-entrypoint-initdb.d - ./testdata/init/mysql:/docker-entrypoint-initdb.d
cockroach:
image: cockroachdb/cockroach-unstable:v22.1.0-beta.4
environment:
- COCKROACH_USER=jet
- COCKROACH_PASSWORD=jet
- COCKROACH_DATABASE=jetdb
ports:
- "26257:26257"
command: start-single-node --insecure
# volumes:
# - ./testdata/init/cockroach:/docker-entrypoint-initdb.d

View file

@ -1,10 +1,12 @@
package main package main
import ( import (
"context"
"database/sql" "database/sql"
"flag" "flag"
"fmt" "fmt"
"github.com/go-jet/jet/v2/generator/mysql" "github.com/go-jet/jet/v2/generator/mysql"
"github.com/go-jet/jet/v2/generator/postgres"
"github.com/go-jet/jet/v2/generator/sqlite" "github.com/go-jet/jet/v2/generator/sqlite"
"github.com/go-jet/jet/v2/tests/internal/utils/repo" "github.com/go-jet/jet/v2/tests/internal/utils/repo"
"io/ioutil" "io/ioutil"
@ -12,46 +14,53 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"github.com/go-jet/jet/v2/generator/postgres"
"github.com/go-jet/jet/v2/internal/utils/throw" "github.com/go-jet/jet/v2/internal/utils/throw"
"github.com/go-jet/jet/v2/tests/dbconfig" "github.com/go-jet/jet/v2/tests/dbconfig"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq" _ "github.com/jackc/pgx/v4/stdlib"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
) )
var testSuite string var testSuite string
func init() { func init() {
flag.StringVar(&testSuite, "testsuite", "all", "Test suite name (postgres or mysql)") flag.StringVar(&testSuite, "testsuite", "all", "Test suite name (postgres, mysql, mariadb, cockroach, sqlite or all)")
flag.Parse() flag.Parse()
} }
// Database names
const (
Postgres = "postgres"
MySql = "mysql"
MariaDB = "mariadb"
Sqlite = "sqlite"
Cockroach = "cockroach"
)
func main() { func main() {
testSuite = strings.ToLower(testSuite) switch strings.ToLower(testSuite) {
case Postgres:
if testSuite == "postgres" { initPostgresDB(Postgres, dbconfig.PostgresConnectString)
initPostgresDB() case Cockroach:
return initPostgresDB(Cockroach, dbconfig.CockroachConnectString)
} case MySql:
initMySQLDB(false)
if testSuite == "mysql" || testSuite == "mariadb" { case MariaDB:
initMySQLDB(testSuite == "mariadb") initMySQLDB(true)
return case Sqlite:
}
if testSuite == "sqlite" {
initSQLiteDB() initSQLiteDB()
return case "all":
initPostgresDB(Cockroach, dbconfig.CockroachConnectString)
initPostgresDB(Postgres, dbconfig.PostgresConnectString)
initMySQLDB(false)
initMySQLDB(true)
initSQLiteDB()
default:
panic("invalid testsuite flag. Test suite name (postgres, mysql, mariadb, cockroach, sqlite or all)")
} }
initPostgresDB()
initMySQLDB(false)
initMySQLDB(true)
initSQLiteDB()
} }
func initSQLiteDB() { func initSQLiteDB() {
@ -109,8 +118,8 @@ func initMySQLDB(isMariaDB bool) {
} }
} }
func initPostgresDB() { func initPostgresDB(dbType string, connectionString string) {
db, err := sql.Open("postgres", dbconfig.PostgresConnectString) db, err := sql.Open("postgres", connectionString)
if err != nil { if err != nil {
panic("Failed to connect to test db: " + err.Error()) panic("Failed to connect to test db: " + err.Error())
} }
@ -120,26 +129,19 @@ func initPostgresDB() {
}() }()
schemaNames := []string{ schemaNames := []string{
"northwind",
"dvds", "dvds",
"test_sample", "test_sample",
"chinook", "chinook",
"chinook2", "chinook2",
"northwind",
} }
for _, schemaName := range schemaNames { for _, schemaName := range schemaNames {
fmt.Println("\nInitializing", schemaName, "schema...")
execFile(db, "./testdata/init/postgres/"+schemaName+".sql") execFile(db, fmt.Sprintf("./testdata/init/%s/%s.sql", dbType, schemaName))
err = postgres.Generate("./.gentestdata", postgres.DBConnection{ err = postgres.GenerateDSN(connectionString, schemaName, "./.gentestdata")
Host: dbconfig.PgHost,
Port: dbconfig.PgPort,
User: dbconfig.PgUser,
Password: dbconfig.PgPassword,
DBName: dbconfig.PgDBName,
SchemaName: schemaName,
SslMode: "disable",
})
throw.OnError(err) throw.OnError(err)
} }
} }
@ -148,10 +150,32 @@ func execFile(db *sql.DB, sqlFilePath string) {
testSampleSql, err := ioutil.ReadFile(sqlFilePath) testSampleSql, err := ioutil.ReadFile(sqlFilePath)
throw.OnError(err) throw.OnError(err)
_, err = db.Exec(string(testSampleSql)) err = execInTx(db, func(tx *sql.Tx) error {
_, err := tx.Exec(string(testSampleSql))
return err
})
throw.OnError(err) throw.OnError(err)
} }
func execInTx(db *sql.DB, f func(tx *sql.Tx) error) error {
tx, err := db.BeginTx(context.Background(), &sql.TxOptions{
Isolation: sql.LevelReadUncommitted, // to speed up initialization of test database
})
if err != nil {
return err
}
err = f(tx)
if err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
func printOnError(err error) { func printOnError(err error) {
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())

View file

@ -20,7 +20,7 @@ import (
func TestAllTypes(t *testing.T) { func TestAllTypes(t *testing.T) {
dest := []model.AllTypes{} var dest []model.AllTypes
err := AllTypes. err := AllTypes.
SELECT(AllTypes.AllColumns). SELECT(AllTypes.AllColumns).
@ -39,7 +39,7 @@ func TestAllTypesViewSelect(t *testing.T) {
type AllTypesView model.AllTypes type AllTypesView model.AllTypes
dest := []AllTypesView{} var dest []AllTypesView
err := view.AllTypesView.SELECT(view.AllTypesView.AllColumns).Query(db, &dest) err := view.AllTypesView.SELECT(view.AllTypesView.AllColumns).Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
@ -539,6 +539,8 @@ func TestTimeExpressions(t *testing.T) {
AllTypes.Time.ADD(INTERVAL(20, MINUTE)).SUB(INTERVAL(11, HOUR)), AllTypes.Time.ADD(INTERVAL(20, MINUTE)).SUB(INTERVAL(11, HOUR)),
EXTRACT(DAY_HOUR, AllTypes.Time),
CURRENT_TIME(), CURRENT_TIME(),
CURRENT_TIME(3), CURRENT_TIME(3),
) )
@ -574,6 +576,7 @@ SELECT CAST('20:34:58' AS TIME),
all_types.time - INTERVAL all_types.small_int MINUTE, all_types.time - INTERVAL all_types.small_int MINUTE,
all_types.time - INTERVAL 3 MINUTE, all_types.time - INTERVAL 3 MINUTE,
(all_types.time + INTERVAL 20 MINUTE) - INTERVAL 11 HOUR, (all_types.time + INTERVAL 20 MINUTE) - INTERVAL 11 HOUR,
EXTRACT(DAY_HOUR FROM all_types.time),
CURRENT_TIME, CURRENT_TIME,
CURRENT_TIME(3) CURRENT_TIME(3)
FROM test_sample.all_types; FROM test_sample.all_types;
@ -936,6 +939,62 @@ func TestINTERVAL(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
} }
func TestTimeEXTRACT(t *testing.T) {
stmt := SELECT(
EXTRACT(MICROSECOND, TimeT(time.Now())),
EXTRACT(SECOND, AllTypes.Time),
EXTRACT(MINUTE, AllTypes.Timestamp),
EXTRACT(HOUR, AllTypes.Timestamp),
EXTRACT(DAY, AllTypes.Date),
EXTRACT(WEEK, AllTypes.Timestamp),
EXTRACT(MONTH, AllTypes.Timestamp.ADD(INTERVAL(1, DAY))),
EXTRACT(QUARTER, AllTypes.Timestamp),
EXTRACT(YEAR, AllTypes.Timestamp).EQ(Int(1189654)),
EXTRACT(SECOND_MICROSECOND, AllTypes.Time),
EXTRACT(MINUTE_MICROSECOND, AllTypes.DateTime),
EXTRACT(MINUTE_SECOND, AllTypes.Timestamp),
EXTRACT(HOUR_MICROSECOND, AllTypes.Timestamp),
EXTRACT(HOUR_SECOND, AllTypes.Timestamp),
EXTRACT(HOUR_MINUTE, AllTypes.Timestamp),
EXTRACT(DAY_MICROSECOND, AllTypes.Timestamp),
EXTRACT(DAY_SECOND, AllTypes.Timestamp),
EXTRACT(DAY_MINUTE, AllTypes.Timestamp),
EXTRACT(DAY_HOUR, AllTypes.Timestamp),
EXTRACT(YEAR_MONTH, AllTypes.Timestamp),
).FROM(
AllTypes,
)
//fmt.Println(stmt.Sql())
testutils.AssertStatementSql(t, stmt, `
SELECT EXTRACT(MICROSECOND FROM CAST(? AS TIME)),
EXTRACT(SECOND FROM all_types.time),
EXTRACT(MINUTE FROM all_types.timestamp),
EXTRACT(HOUR FROM all_types.timestamp),
EXTRACT(DAY FROM all_types.date),
EXTRACT(WEEK FROM all_types.timestamp),
EXTRACT(MONTH FROM all_types.timestamp + INTERVAL 1 DAY),
EXTRACT(QUARTER FROM all_types.timestamp),
EXTRACT(YEAR FROM all_types.timestamp) = ?,
EXTRACT(SECOND_MICROSECOND FROM all_types.time),
EXTRACT(MINUTE_MICROSECOND FROM all_types.date_time),
EXTRACT(MINUTE_SECOND FROM all_types.timestamp),
EXTRACT(HOUR_MICROSECOND FROM all_types.timestamp),
EXTRACT(HOUR_SECOND FROM all_types.timestamp),
EXTRACT(HOUR_MINUTE FROM all_types.timestamp),
EXTRACT(DAY_MICROSECOND FROM all_types.timestamp),
EXTRACT(DAY_SECOND FROM all_types.timestamp),
EXTRACT(DAY_MINUTE FROM all_types.timestamp),
EXTRACT(DAY_HOUR FROM all_types.timestamp),
EXTRACT(YEAR_MONTH FROM all_types.timestamp)
FROM test_sample.all_types;
`)
err := stmt.Query(db, &struct{}{})
require.NoError(t, err)
}
func TestAllTypesInsert(t *testing.T) { func TestAllTypesInsert(t *testing.T) {
tx, err := db.Begin() tx, err := db.Begin()
require.NoError(t, err) require.NoError(t, err)

View file

@ -13,24 +13,20 @@ import (
) )
func TestDeleteWithWhere(t *testing.T) { func TestDeleteWithWhere(t *testing.T) {
initForDeleteTest(t)
var expectedSQL = `
DELETE FROM test_sample.link
WHERE link.name IN ('Gmail', 'Outlook');
`
deleteStmt := Link. deleteStmt := Link.
DELETE(). DELETE().
WHERE(Link.Name.IN(String("Gmail"), String("Outlook"))) WHERE(Link.Name.IN(String("Gmail"), String("Outlook")))
testutils.AssertDebugStatementSql(t, deleteStmt, expectedSQL, "Gmail", "Outlook") testutils.AssertDebugStatementSql(t, deleteStmt, `
testutils.AssertExec(t, deleteStmt, db, 2) DELETE FROM test_sample.link
WHERE link.name IN ('Gmail', 'Outlook');
`, "Gmail", "Outlook")
testutils.AssertExecAndRollback(t, deleteStmt, db, 2)
requireLogged(t, deleteStmt) requireLogged(t, deleteStmt)
} }
func TestDeleteWithWhereOrderByLimit(t *testing.T) { func TestDeleteWithWhereOrderByLimit(t *testing.T) {
initForDeleteTest(t)
var expectedSQL = ` var expectedSQL = `
DELETE FROM test_sample.link DELETE FROM test_sample.link
WHERE link.name IN ('Gmail', 'Outlook') WHERE link.name IN ('Gmail', 'Outlook')
@ -44,13 +40,11 @@ LIMIT 1;
LIMIT(1) LIMIT(1)
testutils.AssertDebugStatementSql(t, deleteStmt, expectedSQL, "Gmail", "Outlook", int64(1)) testutils.AssertDebugStatementSql(t, deleteStmt, expectedSQL, "Gmail", "Outlook", int64(1))
testutils.AssertExec(t, deleteStmt, db, 1) testutils.AssertExecAndRollback(t, deleteStmt, db, 1)
requireLogged(t, deleteStmt) requireLogged(t, deleteStmt)
} }
func TestDeleteQueryContext(t *testing.T) { func TestDeleteQueryContext(t *testing.T) {
initForDeleteTest(t)
deleteStmt := Link. deleteStmt := Link.
DELETE(). DELETE().
WHERE(Link.Name.IN(String("Gmail"), String("Outlook"))) WHERE(Link.Name.IN(String("Gmail"), String("Outlook")))
@ -60,7 +54,7 @@ func TestDeleteQueryContext(t *testing.T) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
dest := []model.Link{} var dest []model.Link
err := deleteStmt.QueryContext(ctx, db, &dest) err := deleteStmt.QueryContext(ctx, db, &dest)
require.Error(t, err, "context deadline exceeded") require.Error(t, err, "context deadline exceeded")
@ -68,8 +62,6 @@ func TestDeleteQueryContext(t *testing.T) {
} }
func TestDeleteExecContext(t *testing.T) { func TestDeleteExecContext(t *testing.T) {
initForDeleteTest(t)
deleteStmt := Link. deleteStmt := Link.
DELETE(). DELETE().
WHERE(Link.Name.IN(String("Gmail"), String("Outlook"))) WHERE(Link.Name.IN(String("Gmail"), String("Outlook")))
@ -84,19 +76,7 @@ func TestDeleteExecContext(t *testing.T) {
require.Error(t, err, "context deadline exceeded") require.Error(t, err, "context deadline exceeded")
} }
func initForDeleteTest(t *testing.T) {
cleanUpLinkTable(t)
stmt := Link.INSERT(Link.URL, Link.Name, Link.Description).
VALUES("www.gmail.com", "Gmail", "Email service developed by Google").
VALUES("www.outlook.live.com", "Outlook", "Email service developed by Microsoft")
testutils.AssertExec(t, stmt, db, 2)
}
func TestDeleteWithUsing(t *testing.T) { func TestDeleteWithUsing(t *testing.T) {
tx := beginTx(t)
defer tx.Rollback()
stmt := table.Rental.DELETE(). stmt := table.Rental.DELETE().
USING( USING(
table.Rental. table.Rental.
@ -116,5 +96,5 @@ USING dvds.rental
WHERE (staff.staff_id != ?) AND (rental.rental_id < ?); WHERE (staff.staff_id != ?) AND (rental.rental_id < ?);
`) `)
testutils.AssertExec(t, stmt, tx) testutils.AssertExecAndRollback(t, stmt, db)
} }

View file

@ -88,45 +88,70 @@ func TestCmdGenerator(t *testing.T) {
} }
func TestIgnoreTablesViewsEnums(t *testing.T) { func TestIgnoreTablesViewsEnums(t *testing.T) {
cmd := exec.Command("jet", tests := []struct {
"-source=MySQL", name string
"-dbname=dvds", args []string
"-host="+dbconfig.MySqLHost, }{
"-port="+strconv.Itoa(dbconfig.MySQLPort), {
"-user="+dbconfig.MySQLUser, name: "with dsn",
"-password="+dbconfig.MySQLPassword, args: []string{
"-ignore-tables=actor,ADDRESS,Category, city ,country,staff,store,rental", "-dsn=mysql://" + dbconfig.MySQLConnectionString(sourceIsMariaDB(), "dvds"),
"-ignore-views=actor_info,CUSTomER_LIST, film_list", "-ignore-tables=actor,ADDRESS,Category, city ,country,staff,store,rental",
"-ignore-enums=film_list_rating,film_rating", "-ignore-views=actor_info,CUSTomER_LIST, film_list",
"-path="+genTestDir3) "-ignore-enums=film_list_rating,film_rating",
"-path=" + genTestDir3,
},
},
{
name: "without dsn",
args: []string{
"-source=MySQL",
"-dbname=dvds",
"-host=" + dbconfig.MySqLHost,
"-port=" + strconv.Itoa(dbconfig.MySQLPort),
"-user=" + dbconfig.MySQLUser,
"-password=" + dbconfig.MySQLPassword,
"-ignore-tables=actor,ADDRESS,Category, city ,country,staff,store,rental",
"-ignore-views=actor_info,CUSTomER_LIST, film_list",
"-ignore-enums=film_list_rating,film_rating",
"-path=" + genTestDir3,
},
},
}
cmd.Stderr = os.Stderr for _, tt := range tests {
cmd.Stdout = os.Stdout t.Run(tt.name, func(t *testing.T) {
cmd := exec.Command("jet", tt.args...)
err := cmd.Run() cmd.Stderr = os.Stderr
require.NoError(t, err) cmd.Stdout = os.Stdout
tableSQLBuilderFiles, err := ioutil.ReadDir(genTestDir3 + "/dvds/table") err := cmd.Run()
require.NoError(t, err) require.NoError(t, err)
testutils.AssertFileNamesEqual(t, tableSQLBuilderFiles, "customer.go", "film.go", "film_actor.go",
"film_category.go", "film_text.go", "inventory.go", "language.go", "payment.go")
viewSQLBuilderFiles, err := ioutil.ReadDir(genTestDir3 + "/dvds/view") tableSQLBuilderFiles, err := ioutil.ReadDir(genTestDir3 + "/dvds/table")
require.NoError(t, err) require.NoError(t, err)
testutils.AssertFileNamesEqual(t, viewSQLBuilderFiles, "nicer_but_slower_film_list.go", testutils.AssertFileNamesEqual(t, tableSQLBuilderFiles, "customer.go", "film.go", "film_actor.go",
"sales_by_film_category.go", "sales_by_store.go", "staff_list.go") "film_category.go", "film_text.go", "inventory.go", "language.go", "payment.go")
enumFiles, err := ioutil.ReadDir(genTestDir3 + "/dvds/enum") viewSQLBuilderFiles, err := ioutil.ReadDir(genTestDir3 + "/dvds/view")
require.NoError(t, err) require.NoError(t, err)
testutils.AssertFileNamesEqual(t, enumFiles, "nicer_but_slower_film_list_rating.go") testutils.AssertFileNamesEqual(t, viewSQLBuilderFiles, "nicer_but_slower_film_list.go",
"sales_by_film_category.go", "sales_by_store.go", "staff_list.go")
modelFiles, err := ioutil.ReadDir(genTestDir3 + "/dvds/model") enumFiles, err := ioutil.ReadDir(genTestDir3 + "/dvds/enum")
require.NoError(t, err) require.NoError(t, err)
testutils.AssertFileNamesEqual(t, enumFiles, "nicer_but_slower_film_list_rating.go")
testutils.AssertFileNamesEqual(t, modelFiles, modelFiles, err := ioutil.ReadDir(genTestDir3 + "/dvds/model")
"customer.go", "film.go", "film_actor.go", "film_category.go", "film_text.go", "inventory.go", "language.go", require.NoError(t, err)
"payment.go", "nicer_but_slower_film_list_rating.go", "nicer_but_slower_film_list.go", "sales_by_film_category.go",
"sales_by_store.go", "staff_list.go") testutils.AssertFileNamesEqual(t, modelFiles,
"customer.go", "film.go", "film_actor.go", "film_category.go", "film_text.go", "inventory.go", "language.go",
"payment.go", "nicer_but_slower_film_list_rating.go", "nicer_but_slower_film_list.go", "sales_by_film_category.go",
"sales_by_store.go", "staff_list.go")
})
}
} }
func assertGeneratedFiles(t *testing.T) { func assertGeneratedFiles(t *testing.T) {
@ -236,6 +261,16 @@ func (a ActorTable) FromSchema(schemaName string) ActorTable {
return newActorTable(schemaName, a.TableName(), a.Alias()) return newActorTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new ActorTable with assigned table prefix
func (a ActorTable) WithPrefix(prefix string) ActorTable {
return newActorTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new ActorTable with assigned table suffix
func (a ActorTable) WithSuffix(suffix string) ActorTable {
return newActorTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newActorTable(schemaName, tableName, alias string) ActorTable { func newActorTable(schemaName, tableName, alias string) ActorTable {
var ( var (
ActorIDColumn = mysql.IntegerColumn("actor_id") ActorIDColumn = mysql.IntegerColumn("actor_id")
@ -322,6 +357,16 @@ func (a ActorInfoTable) FromSchema(schemaName string) ActorInfoTable {
return newActorInfoTable(schemaName, a.TableName(), a.Alias()) return newActorInfoTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new ActorInfoTable with assigned table prefix
func (a ActorInfoTable) WithPrefix(prefix string) ActorInfoTable {
return newActorInfoTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new ActorInfoTable with assigned table suffix
func (a ActorInfoTable) WithSuffix(suffix string) ActorInfoTable {
return newActorInfoTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newActorInfoTable(schemaName, tableName, alias string) ActorInfoTable { func newActorInfoTable(schemaName, tableName, alias string) ActorInfoTable {
var ( var (
ActorIDColumn = mysql.IntegerColumn("actor_id") ActorIDColumn = mysql.IntegerColumn("actor_id")

View file

@ -2,6 +2,7 @@ package mysql
import ( import (
"context" "context"
"database/sql"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
. "github.com/go-jet/jet/v2/mysql" . "github.com/go-jet/jet/v2/mysql"
"github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/model" "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/model"
@ -13,51 +14,47 @@ import (
) )
func TestInsertValues(t *testing.T) { func TestInsertValues(t *testing.T) {
cleanUpLinkTable(t)
var expectedSQL = `
INSERT INTO test_sample.link (id, url, name, description)
VALUES (100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT),
(101, 'http://www.google.com', 'Google', DEFAULT),
(102, 'http://www.yahoo.com', 'Yahoo', NULL);
`
insertQuery := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description). insertQuery := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT). VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
VALUES(101, "http://www.google.com", "Google", DEFAULT). VALUES(101, "http://www.google.com", "Google", DEFAULT).
VALUES(102, "http://www.yahoo.com", "Yahoo", nil) VALUES(102, "http://www.yahoo.com", "Yahoo", nil)
testutils.AssertDebugStatementSql(t, insertQuery, expectedSQL, testutils.AssertDebugStatementSql(t, insertQuery, `
INSERT INTO test_sample.link (id, url, name, description)
VALUES (100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT),
(101, 'http://www.google.com', 'Google', DEFAULT),
(102, 'http://www.yahoo.com', 'Yahoo', NULL);
`,
100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", 100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
101, "http://www.google.com", "Google", 101, "http://www.google.com", "Google",
102, "http://www.yahoo.com", "Yahoo", nil) 102, "http://www.yahoo.com", "Yahoo", nil)
_, err := insertQuery.Exec(db) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
require.NoError(t, err) _, err := insertQuery.Exec(tx)
requireLogged(t, insertQuery) require.NoError(t, err)
requireLogged(t, insertQuery)
insertedLinks := []model.Link{} var insertedLinks []model.Link
err = Link.SELECT(Link.AllColumns). err = Link.SELECT(Link.AllColumns).
WHERE(Link.ID.GT_EQ(Int(100))). WHERE(Link.ID.BETWEEN(Int(100), Int(199))).
ORDER_BY(Link.ID). ORDER_BY(Link.ID).
Query(db, &insertedLinks) Query(tx, &insertedLinks)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(insertedLinks), 3) require.Equal(t, len(insertedLinks), 3)
testutils.AssertDeepEqual(t, insertedLinks[0], postgreTutorial) testutils.AssertDeepEqual(t, insertedLinks[0], postgreTutorial)
testutils.AssertDeepEqual(t, insertedLinks[1], model.Link{
testutils.AssertDeepEqual(t, insertedLinks[1], model.Link{ ID: 101,
ID: 101, URL: "http://www.google.com",
URL: "http://www.google.com", Name: "Google",
Name: "Google", })
}) testutils.AssertDeepEqual(t, insertedLinks[2], model.Link{
ID: 102,
testutils.AssertDeepEqual(t, insertedLinks[2], model.Link{ URL: "http://www.yahoo.com",
ID: 102, Name: "Yahoo",
URL: "http://www.yahoo.com", })
Name: "Yahoo",
}) })
} }
@ -68,42 +65,34 @@ var postgreTutorial = model.Link{
} }
func TestInsertEmptyColumnList(t *testing.T) { func TestInsertEmptyColumnList(t *testing.T) {
cleanUpLinkTable(t)
expectedSQL := `
INSERT INTO test_sample.link
VALUES (100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT);
`
stmt := Link.INSERT(). stmt := Link.INSERT().
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT) VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT)
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, testutils.AssertDebugStatementSql(t, stmt, `
INSERT INTO test_sample.link
VALUES (100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT);
`,
100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial") 100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial")
_, err := stmt.Exec(db) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
require.NoError(t, err) _, err := stmt.Exec(tx)
requireLogged(t, stmt) require.NoError(t, err)
requireLogged(t, stmt)
insertedLinks := []model.Link{} var insertedLinks []model.Link
err = Link.SELECT(Link.AllColumns). err = Link.SELECT(Link.AllColumns).
WHERE(Link.ID.GT_EQ(Int(100))). WHERE(Link.ID.BETWEEN(Int(100), Int(199))).
ORDER_BY(Link.ID). ORDER_BY(Link.ID).
Query(db, &insertedLinks) Query(tx, &insertedLinks)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(insertedLinks), 1) require.Equal(t, len(insertedLinks), 1)
testutils.AssertDeepEqual(t, insertedLinks[0], postgreTutorial) testutils.AssertDeepEqual(t, insertedLinks[0], postgreTutorial)
})
} }
func TestInsertModelObject(t *testing.T) { func TestInsertModelObject(t *testing.T) {
cleanUpLinkTable(t)
var expectedSQL = `
INSERT INTO test_sample.link (url, name)
VALUES ('http://www.duckduckgo.com', 'Duck Duck go');
`
linkData := model.Link{ linkData := model.Link{
URL: "http://www.duckduckgo.com", URL: "http://www.duckduckgo.com",
Name: "Duck Duck go", Name: "Duck Duck go",
@ -113,19 +102,19 @@ VALUES ('http://www.duckduckgo.com', 'Duck Duck go');
INSERT(Link.URL, Link.Name). INSERT(Link.URL, Link.Name).
MODEL(linkData) MODEL(linkData)
testutils.AssertDebugStatementSql(t, query, expectedSQL, "http://www.duckduckgo.com", "Duck Duck go") testutils.AssertDebugStatementSql(t, query, `
INSERT INTO test_sample.link (url, name)
VALUES ('http://www.duckduckgo.com', 'Duck Duck go');
`,
"http://www.duckduckgo.com", "Duck Duck go")
_, err := query.Exec(db) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
require.NoError(t, err) _, err := query.Exec(tx)
require.NoError(t, err)
})
} }
func TestInsertModelObjectEmptyColumnList(t *testing.T) { func TestInsertModelObjectEmptyColumnList(t *testing.T) {
cleanUpLinkTable(t)
var expectedSQL = `
INSERT INTO test_sample.link
VALUES (1000, 'http://www.duckduckgo.com', 'Duck Duck go', NULL);
`
linkData := model.Link{ linkData := model.Link{
ID: 1000, ID: 1000,
URL: "http://www.duckduckgo.com", URL: "http://www.duckduckgo.com",
@ -136,20 +125,18 @@ VALUES (1000, 'http://www.duckduckgo.com', 'Duck Duck go', NULL);
INSERT(). INSERT().
MODEL(linkData) MODEL(linkData)
testutils.AssertDebugStatementSql(t, query, expectedSQL, int32(1000), "http://www.duckduckgo.com", "Duck Duck go", nil) testutils.AssertDebugStatementSql(t, query, `
INSERT INTO test_sample.link
VALUES (1000, 'http://www.duckduckgo.com', 'Duck Duck go', NULL);
`, int32(1000), "http://www.duckduckgo.com", "Duck Duck go", nil)
_, err := query.Exec(db) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
require.NoError(t, err) _, err := query.Exec(tx)
require.NoError(t, err)
})
} }
func TestInsertModelsObject(t *testing.T) { func TestInsertModelsObject(t *testing.T) {
expectedSQL := `
INSERT INTO test_sample.link (url, name)
VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial'),
('http://www.google.com', 'Google'),
('http://www.yahoo.com', 'Yahoo');
`
tutorial := model.Link{ tutorial := model.Link{
URL: "http://www.postgresqltutorial.com", URL: "http://www.postgresqltutorial.com",
Name: "PostgreSQL Tutorial", Name: "PostgreSQL Tutorial",
@ -169,24 +156,23 @@ VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial'),
INSERT(Link.URL, Link.Name). INSERT(Link.URL, Link.Name).
MODELS([]model.Link{tutorial, google, yahoo}) MODELS([]model.Link{tutorial, google, yahoo})
testutils.AssertDebugStatementSql(t, query, expectedSQL, testutils.AssertDebugStatementSql(t, query, `
INSERT INTO test_sample.link (url, name)
VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial'),
('http://www.google.com', 'Google'),
('http://www.yahoo.com', 'Yahoo');
`,
"http://www.postgresqltutorial.com", "PostgreSQL Tutorial", "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
"http://www.google.com", "Google", "http://www.google.com", "Google",
"http://www.yahoo.com", "Yahoo") "http://www.yahoo.com", "Yahoo")
_, err := query.Exec(db) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
require.NoError(t, err) _, err := query.Exec(tx)
require.NoError(t, err)
})
} }
func TestInsertUsingMutableColumns(t *testing.T) { func TestInsertUsingMutableColumns(t *testing.T) {
var expectedSQL = `
INSERT INTO test_sample.link (url, name, description)
VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT),
('http://www.google.com', 'Google', NULL),
('http://www.google.com', 'Google', NULL),
('http://www.yahoo.com', 'Yahoo', NULL);
`
google := model.Link{ google := model.Link{
URL: "http://www.google.com", URL: "http://www.google.com",
Name: "Google", Name: "Google",
@ -203,31 +189,25 @@ VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT),
MODEL(google). MODEL(google).
MODELS([]model.Link{google, yahoo}) MODELS([]model.Link{google, yahoo})
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, testutils.AssertDebugStatementSql(t, stmt, `
INSERT INTO test_sample.link (url, name, description)
VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT),
('http://www.google.com', 'Google', NULL),
('http://www.google.com', 'Google', NULL),
('http://www.yahoo.com', 'Yahoo', NULL);
`,
"http://www.postgresqltutorial.com", "PostgreSQL Tutorial", "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
"http://www.google.com", "Google", nil, "http://www.google.com", "Google", nil,
"http://www.google.com", "Google", nil, "http://www.google.com", "Google", nil,
"http://www.yahoo.com", "Yahoo", nil) "http://www.yahoo.com", "Yahoo", nil)
_, err := stmt.Exec(db) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
require.NoError(t, err) _, err := stmt.Exec(tx)
require.NoError(t, err)
})
} }
func TestInsertQuery(t *testing.T) { func TestInsertQuery(t *testing.T) {
_, err := Link.DELETE().
WHERE(Link.ID.NOT_EQ(Int(1)).AND(Link.Name.EQ(String("Youtube")))).
Exec(db)
require.NoError(t, err)
var expectedSQL = `
INSERT INTO test_sample.link (url, name) (
SELECT link.url AS "link.url",
link.name AS "link.name"
FROM test_sample.link
WHERE link.id = 1
);
`
query := Link. query := Link.
INSERT(Link.URL, Link.Name). INSERT(Link.URL, Link.Name).
QUERY( QUERY(
@ -236,19 +216,28 @@ INSERT INTO test_sample.link (url, name) (
WHERE(Link.ID.EQ(Int(1))), WHERE(Link.ID.EQ(Int(1))),
) )
testutils.AssertDebugStatementSql(t, query, expectedSQL, int64(1)) testutils.AssertDebugStatementSql(t, query, `
INSERT INTO test_sample.link (url, name) (
SELECT link.url AS "link.url",
link.name AS "link.name"
FROM test_sample.link
WHERE link.id = 1
);
`, int64(1))
_, err = query.Exec(db) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
require.NoError(t, err) _, err := query.Exec(tx)
require.NoError(t, err)
youtubeLinks := []model.Link{} var youtubeLinks []model.Link
err = Link. err = Link.
SELECT(Link.AllColumns). SELECT(Link.AllColumns).
WHERE(Link.Name.EQ(String("Youtube"))). WHERE(Link.Name.EQ(String("Youtube"))).
Query(db, &youtubeLinks) Query(tx, &youtubeLinks)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(youtubeLinks), 2) require.Equal(t, len(youtubeLinks), 2)
})
} }
func TestInsertOnDuplicateKey(t *testing.T) { func TestInsertOnDuplicateKey(t *testing.T) {
@ -272,28 +261,29 @@ ON DUPLICATE KEY UPDATE id = (id + ?),
randId, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", randId, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
int64(11), "PostgreSQL Tutorial 2") int64(11), "PostgreSQL Tutorial 2")
testutils.AssertExec(t, stmt, db, 3) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
_, err := stmt.Exec(tx)
require.NoError(t, err)
newLinks := []model.Link{} var newLinks []model.Link
err := SELECT(Link.AllColumns). err = SELECT(Link.AllColumns).
FROM(Link). FROM(Link).
WHERE(Link.ID.EQ(Int32(randId).ADD(Int(11)))). WHERE(Link.ID.EQ(Int32(randId).ADD(Int(11)))).
Query(db, &newLinks) Query(tx, &newLinks)
require.NoError(t, err) require.NoError(t, err)
require.Len(t, newLinks, 1) require.Len(t, newLinks, 1)
require.Equal(t, newLinks[0], model.Link{ require.Equal(t, newLinks[0], model.Link{
ID: randId + 11, ID: randId + 11,
URL: "http://www.postgresqltutorial.com", URL: "http://www.postgresqltutorial.com",
Name: "PostgreSQL Tutorial 2", Name: "PostgreSQL Tutorial 2",
Description: nil, Description: nil,
})
}) })
} }
func TestInsertWithQueryContext(t *testing.T) { func TestInsertWithQueryContext(t *testing.T) {
cleanUpLinkTable(t)
stmt := Link.INSERT(). stmt := Link.INSERT().
VALUES(1100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT) VALUES(1100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT)
@ -302,15 +292,13 @@ func TestInsertWithQueryContext(t *testing.T) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
dest := []model.Link{} var dest []model.Link
err := stmt.QueryContext(ctx, db, &dest) err := stmt.QueryContext(ctx, db, &dest)
require.Error(t, err, "context deadline exceeded") require.Error(t, err, "context deadline exceeded")
} }
func TestInsertWithExecContext(t *testing.T) { func TestInsertWithExecContext(t *testing.T) {
cleanUpLinkTable(t)
stmt := Link.INSERT(). stmt := Link.INSERT().
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT) VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT)
@ -323,8 +311,3 @@ func TestInsertWithExecContext(t *testing.T) {
require.Error(t, err, "context deadline exceeded") require.Error(t, err, "context deadline exceeded")
} }
func cleanUpLinkTable(t *testing.T) {
_, err := Link.DELETE().WHERE(Link.ID.GT(Int(1))).Exec(db)
require.NoError(t, err)
}

View file

@ -96,9 +96,3 @@ func skipForMariaDB(t *testing.T) {
t.SkipNow() t.SkipNow()
} }
} }
func beginTx(t *testing.T) *sql.Tx {
tx, err := db.Begin()
require.NoError(t, err)
return tx
}

View file

@ -2,6 +2,7 @@ package mysql
import ( import (
"context" "context"
"database/sql"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
. "github.com/go-jet/jet/v2/mysql" . "github.com/go-jet/jet/v2/mysql"
"github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table" "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table"
@ -13,8 +14,6 @@ import (
) )
func TestUpdateValues(t *testing.T) { func TestUpdateValues(t *testing.T) {
setupLinkTableForUpdateTest(t)
var expectedSQL = ` var expectedSQL = `
UPDATE test_sample.link UPDATE test_sample.link
SET name = 'Bong', SET name = 'Bong',
@ -28,8 +27,26 @@ WHERE link.name = 'Bing';
WHERE(Link.Name.EQ(String("Bing"))) WHERE(Link.Name.EQ(String("Bing")))
testutils.AssertDebugStatementSql(t, query, expectedSQL, "Bong", "http://bong.com", "Bing") testutils.AssertDebugStatementSql(t, query, expectedSQL, "Bong", "http://bong.com", "Bing")
testutils.AssertExec(t, query, db) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
requireLogged(t, query) testutils.AssertExec(t, query, tx)
requireLogged(t, query)
var links []model.Link
err := Link.
SELECT(Link.AllColumns).
WHERE(Link.Name.EQ(String("Bong"))).
Query(tx, &links)
require.NoError(t, err)
require.Equal(t, len(links), 1)
testutils.AssertDeepEqual(t, links[0], model.Link{
ID: 204,
URL: "http://bong.com",
Name: "Bong",
})
})
}) })
t.Run("new version", func(t *testing.T) { t.Run("new version", func(t *testing.T) {
@ -41,29 +58,29 @@ WHERE link.name = 'Bing';
WHERE(Link.Name.EQ(String("Bing"))) WHERE(Link.Name.EQ(String("Bing")))
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, "Bong", "http://bong.com", "Bing") testutils.AssertDebugStatementSql(t, stmt, expectedSQL, "Bong", "http://bong.com", "Bing")
testutils.AssertExec(t, stmt, db) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
requireLogged(t, stmt) testutils.AssertExec(t, stmt, tx)
}) requireLogged(t, stmt)
links := []model.Link{} var links []model.Link
err := Link. err := Link.
SELECT(Link.AllColumns). SELECT(Link.AllColumns).
WHERE(Link.Name.EQ(String("Bong"))). WHERE(Link.Name.EQ(String("Bong"))).
Query(db, &links) Query(tx, &links)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(links), 1) require.Equal(t, len(links), 1)
testutils.AssertDeepEqual(t, links[0], model.Link{ testutils.AssertDeepEqual(t, links[0], model.Link{
ID: 204, ID: 204,
URL: "http://bong.com", URL: "http://bong.com",
Name: "Bong", Name: "Bong",
})
})
}) })
} }
func TestUpdateWithSubQueries(t *testing.T) { func TestUpdateWithSubQueries(t *testing.T) {
setupLinkTableForUpdateTest(t)
expectedSQL := ` expectedSQL := `
UPDATE test_sample.link UPDATE test_sample.link
SET name = ?, SET name = ?,
@ -86,7 +103,7 @@ WHERE link.name = ?;
WHERE(Link.Name.EQ(String("Bing"))) WHERE(Link.Name.EQ(String("Bing")))
testutils.AssertStatementSql(t, query, expectedSQL, "Bong", "Youtube", "Bing") testutils.AssertStatementSql(t, query, expectedSQL, "Bong", "Youtube", "Bing")
testutils.AssertExec(t, query, db) testutils.AssertExecAndRollback(t, query, db)
requireLogged(t, query) requireLogged(t, query)
}) })
@ -104,14 +121,12 @@ WHERE link.name = ?;
WHERE(Link.Name.EQ(String("Bing"))) WHERE(Link.Name.EQ(String("Bing")))
testutils.AssertStatementSql(t, query, expectedSQL, "Bong", "Youtube", "Bing") testutils.AssertStatementSql(t, query, expectedSQL, "Bong", "Youtube", "Bing")
testutils.AssertExec(t, query, db) testutils.AssertExecAndRollback(t, query, db)
requireLogged(t, query) requireLogged(t, query)
}) })
} }
func TestUpdateWithModelData(t *testing.T) { func TestUpdateWithModelData(t *testing.T) {
setupLinkTableForUpdateTest(t)
link := model.Link{ link := model.Link{
ID: 201, ID: 201,
URL: "http://www.duckduckgo.com", URL: "http://www.duckduckgo.com",
@ -123,24 +138,20 @@ func TestUpdateWithModelData(t *testing.T) {
MODEL(link). MODEL(link).
WHERE(Link.ID.EQ(Int32(link.ID))) WHERE(Link.ID.EQ(Int32(link.ID)))
expectedSQL := ` testutils.AssertStatementSql(t, stmt, `
UPDATE test_sample.link UPDATE test_sample.link
SET id = ?, SET id = ?,
url = ?, url = ?,
name = ?, name = ?,
description = ? description = ?
WHERE link.id = ?; WHERE link.id = ?;
` `, int32(201), "http://www.duckduckgo.com", "DuckDuckGo", nil, int32(201))
testutils.AssertStatementSql(t, stmt, expectedSQL, int32(201), "http://www.duckduckgo.com", "DuckDuckGo", nil, int32(201))
testutils.AssertExec(t, stmt, db) testutils.AssertExecAndRollback(t, stmt, db)
requireLogged(t, stmt) requireLogged(t, stmt)
} }
func TestUpdateWithModelDataAndPredefinedColumnList(t *testing.T) { func TestUpdateWithModelDataAndPredefinedColumnList(t *testing.T) {
setupLinkTableForUpdateTest(t)
link := model.Link{ link := model.Link{
ID: 201, ID: 201,
URL: "http://www.duckduckgo.com", URL: "http://www.duckduckgo.com",
@ -154,23 +165,19 @@ func TestUpdateWithModelDataAndPredefinedColumnList(t *testing.T) {
MODEL(link). MODEL(link).
WHERE(Link.ID.EQ(Int32(link.ID))) WHERE(Link.ID.EQ(Int32(link.ID)))
var expectedSQL = ` testutils.AssertDebugStatementSql(t, stmt, `
UPDATE test_sample.link UPDATE test_sample.link
SET description = NULL, SET description = NULL,
name = 'DuckDuckGo', name = 'DuckDuckGo',
url = 'http://www.duckduckgo.com' url = 'http://www.duckduckgo.com'
WHERE link.id = 201; WHERE link.id = 201;
` `, nil, "DuckDuckGo", "http://www.duckduckgo.com", int32(201))
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, nil, "DuckDuckGo", "http://www.duckduckgo.com", int32(201)) testutils.AssertExecAndRollback(t, stmt, db)
testutils.AssertExec(t, stmt, db)
requireLogged(t, stmt) requireLogged(t, stmt)
} }
func TestUpdateWithModelDataAndMutableColumns(t *testing.T) { func TestUpdateWithModelDataAndMutableColumns(t *testing.T) {
setupLinkTableForUpdateTest(t)
link := model.Link{ link := model.Link{
ID: 201, ID: 201,
URL: "http://www.duckduckgo.com", URL: "http://www.duckduckgo.com",
@ -192,7 +199,7 @@ WHERE link.id = 201;
//fmt.Println(stmt.DebugSql()) //fmt.Println(stmt.DebugSql())
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, "http://www.duckduckgo.com", "DuckDuckGo", nil, int32(201)) testutils.AssertDebugStatementSql(t, stmt, expectedSQL, "http://www.duckduckgo.com", "DuckDuckGo", nil, int32(201))
testutils.AssertExec(t, stmt, db) testutils.AssertExecAndRollback(t, stmt, db)
} }
func TestUpdateWithInvalidModelData(t *testing.T) { func TestUpdateWithInvalidModelData(t *testing.T) {
@ -201,8 +208,6 @@ func TestUpdateWithInvalidModelData(t *testing.T) {
require.Equal(t, r, "missing struct field for column : id") require.Equal(t, r, "missing struct field for column : id")
}() }()
setupLinkTableForUpdateTest(t)
link := struct { link := struct {
Ident int Ident int
URL string URL string
@ -215,17 +220,13 @@ func TestUpdateWithInvalidModelData(t *testing.T) {
Name: "DuckDuckGo", Name: "DuckDuckGo",
} }
stmt := Link. _ = Link.
UPDATE(Link.AllColumns). UPDATE(Link.AllColumns).
MODEL(link). MODEL(link).
WHERE(Link.ID.EQ(Int(int64(link.Ident)))) WHERE(Link.ID.EQ(Int(int64(link.Ident))))
stmt.Sql()
} }
func TestUpdateQueryContext(t *testing.T) { func TestUpdateQueryContext(t *testing.T) {
setupLinkTableForUpdateTest(t)
updateStmt := Link. updateStmt := Link.
UPDATE(Link.Name, Link.URL). UPDATE(Link.Name, Link.URL).
SET("Bong", "http://bong.com"). SET("Bong", "http://bong.com").
@ -243,8 +244,6 @@ func TestUpdateQueryContext(t *testing.T) {
} }
func TestUpdateExecContext(t *testing.T) { func TestUpdateExecContext(t *testing.T) {
setupLinkTableForUpdateTest(t)
updateStmt := Link. updateStmt := Link.
UPDATE(Link.Name, Link.URL). UPDATE(Link.Name, Link.URL).
SET("Bong", "http://bong.com"). SET("Bong", "http://bong.com").
@ -261,9 +260,6 @@ func TestUpdateExecContext(t *testing.T) {
} }
func TestUpdateWithJoin(t *testing.T) { func TestUpdateWithJoin(t *testing.T) {
tx := beginTx(t)
defer tx.Rollback()
statement := table.Staff.INNER_JOIN(table.Address, table.Address.AddressID.EQ(table.Staff.AddressID)). statement := table.Staff.INNER_JOIN(table.Address, table.Address.AddressID.EQ(table.Staff.AddressID)).
UPDATE(table.Staff.LastName). UPDATE(table.Staff.LastName).
SET(String("New staff name")). SET(String("New staff name")).
@ -276,21 +272,5 @@ SET last_name = ?
WHERE staff.staff_id = ?; WHERE staff.staff_id = ?;
`, "New staff name", int64(1)) `, "New staff name", int64(1))
_, err := statement.Exec(tx) testutils.AssertExecAndRollback(t, statement, db)
require.NoError(t, err)
}
func setupLinkTableForUpdateTest(t *testing.T) {
cleanUpLinkTable(t)
_, err := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(200, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
VALUES(201, "http://www.ask.com", "Ask", DEFAULT).
VALUES(202, "http://www.ask.com", "Ask", DEFAULT).
VALUES(203, "http://www.yahoo.com", "Yahoo", DEFAULT).
VALUES(204, "http://www.bing.com", "Bing", DEFAULT).
Exec(db)
require.NoError(t, err)
} }

View file

@ -1,6 +1,7 @@
package postgres package postgres
import ( import (
"database/sql"
"testing" "testing"
"time" "time"
@ -17,10 +18,10 @@ import (
) )
func TestAllTypesSelect(t *testing.T) { func TestAllTypesSelect(t *testing.T) {
dest := []model.AllTypes{} var dest []model.AllTypes
err := AllTypes.SELECT( err := AllTypes.SELECT(
AllTypes.AllColumns, AllTypesAllColumns,
).LIMIT(2). ).LIMIT(2).
Query(db, &dest) Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
@ -32,7 +33,7 @@ func TestAllTypesSelect(t *testing.T) {
func TestAllTypesViewSelect(t *testing.T) { func TestAllTypesViewSelect(t *testing.T) {
type AllTypesView model.AllTypes type AllTypesView model.AllTypes
dest := []AllTypesView{} var dest []AllTypesView
err := view.AllTypesView.SELECT(view.AllTypesView.AllColumns).Query(db, &dest) err := view.AllTypesView.SELECT(view.AllTypesView.AllColumns).Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
@ -44,40 +45,123 @@ func TestAllTypesViewSelect(t *testing.T) {
func TestAllTypesInsertModel(t *testing.T) { func TestAllTypesInsertModel(t *testing.T) {
skipForPgxDriver(t) // pgx driver bug ERROR: date/time field value out of range: "0000-01-01 12:05:06Z" (SQLSTATE 22008) skipForPgxDriver(t) // pgx driver bug ERROR: date/time field value out of range: "0000-01-01 12:05:06Z" (SQLSTATE 22008)
query := AllTypes.INSERT(AllTypes.AllColumns). query := AllTypes.INSERT(AllTypesAllColumns).
MODEL(allTypesRow0). MODEL(allTypesRow0).
MODEL(&allTypesRow1). MODEL(&allTypesRow1).
RETURNING(AllTypes.AllColumns) RETURNING(AllTypes.AllColumns)
dest := []model.AllTypes{} testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
err := query.Query(db, &dest) var dest []model.AllTypes
require.NoError(t, err) err := query.Query(tx, &dest)
require.NoError(t, err)
require.Equal(t, len(dest), 2) if sourceIsCockroachDB() {
testutils.AssertDeepEqual(t, dest[0], allTypesRow0) return
testutils.AssertDeepEqual(t, dest[1], allTypesRow1) }
require.Equal(t, len(dest), 2)
testutils.AssertDeepEqual(t, dest[0], allTypesRow0)
testutils.AssertDeepEqual(t, dest[1], allTypesRow1)
})
} }
var AllTypesAllColumns = AllTypes.AllColumns.Except(IntegerColumn("rowid"))
func TestAllTypesInsertQuery(t *testing.T) { func TestAllTypesInsertQuery(t *testing.T) {
query := AllTypes.INSERT(AllTypes.AllColumns). query := AllTypes.INSERT(AllTypesAllColumns).
QUERY( QUERY(
AllTypes. AllTypes.
SELECT(AllTypes.AllColumns). SELECT(AllTypesAllColumns).
LIMIT(2), LIMIT(2),
). ).
RETURNING(AllTypes.AllColumns) RETURNING(AllTypesAllColumns)
dest := []model.AllTypes{} testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
err := query.Query(db, &dest) var dest []model.AllTypes
err := query.Query(tx, &dest)
require.NoError(t, err)
require.Equal(t, len(dest), 2)
testutils.AssertDeepEqual(t, dest[0], allTypesRow0)
testutils.AssertDeepEqual(t, dest[1], allTypesRow1)
})
}
func TestUUIDType(t *testing.T) {
id := uuid.MustParse("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")
query := AllTypes.
SELECT(AllTypes.UUID, AllTypes.UUIDPtr).
WHERE(AllTypes.UUID.EQ(UUID(id)))
testutils.AssertDebugStatementSql(t, query, `
SELECT all_types.uuid AS "all_types.uuid",
all_types.uuid_ptr AS "all_types.uuid_ptr"
FROM test_sample.all_types
WHERE all_types.uuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
`, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")
result := model.AllTypes{}
err := query.Query(db, &result)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(dest), 2) require.Equal(t, result.UUID, uuid.MustParse("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"))
testutils.AssertDeepEqual(t, dest[0], allTypesRow0) testutils.AssertDeepEqual(t, result.UUIDPtr, testutils.UUIDPtr("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"))
testutils.AssertDeepEqual(t, dest[1], allTypesRow1) requireLogged(t, query)
}
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!")
} }
func TestAllTypesFromSubQuery(t *testing.T) { func TestAllTypesFromSubQuery(t *testing.T) {
subQuery := SELECT(AllTypes.AllColumns). subQuery := SELECT(AllTypesAllColumns).
FROM(AllTypes). FROM(AllTypes).
AsTable("allTypesSubQuery") AsTable("allTypesSubQuery")
@ -214,7 +298,7 @@ FROM (
LIMIT 2; LIMIT 2;
`) `)
dest := []model.AllTypes{} var dest []model.AllTypes
err := mainQuery.Query(db, &dest) err := mainQuery.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
@ -298,7 +382,6 @@ LIMIT $11;
} }
func TestExpressionCast(t *testing.T) { func TestExpressionCast(t *testing.T) {
skipForPgxDriver(t) // pgx driver bug 'cannot convert 151 to Text' skipForPgxDriver(t) // pgx driver bug 'cannot convert 151 to Text'
query := AllTypes.SELECT( query := AllTypes.SELECT(
@ -315,19 +398,28 @@ func TestExpressionCast(t *testing.T) {
CAST(Int(234)).AS_TEXT(), CAST(Int(234)).AS_TEXT(),
CAST(String("1/8/1999")).AS_DATE(), CAST(String("1/8/1999")).AS_DATE(),
CAST(String("04:05:06.789")).AS_TIME(), CAST(String("04:05:06.789")).AS_TIME(),
CAST(String("04:05:06 PST")).AS_TIMEZ(), CAST(String("04:05:06+01:00")).AS_TIMEZ(),
CAST(String("1999-01-08 04:05:06")).AS_TIMESTAMP(), CAST(String("1999-01-08 04:05:06")).AS_TIMESTAMP(),
CAST(String("January 8 04:05:06 1999 PST")).AS_TIMESTAMPZ(), CAST(String("1999-01-08 04:05:06+01:00")).AS_TIMESTAMPZ(),
CAST(String("04:05:06")).AS_INTERVAL(), CAST(String("04:05:06")).AS_INTERVAL(),
TO_CHAR(AllTypes.Timestamp, String("HH12:MI:SS")), func() ProjectionList {
TO_CHAR(AllTypes.Integer, String("999")), if sourceIsCockroachDB() {
TO_CHAR(AllTypes.DoublePrecision, String("999D9")), return ProjectionList{NULL}
TO_CHAR(AllTypes.Numeric, String("999D99S")), }
TO_DATE(String("05 Dec 2000"), String("DD Mon YYYY")), // cockroach doesn't support currently
TO_NUMBER(String("12,454"), String("99G999D9S")), return ProjectionList{
TO_TIMESTAMP(String("05 Dec 2000"), String("DD Mon YYYY")), TO_CHAR(AllTypes.Timestamp, String("HH12:MI:SS")),
TO_CHAR(AllTypes.Integer, String("999")),
TO_CHAR(AllTypes.DoublePrecision, String("999D9")),
TO_CHAR(AllTypes.Numeric, String("999D99S")),
TO_DATE(String("05 Dec 2000"), String("DD Mon YYYY")),
TO_NUMBER(String("12,454"), String("99G999D9S")),
TO_TIMESTAMP(String("05 Dec 2000"), String("DD Mon YYYY")),
}
}(),
COALESCE(AllTypes.IntegerPtr, AllTypes.SmallIntPtr, NULL, Int(11)), COALESCE(AllTypes.IntegerPtr, AllTypes.SmallIntPtr, NULL, Int(11)),
NULLIF(AllTypes.Text, String("(none)")), NULLIF(AllTypes.Text, String("(none)")),
@ -337,16 +429,15 @@ func TestExpressionCast(t *testing.T) {
Raw("current_database()"), Raw("current_database()"),
) )
//fmt.Println(query.DebugSql()) var dest []struct{}
dest := []struct{}{}
err := query.Query(db, &dest) err := query.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
} }
func TestStringOperators(t *testing.T) { func TestStringOperators(t *testing.T) {
skipForPgxDriver(t) // pgx driver bug 'cannot convert 11 to Text' skipForCockroachDB(t) // some string functions are still unimplemented
skipForPgxDriver(t) // pgx driver bug 'cannot convert 11 to Text'
query := AllTypes.SELECT( query := AllTypes.SELECT(
AllTypes.Text.EQ(AllTypes.Char), AllTypes.Text.EQ(AllTypes.Char),
@ -395,18 +486,18 @@ func TestStringOperators(t *testing.T) {
CONCAT(AllTypes.VarCharPtr, AllTypes.VarCharPtr, String("aaa"), Int(1)), CONCAT(AllTypes.VarCharPtr, AllTypes.VarCharPtr, String("aaa"), Int(1)),
CONCAT(Bool(false), Int(1), Float(22.2), String("test test")), CONCAT(Bool(false), Int(1), Float(22.2), String("test test")),
CONCAT_WS(String("string1"), Int(1), Float(11.22), String("bytea"), Bool(false)), //Float(11.12)), CONCAT_WS(String("string1"), Int(1), Float(11.22), String("bytea"), Bool(false)), //Float(11.12)),
CONVERT(String("bytea"), String("UTF8"), String("LATIN1")), CONVERT(Bytea("bytea"), String("UTF8"), String("LATIN1")),
CONVERT(AllTypes.Bytea, String("UTF8"), String("LATIN1")), CONVERT(AllTypes.Bytea, String("UTF8"), String("LATIN1")),
CONVERT_FROM(String("text_in_utf8"), String("UTF8")), CONVERT_FROM(Bytea("text_in_utf8"), String("UTF8")),
CONVERT_TO(String("text_in_utf8"), String("UTF8")), CONVERT_TO(String("text_in_utf8"), String("UTF8")),
ENCODE(String("123\000\001"), String("base64")), ENCODE(Bytea("123\000\001"), String("base64")),
DECODE(String("MTIzAAE="), String("base64")), DECODE(String("MTIzAAE="), String("base64")),
FORMAT(String("Hello %s, %1$s"), String("World")), FORMAT(String("Hello %s, %1$s"), String("World")),
INITCAP(String("hi THOMAS")), INITCAP(String("hi THOMAS")),
LEFT(String("abcde"), Int(2)), LEFT(String("abcde"), Int(2)),
RIGHT(String("abcde"), Int(2)), RIGHT(String("abcde"), Int(2)),
LENGTH(String("jose")), LENGTH(Bytea("jose")),
LENGTH(String("jose"), String("UTF8")), LENGTH(Bytea("jose"), String("UTF8")),
LPAD(String("Hi"), Int(5)), LPAD(String("Hi"), Int(5)),
LPAD(String("Hi"), Int(5), String("xy")), LPAD(String("Hi"), Int(5), String("xy")),
RPAD(String("Hi"), Int(5)), RPAD(String("Hi"), Int(5)),
@ -421,8 +512,6 @@ func TestStringOperators(t *testing.T) {
TO_HEX(AllTypes.IntegerPtr), TO_HEX(AllTypes.IntegerPtr),
) )
//fmt.Println(query.DebugSql())
dest := []struct{}{} dest := []struct{}{}
err := query.Query(db, &dest) err := query.Query(db, &dest)
@ -501,6 +590,8 @@ LIMIT $5;
} }
func TestFloatOperators(t *testing.T) { func TestFloatOperators(t *testing.T) {
skipForCockroachDB(t) // some functions are still unimplemented
query := AllTypes.SELECT( query := AllTypes.SELECT(
AllTypes.Numeric.EQ(AllTypes.Numeric).AS("eq1"), AllTypes.Numeric.EQ(AllTypes.Numeric).AS("eq1"),
AllTypes.Decimal.EQ(Float(12.22)).AS("eq2"), AllTypes.Decimal.EQ(Float(12.22)).AS("eq2"),
@ -604,6 +695,8 @@ LIMIT $38;
} }
func TestIntegerOperators(t *testing.T) { func TestIntegerOperators(t *testing.T) {
skipForCockroachDB(t) // some functions are still unimplemented
query := AllTypes.SELECT( query := AllTypes.SELECT(
AllTypes.BigInt, AllTypes.BigInt,
AllTypes.BigIntPtr, AllTypes.BigIntPtr,
@ -733,6 +826,8 @@ LIMIT $27;
} }
func TestTimeExpression(t *testing.T) { func TestTimeExpression(t *testing.T) {
skipForCockroachDB(t)
query := AllTypes.SELECT( query := AllTypes.SELECT(
AllTypes.Time.EQ(AllTypes.Time), AllTypes.Time.EQ(AllTypes.Time),
AllTypes.Time.EQ(Time(23, 6, 6, 1)), AllTypes.Time.EQ(Time(23, 6, 6, 1)),
@ -804,15 +899,17 @@ func TestTimeExpression(t *testing.T) {
NOW(), NOW(),
) )
//fmt.Println(query.DebugSql()) // fmt.Println(query.DebugSql())
dest := []struct{}{} var dest []struct{}
err := query.Query(db, &dest) err := query.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
} }
func TestInterval(t *testing.T) { func TestInterval(t *testing.T) {
skipForCockroachDB(t)
stmt := SELECT( stmt := SELECT(
INTERVAL(1, YEAR), INTERVAL(1, YEAR),
INTERVAL(1, MONTH), INTERVAL(1, MONTH),
@ -866,6 +963,66 @@ func TestInterval(t *testing.T) {
requireLogged(t, stmt) requireLogged(t, stmt)
} }
func TestTimeEXTRACT(t *testing.T) {
stmt := SELECT(
EXTRACT(CENTURY, AllTypes.Timestampz),
EXTRACT(DAY, AllTypes.Timestamp),
EXTRACT(DECADE, AllTypes.Date),
EXTRACT(DOW, AllTypes.TimestampzPtr),
EXTRACT(DOY, DateT(time.Now())),
EXTRACT(EPOCH, TimestampT(time.Now())),
EXTRACT(HOUR, AllTypes.Time.ADD(INTERVAL(1, HOUR))),
EXTRACT(ISODOW, AllTypes.Timestampz),
EXTRACT(ISOYEAR, AllTypes.Timestampz),
EXTRACT(JULIAN, AllTypes.Timestampz).EQ(Float(3456.123)),
EXTRACT(MICROSECOND, AllTypes.Timestampz),
EXTRACT(MILLENNIUM, AllTypes.Timestampz),
EXTRACT(MILLISECOND, AllTypes.Timez),
EXTRACT(MINUTE, INTERVAL(1, HOUR, 2, MINUTE)),
EXTRACT(MONTH, AllTypes.Timestampz),
EXTRACT(QUARTER, AllTypes.Timestampz),
EXTRACT(SECOND, AllTypes.Timestampz),
EXTRACT(TIMEZONE, AllTypes.Timestampz),
EXTRACT(TIMEZONE_HOUR, AllTypes.Timestampz),
EXTRACT(TIMEZONE_MINUTE, AllTypes.Timestampz),
EXTRACT(WEEK, AllTypes.Timestampz),
EXTRACT(YEAR, AllTypes.Timestampz),
).FROM(
AllTypes,
)
// fmt.Println(stmt.Sql())
testutils.AssertStatementSql(t, stmt, `
SELECT EXTRACT(CENTURY FROM all_types.timestampz),
EXTRACT(DAY FROM all_types.timestamp),
EXTRACT(DECADE FROM all_types.date),
EXTRACT(DOW FROM all_types.timestampz_ptr),
EXTRACT(DOY FROM $1::date),
EXTRACT(EPOCH FROM $2::timestamp without time zone),
EXTRACT(HOUR FROM all_types.time + INTERVAL '1 HOUR'),
EXTRACT(ISODOW FROM all_types.timestampz),
EXTRACT(ISOYEAR FROM all_types.timestampz),
EXTRACT(JULIAN FROM all_types.timestampz) = $3,
EXTRACT(MICROSECOND FROM all_types.timestampz),
EXTRACT(MILLENNIUM FROM all_types.timestampz),
EXTRACT(MILLISECOND FROM all_types.timez),
EXTRACT(MINUTE FROM INTERVAL '1 HOUR 2 MINUTE'),
EXTRACT(MONTH FROM all_types.timestampz),
EXTRACT(QUARTER FROM all_types.timestampz),
EXTRACT(SECOND FROM all_types.timestampz),
EXTRACT(TIMEZONE FROM all_types.timestampz),
EXTRACT(TIMEZONE_HOUR FROM all_types.timestampz),
EXTRACT(TIMEZONE_MINUTE FROM all_types.timestampz),
EXTRACT(WEEK FROM all_types.timestampz),
EXTRACT(YEAR FROM all_types.timestampz)
FROM test_sample.all_types;
`)
err := stmt.Query(db, &struct{}{})
require.NoError(t, err)
}
func TestSubQueryColumnReference(t *testing.T) { func TestSubQueryColumnReference(t *testing.T) {
type expected struct { type expected struct {
sql string sql string
@ -1084,6 +1241,10 @@ LIMIT $6;
dest.Timez = dest.Timez.UTC() dest.Timez = dest.Timez.UTC()
dest.Timestampz = dest.Timestampz.UTC() dest.Timestampz = dest.Timestampz.UTC()
if sourceIsCockroachDB() {
return // rounding differences
}
testutils.AssertJSON(t, dest, ` testutils.AssertJSON(t, dest, `
{ {
"Date": "2009-11-17T00:00:00Z", "Date": "2009-11-17T00:00:00Z",

View file

@ -6,6 +6,7 @@ import (
. "github.com/go-jet/jet/v2/postgres" . "github.com/go-jet/jet/v2/postgres"
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/chinook/model" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/chinook/model"
. "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/chinook/table" . "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/chinook/table"
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/chinook2/table"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"testing" "testing"
"time" "time"
@ -188,34 +189,36 @@ func TestJoinEverything(t *testing.T) {
manager := Employee.AS("Manager") manager := Employee.AS("Manager")
stmt := Artist. stmt := SELECT(
LEFT_JOIN(Album, Artist.ArtistId.EQ(Album.ArtistId)). Artist.AllColumns,
LEFT_JOIN(Track, Track.AlbumId.EQ(Album.AlbumId)). Album.AllColumns,
LEFT_JOIN(Genre, Genre.GenreId.EQ(Track.GenreId)). Track.AllColumns,
LEFT_JOIN(MediaType, MediaType.MediaTypeId.EQ(Track.MediaTypeId)). Genre.AllColumns,
LEFT_JOIN(PlaylistTrack, PlaylistTrack.TrackId.EQ(Track.TrackId)). MediaType.AllColumns,
LEFT_JOIN(Playlist, Playlist.PlaylistId.EQ(PlaylistTrack.PlaylistId)). PlaylistTrack.AllColumns,
LEFT_JOIN(InvoiceLine, InvoiceLine.TrackId.EQ(Track.TrackId)). Playlist.AllColumns,
LEFT_JOIN(Invoice, Invoice.InvoiceId.EQ(InvoiceLine.InvoiceId)). Invoice.AllColumns,
LEFT_JOIN(Customer, Customer.CustomerId.EQ(Invoice.CustomerId)). Customer.AllColumns,
LEFT_JOIN(Employee, Employee.EmployeeId.EQ(Customer.SupportRepId)). Employee.AllColumns,
LEFT_JOIN(manager, manager.EmployeeId.EQ(Employee.ReportsTo)). manager.AllColumns,
SELECT( ).FROM(
Artist.AllColumns, Artist.
Album.AllColumns, LEFT_JOIN(Album, Artist.ArtistId.EQ(Album.ArtistId)).
Track.AllColumns, LEFT_JOIN(Track, Track.AlbumId.EQ(Album.AlbumId)).
Genre.AllColumns, LEFT_JOIN(Genre, Genre.GenreId.EQ(Track.GenreId)).
MediaType.AllColumns, LEFT_JOIN(MediaType, MediaType.MediaTypeId.EQ(Track.MediaTypeId)).
PlaylistTrack.AllColumns, LEFT_JOIN(PlaylistTrack, PlaylistTrack.TrackId.EQ(Track.TrackId)).
Playlist.AllColumns, LEFT_JOIN(Playlist, Playlist.PlaylistId.EQ(PlaylistTrack.PlaylistId)).
Invoice.AllColumns, LEFT_JOIN(InvoiceLine, InvoiceLine.TrackId.EQ(Track.TrackId)).
Customer.AllColumns, LEFT_JOIN(Invoice, Invoice.InvoiceId.EQ(InvoiceLine.InvoiceId)).
Employee.AllColumns, LEFT_JOIN(Customer, Customer.CustomerId.EQ(Invoice.CustomerId)).
manager.AllColumns, LEFT_JOIN(Employee, Employee.EmployeeId.EQ(Customer.SupportRepId)).
). LEFT_JOIN(manager, manager.EmployeeId.EQ(Employee.ReportsTo)),
ORDER_BY(Artist.ArtistId, Album.AlbumId, Track.TrackId, ).ORDER_BY(
Genre.GenreId, MediaType.MediaTypeId, Playlist.PlaylistId, Artist.ArtistId, Album.AlbumId, Track.TrackId,
Invoice.InvoiceId, Customer.CustomerId) Genre.GenreId, MediaType.MediaTypeId, Playlist.PlaylistId,
Invoice.InvoiceId, Customer.CustomerId,
)
var dest []struct { //list of all artist var dest []struct { //list of all artist
model.Artist model.Artist
@ -398,11 +401,11 @@ FROM (
SELECT "subQuery1"."Artist.ArtistId" AS "Artist.ArtistId", SELECT "subQuery1"."Artist.ArtistId" AS "Artist.ArtistId",
"subQuery1"."Artist.Name" AS "Artist.Name", "subQuery1"."Artist.Name" AS "Artist.Name",
"subQuery1".custom_column_1 AS "custom_column_1", "subQuery1".custom_column_1 AS "custom_column_1",
$1 AS "custom_column_2" $1::text AS "custom_column_2"
FROM ( FROM (
SELECT "Artist"."ArtistId" AS "Artist.ArtistId", SELECT "Artist"."ArtistId" AS "Artist.ArtistId",
"Artist"."Name" AS "Artist.Name", "Artist"."Name" AS "Artist.Name",
$2 AS "custom_column_1" $2::text AS "custom_column_1"
FROM chinook."Artist" FROM chinook."Artist"
ORDER BY "Artist"."ArtistId" ASC ORDER BY "Artist"."ArtistId" ASC
) AS "subQuery1" ) AS "subQuery1"
@ -721,11 +724,14 @@ ORDER BY "Album.AlbumId";
} }
func TestQueryWithContext(t *testing.T) { func TestQueryWithContext(t *testing.T) {
if sourceIsCockroachDB() && !isPgxDriver() {
return // context cancellation doesn't work for pq driver
}
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel() defer cancel()
dest := []model.Album{} var dest []model.Album
err := Album. err := Album.
CROSS_JOIN(Track). CROSS_JOIN(Track).
@ -737,6 +743,9 @@ func TestQueryWithContext(t *testing.T) {
} }
func TestExecWithContext(t *testing.T) { func TestExecWithContext(t *testing.T) {
if sourceIsCockroachDB() && !isPgxDriver() {
return // context cancellation doesn't work for pq driver
}
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel() defer cancel()
@ -807,7 +816,7 @@ ORDER BY "first10Artist"."Artist.ArtistId";
require.NoError(t, err) require.NoError(t, err)
} }
func Test_SchemaRename(t *testing.T) { func TestMultiTenantDifferentSchema(t *testing.T) {
Artist2 := Artist.FromSchema("chinook2") Artist2 := Artist.FromSchema("chinook2")
Album2 := Album.FromSchema("chinook2") Album2 := Album.FromSchema("chinook2")
@ -828,10 +837,12 @@ func Test_SchemaRename(t *testing.T) {
albumArtistID := Album2.ArtistId.From(first10Albums) albumArtistID := Album2.ArtistId.From(first10Albums)
stmt := SELECT(first10Artist.AllColumns(), first10Albums.AllColumns()). stmt := SELECT(
FROM(first10Artist. first10Artist.AllColumns(),
INNER_JOIN(first10Albums, artistID.EQ(albumArtistID))). first10Albums.AllColumns(),
ORDER_BY(artistID) ).FROM(first10Artist.
INNER_JOIN(first10Albums, artistID.EQ(albumArtistID)),
).ORDER_BY(artistID)
testutils.AssertDebugStatementSql(t, stmt, ` testutils.AssertDebugStatementSql(t, stmt, `
SELECT "first10Artist"."Artist.ArtistId" AS "Artist.ArtistId", SELECT "first10Artist"."Artist.ArtistId" AS "Artist.ArtistId",
@ -872,6 +883,182 @@ ORDER BY "first10Artist"."Artist.ArtistId";
require.Equal(t, dest[0].Album[0].Title, "Plays Metallica By Four Cellos") require.Equal(t, dest[0].Album[0].Title, "Plays Metallica By Four Cellos")
} }
func TestMultiTenantSameSchemaDifferentTablePrefix(t *testing.T) {
var selectAlbumsFrom = func(tenant string) SelectStatement {
Album := table.Album.WithPrefix(tenant)
return SELECT(
Album.AllColumns,
).FROM(
Album,
).ORDER_BY(
Album.AlbumId.ASC(),
).LIMIT(3)
}
t.Run("tenant1", func(t *testing.T) {
stmt := selectAlbumsFrom("tenant1.")
testutils.AssertStatementSql(t, stmt, `
SELECT "Album"."AlbumId" AS "Album.AlbumId",
"Album"."Title" AS "Album.Title",
"Album"."ArtistId" AS "Album.ArtistId"
FROM chinook2."tenant1.Album" AS "Album"
ORDER BY "Album"."AlbumId" ASC
LIMIT $1;
`)
var albums []model.Album
err := stmt.Query(db, &albums)
require.NoError(t, err)
testutils.AssertJSON(t, albums, `
[
{
"AlbumId": 80,
"Title": "In Your Honor [Disc 2]",
"ArtistId": 84
},
{
"AlbumId": 81,
"Title": "One By One",
"ArtistId": 84
},
{
"AlbumId": 82,
"Title": "The Colour And The Shape",
"ArtistId": 84
}
]
`)
})
t.Run("tenant2", func(t *testing.T) {
stmt := selectAlbumsFrom("tenant2.")
testutils.AssertStatementSql(t, stmt, `
SELECT "Album"."AlbumId" AS "Album.AlbumId",
"Album"."Title" AS "Album.Title",
"Album"."ArtistId" AS "Album.ArtistId"
FROM chinook2."tenant2.Album" AS "Album"
ORDER BY "Album"."AlbumId" ASC
LIMIT $1;
`)
var albums []model.Album
err := stmt.Query(db, &albums)
require.NoError(t, err)
testutils.AssertJSON(t, albums, `
[
{
"AlbumId": 152,
"Title": "Master Of Puppets",
"ArtistId": 50
},
{
"AlbumId": 153,
"Title": "ReLoad",
"ArtistId": 50
},
{
"AlbumId": 154,
"Title": "Ride The Lightning",
"ArtistId": 50
}
]
`)
})
}
func TestMultiTenantSameSchemaDifferentTableSuffix(t *testing.T) {
var selectAlbumsFrom = func(tenant string) SelectStatement {
Album := table.Album.WithSuffix(tenant)
return SELECT(
Album.AllColumns,
).FROM(
Album,
).ORDER_BY(
Album.AlbumId.ASC(),
).LIMIT(3)
}
t.Run("tenant1", func(t *testing.T) {
stmt := selectAlbumsFrom(".tenant1")
testutils.AssertStatementSql(t, stmt, `
SELECT "Album"."AlbumId" AS "Album.AlbumId",
"Album"."Title" AS "Album.Title",
"Album"."ArtistId" AS "Album.ArtistId"
FROM chinook2."Album.tenant1" AS "Album"
ORDER BY "Album"."AlbumId" ASC
LIMIT $1;
`)
var albums []model.Album
err := stmt.Query(db, &albums)
require.NoError(t, err)
testutils.AssertJSON(t, albums, `
[
{
"AlbumId": 80,
"Title": "In Your Honor [Disc 2]",
"ArtistId": 84
},
{
"AlbumId": 81,
"Title": "One By One",
"ArtistId": 84
},
{
"AlbumId": 82,
"Title": "The Colour And The Shape",
"ArtistId": 84
}
]
`)
})
t.Run("tenant2", func(t *testing.T) {
stmt := selectAlbumsFrom(".tenant2")
testutils.AssertStatementSql(t, stmt, `
SELECT "Album"."AlbumId" AS "Album.AlbumId",
"Album"."Title" AS "Album.Title",
"Album"."ArtistId" AS "Album.ArtistId"
FROM chinook2."Album.tenant2" AS "Album"
ORDER BY "Album"."AlbumId" ASC
LIMIT $1;
`)
var albums []model.Album
err := stmt.Query(db, &albums)
require.NoError(t, err)
testutils.AssertJSON(t, albums, `
[
{
"AlbumId": 152,
"Title": "Master Of Puppets",
"ArtistId": 50
},
{
"AlbumId": 153,
"Title": "ReLoad",
"ArtistId": 50
},
{
"AlbumId": 154,
"Title": "Ride The Lightning",
"ArtistId": 50
}
]
`)
})
}
var album1 = model.Album{ var album1 = model.Album{
AlbumId: 1, AlbumId: 1,
Title: "For Those About To Rock We Salute You", Title: "For Those About To Rock We Salute You",
@ -891,6 +1078,8 @@ var album347 = model.Album{
} }
func TestAggregateFunc(t *testing.T) { func TestAggregateFunc(t *testing.T) {
skipForCockroachDB(t)
stmt := SELECT( stmt := SELECT(
PERCENTILE_DISC(Float(0.1)).WITHIN_GROUP_ORDER_BY(Invoice.InvoiceId).AS("percentile_disc_1"), PERCENTILE_DISC(Float(0.1)).WITHIN_GROUP_ORDER_BY(Invoice.InvoiceId).AS("percentile_disc_1"),
PERCENTILE_DISC(Invoice.Total.DIV(Float(100))).WITHIN_GROUP_ORDER_BY(Invoice.InvoiceDate.ASC()).AS("percentile_disc_2"), PERCENTILE_DISC(Invoice.Total.DIV(Float(100))).WITHIN_GROUP_ORDER_BY(Invoice.InvoiceDate.ASC()).AS("percentile_disc_2"),

View file

@ -2,6 +2,7 @@ package postgres
import ( import (
"context" "context"
"database/sql"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
. "github.com/go-jet/jet/v2/postgres" . "github.com/go-jet/jet/v2/postgres"
model2 "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model" model2 "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model"
@ -14,69 +15,49 @@ import (
) )
func TestDeleteWithWhere(t *testing.T) { func TestDeleteWithWhere(t *testing.T) {
initForDeleteTest(t)
var expectedSQL = `
DELETE FROM test_sample.link
WHERE link.name IN ('Gmail', 'Outlook');
`
deleteStmt := Link. deleteStmt := Link.
DELETE(). DELETE().
WHERE(Link.Name.IN(String("Gmail"), String("Outlook"))) WHERE(Link.Name.IN(String("Gmail"), String("Outlook")))
testutils.AssertDebugStatementSql(t, deleteStmt, expectedSQL, "Gmail", "Outlook") testutils.AssertDebugStatementSql(t, deleteStmt, `
DELETE FROM test_sample.link
WHERE link.name IN ('Gmail'::text, 'Outlook'::text);
`, "Gmail", "Outlook")
res, err := deleteStmt.ExecContext(context.Background(), db) testutils.AssertExecAndRollback(t, deleteStmt, db, 2)
require.NoError(t, err)
rows, err := res.RowsAffected()
require.NoError(t, err)
require.Equal(t, rows, int64(2))
requireQueryLogged(t, deleteStmt, int64(2)) requireQueryLogged(t, deleteStmt, int64(2))
} }
func TestDeleteWithWhereAndReturning(t *testing.T) { func TestDeleteWithWhereAndReturning(t *testing.T) {
initForDeleteTest(t)
var expectedSQL = `
DELETE FROM test_sample.link
WHERE link.name IN ('Gmail', 'Outlook')
RETURNING link.id AS "link.id",
link.url AS "link.url",
link.name AS "link.name",
link.description AS "link.description";
`
deleteStmt := Link. deleteStmt := Link.
DELETE(). DELETE().
WHERE(Link.Name.IN(String("Gmail"), String("Outlook"))). WHERE(Link.Name.IN(String("Gmail"), String("Outlook"))).
RETURNING(Link.AllColumns) RETURNING(Link.AllColumns)
testutils.AssertDebugStatementSql(t, deleteStmt, expectedSQL, "Gmail", "Outlook") testutils.AssertDebugStatementSql(t, deleteStmt, `
DELETE FROM test_sample.link
WHERE link.name IN ('Gmail'::text, 'Outlook'::text)
RETURNING link.id AS "link.id",
link.url AS "link.url",
link.name AS "link.name",
link.description AS "link.description";
`, "Gmail", "Outlook")
dest := []model.Link{} testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
var dest []model.Link
err := deleteStmt.Query(db, &dest) err := deleteStmt.Query(tx, &dest)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(dest), 2) require.Equal(t, len(dest), 2)
testutils.AssertDeepEqual(t, dest[0].Name, "Gmail") testutils.AssertDeepEqual(t, dest[0].Name, "Gmail")
testutils.AssertDeepEqual(t, dest[1].Name, "Outlook") testutils.AssertDeepEqual(t, dest[1].Name, "Outlook")
requireLogged(t, deleteStmt) requireLogged(t, deleteStmt)
} })
func initForDeleteTest(t *testing.T) {
cleanUpLinkTable(t)
stmt := Link.INSERT(Link.URL, Link.Name, Link.Description).
VALUES("www.gmail.com", "Gmail", "Email service developed by Google").
VALUES("www.outlook.live.com", "Outlook", "Email service developed by Microsoft")
AssertExec(t, stmt, 2)
} }
func TestDeleteQueryContext(t *testing.T) { func TestDeleteQueryContext(t *testing.T) {
initForDeleteTest(t)
deleteStmt := Link. deleteStmt := Link.
DELETE(). DELETE().
WHERE(Link.Name.IN(String("Gmail"), String("Outlook"))) WHERE(Link.Name.IN(String("Gmail"), String("Outlook")))
@ -86,16 +67,16 @@ func TestDeleteQueryContext(t *testing.T) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
dest := []model.Link{} testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
err := deleteStmt.QueryContext(ctx, db, &dest) dest := []model.Link{}
err := deleteStmt.QueryContext(ctx, tx, &dest)
require.Error(t, err, "context deadline exceeded") require.Error(t, err, "context deadline exceeded")
requireLogged(t, deleteStmt) requireLogged(t, deleteStmt)
})
} }
func TestDeleteExecContext(t *testing.T) { func TestDeleteExecContext(t *testing.T) {
initForDeleteTest(t)
list := []Expression{String("Gmail"), String("Outlook")} list := []Expression{String("Gmail"), String("Outlook")}
deleteStmt := Link. deleteStmt := Link.
@ -107,15 +88,16 @@ func TestDeleteExecContext(t *testing.T) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
_, err := deleteStmt.ExecContext(ctx, db) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
_, err := deleteStmt.ExecContext(ctx, tx)
require.Error(t, err, "context deadline exceeded") require.Error(t, err, "context deadline exceeded")
requireLogged(t, deleteStmt) requireLogged(t, deleteStmt)
})
} }
func TestDeleteFrom(t *testing.T) { func TestDeleteFrom(t *testing.T) {
tx := beginTx(t) skipForCockroachDB(t) // USING is not supported
defer tx.Rollback()
stmt := table.Rental.DELETE(). stmt := table.Rental.DELETE().
USING( USING(
@ -158,16 +140,17 @@ RETURNING rental.rental_id AS "rental.rental_id",
store.last_update AS "store.last_update"; store.last_update AS "store.last_update";
`) `)
var dest []struct { testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
Rental model2.Rental var dest []struct {
Store model2.Store Rental model2.Rental
} Store model2.Store
}
err := stmt.Query(tx, &dest) err := stmt.Query(tx, &dest)
require.NoError(t, err) require.NoError(t, err)
require.Len(t, dest, 3) require.Len(t, dest, 3)
testutils.AssertJSON(t, dest[0], ` testutils.AssertJSON(t, dest[0], `
{ {
"Rental": { "Rental": {
"RentalID": 4, "RentalID": 4,
@ -186,4 +169,5 @@ RETURNING rental.rental_id AS "rental.rental_id",
} }
} }
`) `)
})
} }

View file

@ -93,56 +93,88 @@ func TestCmdGenerator(t *testing.T) {
} }
func TestGeneratorIgnoreTables(t *testing.T) { func TestGeneratorIgnoreTables(t *testing.T) {
err := os.RemoveAll(genTestDir2) tests := []struct {
require.NoError(t, err) name string
args []string
}{
{
name: "with dsn",
args: []string{
"-dsn=" + fmt.Sprintf("postgresql://%s:%s@%s:%d/%s?sslmode=disable",
dbconfig.PgUser,
dbconfig.PgPassword,
dbconfig.PgHost,
dbconfig.PgPort,
"jetdb",
),
"-schema=dvds",
"-ignore-tables=actor,ADDRESS,country, Film , cITY,",
"-ignore-views=Actor_info, FILM_LIST ,staff_list",
"-ignore-enums=mpaa_rating",
"-path=" + genTestDir2,
},
},
{
name: "without dsn",
args: []string{
"-source=PostgreSQL",
"-host=localhost",
"-port=" + strconv.Itoa(dbconfig.PgPort),
"-user=jet",
"-password=jet",
"-dbname=jetdb",
"-schema=dvds",
"-ignore-tables=actor,ADDRESS,country, Film , cITY,",
"-ignore-views=Actor_info, FILM_LIST ,staff_list",
"-ignore-enums=mpaa_rating",
"-path=" + genTestDir2,
},
},
}
cmd := exec.Command("jet", for _, tt := range tests {
"-source=PostgreSQL", t.Run(tt.name, func(t *testing.T) {
"-host=localhost", err := os.RemoveAll(genTestDir2)
"-port="+strconv.Itoa(dbconfig.PgPort), require.NoError(t, err)
"-user=jet",
"-password=jet",
"-dbname=jetdb",
"-schema=dvds",
"-ignore-tables=actor,ADDRESS,country, Film , cITY,",
"-ignore-views=Actor_info, FILM_LIST ,staff_list",
"-ignore-enums=mpaa_rating",
"-path="+genTestDir2)
fmt.Println(cmd.Args) cmd := exec.Command("jet", tt.args...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
err = cmd.Run() fmt.Println(cmd.Args)
require.NoError(t, err) cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
// Table SQL Builder files err = cmd.Run()
tableSQLBuilderFiles, err := ioutil.ReadDir("./.gentestdata2/jetdb/dvds/table") require.NoError(t, err)
require.NoError(t, err)
testutils.AssertFileNamesEqual(t, tableSQLBuilderFiles, "category.go", // Table SQL Builder files
"customer.go", "film_actor.go", "film_category.go", "inventory.go", "language.go", tableSQLBuilderFiles, err := ioutil.ReadDir("./.gentestdata2/jetdb/dvds/table")
"payment.go", "rental.go", "staff.go", "store.go") require.NoError(t, err)
// View SQL Builder files testutils.AssertFileNamesEqual(t, tableSQLBuilderFiles, "category.go",
viewSQLBuilderFiles, err := ioutil.ReadDir("./.gentestdata2/jetdb/dvds/view") "customer.go", "film_actor.go", "film_category.go", "inventory.go", "language.go",
require.NoError(t, err) "payment.go", "rental.go", "staff.go", "store.go")
testutils.AssertFileNamesEqual(t, viewSQLBuilderFiles, "nicer_but_slower_film_list.go", // View SQL Builder files
"sales_by_film_category.go", "customer_list.go", "sales_by_store.go") viewSQLBuilderFiles, err := ioutil.ReadDir("./.gentestdata2/jetdb/dvds/view")
require.NoError(t, err)
// Enums SQL Builder files testutils.AssertFileNamesEqual(t, viewSQLBuilderFiles, "nicer_but_slower_film_list.go",
_, err = ioutil.ReadDir("./.gentestdata2/jetdb/dvds/enum") "sales_by_film_category.go", "customer_list.go", "sales_by_store.go")
require.Error(t, err, "open ./.gentestdata2/jetdb/dvds/enum: no such file or directory")
modelFiles, err := ioutil.ReadDir("./.gentestdata2/jetdb/dvds/model") // Enums SQL Builder files
require.NoError(t, err) _, err = ioutil.ReadDir("./.gentestdata2/jetdb/dvds/enum")
require.Error(t, err, "open ./.gentestdata2/jetdb/dvds/enum: no such file or directory")
testutils.AssertFileNamesEqual(t, modelFiles, "category.go", modelFiles, err := ioutil.ReadDir("./.gentestdata2/jetdb/dvds/model")
"customer.go", "film_actor.go", "film_category.go", "inventory.go", "language.go", require.NoError(t, err)
"payment.go", "rental.go", "staff.go", "store.go",
"nicer_but_slower_film_list.go", "sales_by_film_category.go", testutils.AssertFileNamesEqual(t, modelFiles, "category.go",
"customer_list.go", "sales_by_store.go") "customer.go", "film_actor.go", "film_category.go", "inventory.go", "language.go",
"payment.go", "rental.go", "staff.go", "store.go",
"nicer_but_slower_film_list.go", "sales_by_film_category.go",
"customer_list.go", "sales_by_store.go")
})
}
} }
func TestGenerator(t *testing.T) { func TestGenerator(t *testing.T) {
@ -313,6 +345,16 @@ func (a ActorTable) FromSchema(schemaName string) *ActorTable {
return newActorTable(schemaName, a.TableName(), a.Alias()) return newActorTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new ActorTable with assigned table prefix
func (a ActorTable) WithPrefix(prefix string) *ActorTable {
return newActorTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new ActorTable with assigned table suffix
func (a ActorTable) WithSuffix(suffix string) *ActorTable {
return newActorTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newActorTable(schemaName, tableName, alias string) *ActorTable { func newActorTable(schemaName, tableName, alias string) *ActorTable {
return &ActorTable{ return &ActorTable{
actorTable: newActorTableImpl(schemaName, tableName, alias), actorTable: newActorTableImpl(schemaName, tableName, alias),
@ -412,6 +454,16 @@ func (a ActorInfoTable) FromSchema(schemaName string) *ActorInfoTable {
return newActorInfoTable(schemaName, a.TableName(), a.Alias()) return newActorInfoTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new ActorInfoTable with assigned table prefix
func (a ActorInfoTable) WithPrefix(prefix string) *ActorInfoTable {
return newActorInfoTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new ActorInfoTable with assigned table suffix
func (a ActorInfoTable) WithSuffix(suffix string) *ActorInfoTable {
return newActorInfoTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newActorInfoTable(schemaName, tableName, alias string) *ActorInfoTable { func newActorInfoTable(schemaName, tableName, alias string) *ActorInfoTable {
return &ActorInfoTable{ return &ActorInfoTable{
actorInfoTable: newActorInfoTableImpl(schemaName, tableName, alias), actorInfoTable: newActorInfoTableImpl(schemaName, tableName, alias),
@ -445,6 +497,8 @@ func newActorInfoTableImpl(schemaName, tableName, alias string) actorInfoTable {
` `
func TestGeneratedAllTypesSQLBuilderFiles(t *testing.T) { func TestGeneratedAllTypesSQLBuilderFiles(t *testing.T) {
skipForCockroachDB(t)
enumDir := filepath.Join(testRoot, "/.gentestdata/jetdb/test_sample/enum/") enumDir := filepath.Join(testRoot, "/.gentestdata/jetdb/test_sample/enum/")
modelDir := filepath.Join(testRoot, "/.gentestdata/jetdb/test_sample/model/") modelDir := filepath.Join(testRoot, "/.gentestdata/jetdb/test_sample/model/")
tableDir := filepath.Join(testRoot, "/.gentestdata/jetdb/test_sample/table/") tableDir := filepath.Join(testRoot, "/.gentestdata/jetdb/test_sample/table/")
@ -705,6 +759,16 @@ func (a AllTypesTable) FromSchema(schemaName string) *AllTypesTable {
return newAllTypesTable(schemaName, a.TableName(), a.Alias()) return newAllTypesTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new AllTypesTable with assigned table prefix
func (a AllTypesTable) WithPrefix(prefix string) *AllTypesTable {
return newAllTypesTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new AllTypesTable with assigned table suffix
func (a AllTypesTable) WithSuffix(suffix string) *AllTypesTable {
return newAllTypesTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newAllTypesTable(schemaName, tableName, alias string) *AllTypesTable { func newAllTypesTable(schemaName, tableName, alias string) *AllTypesTable {
return &AllTypesTable{ return &AllTypesTable{
allTypesTable: newAllTypesTableImpl(schemaName, tableName, alias), allTypesTable: newAllTypesTableImpl(schemaName, tableName, alias),

View file

@ -2,6 +2,7 @@ package postgres
import ( import (
"context" "context"
"database/sql"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
. "github.com/go-jet/jet/v2/postgres" . "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/model"
@ -13,9 +14,13 @@ import (
) )
func TestInsertValues(t *testing.T) { func TestInsertValues(t *testing.T) {
cleanUpLinkTable(t) insertQuery := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
VALUES(101, "http://www.google.com", "Google", DEFAULT).
VALUES(102, "http://www.yahoo.com", "Yahoo", nil).
RETURNING(Link.AllColumns)
var expectedSQL = ` testutils.AssertDebugStatementSql(t, insertQuery, `
INSERT INTO test_sample.link (id, url, name, description) INSERT INTO test_sample.link (id, url, name, description)
VALUES (100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT), VALUES (100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT),
(101, 'http://www.google.com', 'Google', DEFAULT), (101, 'http://www.google.com', 'Google', DEFAULT),
@ -24,76 +29,61 @@ RETURNING link.id AS "link.id",
link.url AS "link.url", link.url AS "link.url",
link.name AS "link.name", link.name AS "link.name",
link.description AS "link.description"; link.description AS "link.description";
` `,
insertQuery := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
VALUES(101, "http://www.google.com", "Google", DEFAULT).
VALUES(102, "http://www.yahoo.com", "Yahoo", nil).
RETURNING(Link.AllColumns)
testutils.AssertDebugStatementSql(t, insertQuery, expectedSQL,
100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", 100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
101, "http://www.google.com", "Google", 101, "http://www.google.com", "Google",
102, "http://www.yahoo.com", "Yahoo", nil) 102, "http://www.yahoo.com", "Yahoo", nil)
insertedLinks := []model.Link{} testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
var insertedLinks []model.Link
err := insertQuery.Query(db, &insertedLinks) err := insertQuery.Query(tx, &insertedLinks)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(insertedLinks), 3)
testutils.AssertDeepEqual(t, insertedLinks[0], model.Link{
ID: 100,
URL: "http://www.postgresqltutorial.com",
Name: "PostgreSQL Tutorial",
})
testutils.AssertDeepEqual(t, insertedLinks[1], model.Link{
ID: 101,
URL: "http://www.google.com",
Name: "Google",
})
testutils.AssertDeepEqual(t, insertedLinks[2], model.Link{
ID: 102,
URL: "http://www.yahoo.com",
Name: "Yahoo",
})
require.Equal(t, len(insertedLinks), 3) var allLinks []model.Link
testutils.AssertDeepEqual(t, insertedLinks[0], model.Link{ err = Link.SELECT(Link.AllColumns).
ID: 100, WHERE(Link.ID.BETWEEN(Int(100), Int(199))).
URL: "http://www.postgresqltutorial.com", ORDER_BY(Link.ID).
Name: "PostgreSQL Tutorial", Query(tx, &allLinks)
require.NoError(t, err)
testutils.AssertDeepEqual(t, insertedLinks, allLinks)
}) })
testutils.AssertDeepEqual(t, insertedLinks[1], model.Link{
ID: 101,
URL: "http://www.google.com",
Name: "Google",
})
testutils.AssertDeepEqual(t, insertedLinks[2], model.Link{
ID: 102,
URL: "http://www.yahoo.com",
Name: "Yahoo",
})
allLinks := []model.Link{}
err = Link.SELECT(Link.AllColumns).
WHERE(Link.ID.GT_EQ(Int(100))).
ORDER_BY(Link.ID).
Query(db, &allLinks)
require.NoError(t, err)
testutils.AssertDeepEqual(t, insertedLinks, allLinks)
} }
func TestInsertEmptyColumnList(t *testing.T) { func TestInsertEmptyColumnList(t *testing.T) {
cleanUpLinkTable(t)
expectedSQL := `
INSERT INTO test_sample.link
VALUES (100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT);
`
stmt := Link.INSERT(). stmt := Link.INSERT().
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT) VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT)
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, testutils.AssertDebugStatementSql(t, stmt, `
INSERT INTO test_sample.link
VALUES (100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT);
`,
100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial") 100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial")
AssertExec(t, stmt, 1) testutils.AssertExecAndRollback(t, stmt, db, 1)
requireLogged(t, stmt) requireLogged(t, stmt)
} }
func TestInsertOnConflict(t *testing.T) { func TestInsertOnConflict(t *testing.T) {
t.Run("do nothing", func(t *testing.T) { t.Run("do nothing", func(t *testing.T) {
employee := model.Employee{EmployeeID: rand.Int31()} employee := model.Employee{EmployeeID: rand.Int31()}
@ -108,11 +98,12 @@ VALUES ($1, $2, $3, $4, $5),
($6, $7, $8, $9, $10) ($6, $7, $8, $9, $10)
ON CONFLICT (employee_id) DO NOTHING; ON CONFLICT (employee_id) DO NOTHING;
`) `)
AssertExec(t, stmt, 1) testutils.AssertExecAndRollback(t, stmt, db, 1)
requireLogged(t, stmt) requireLogged(t, stmt)
}) })
t.Run("on constraint do nothing", func(t *testing.T) { t.Run("on constraint do nothing", func(t *testing.T) {
skipForCockroachDB(t) // does not support
employee := model.Employee{EmployeeID: rand.Int31()} employee := model.Employee{EmployeeID: rand.Int31()}
stmt := Employee.INSERT(Employee.AllColumns). stmt := Employee.INSERT(Employee.AllColumns).
@ -126,12 +117,11 @@ VALUES ($1, $2, $3, $4, $5),
($6, $7, $8, $9, $10) ($6, $7, $8, $9, $10)
ON CONFLICT ON CONSTRAINT employee_pkey DO NOTHING; ON CONFLICT ON CONSTRAINT employee_pkey DO NOTHING;
`) `)
AssertExec(t, stmt, 1) testutils.AssertExecAndRollback(t, stmt, db, 1)
requireLogged(t, stmt) requireLogged(t, stmt)
}) })
t.Run("do update", func(t *testing.T) { t.Run("do update", func(t *testing.T) {
cleanUpLinkTable(t)
stmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description). stmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT). VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
VALUES(200, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT). VALUES(200, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
@ -148,18 +138,19 @@ VALUES ($1, $2, $3, DEFAULT),
($4, $5, $6, DEFAULT) ($4, $5, $6, DEFAULT)
ON CONFLICT (id) DO UPDATE ON CONFLICT (id) DO UPDATE
SET id = excluded.id, SET id = excluded.id,
url = $7 url = $7::text
RETURNING link.id AS "link.id", RETURNING link.id AS "link.id",
link.url AS "link.url", link.url AS "link.url",
link.name AS "link.name", link.name AS "link.name",
link.description AS "link.description"; link.description AS "link.description";
`) `)
AssertExec(t, stmt, 2) testutils.AssertExecAndRollback(t, stmt, db, 2)
}) })
t.Run("on constraint do update", func(t *testing.T) { t.Run("on constraint do update", func(t *testing.T) {
cleanUpLinkTable(t) skipForCockroachDB(t) // does not support
stmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description). stmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT). VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
VALUES(200, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT). VALUES(200, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
@ -177,18 +168,18 @@ VALUES ($1, $2, $3, DEFAULT),
($4, $5, $6, DEFAULT) ($4, $5, $6, DEFAULT)
ON CONFLICT ON CONSTRAINT link_pkey DO UPDATE ON CONFLICT ON CONSTRAINT link_pkey DO UPDATE
SET id = excluded.id, SET id = excluded.id,
url = $7 url = $7::text
RETURNING link.id AS "link.id", RETURNING link.id AS "link.id",
link.url AS "link.url", link.url AS "link.url",
link.name AS "link.name", link.name AS "link.name",
link.description AS "link.description"; link.description AS "link.description";
`) `)
AssertExec(t, stmt, 2) testutils.AssertExecAndRollback(t, stmt, db, 2)
}) })
t.Run("do update complex", func(t *testing.T) { t.Run("do update complex", func(t *testing.T) {
cleanUpLinkTable(t) skipForCockroachDB(t) // does not support ROW
stmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description). stmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT). VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
@ -210,21 +201,15 @@ ON CONFLICT (id) WHERE (id * 2) > 10 DO UPDATE
SELECT MAX(link.id) + 1 SELECT MAX(link.id) + 1
FROM test_sample.link FROM test_sample.link
), ),
(name, description) = ROW(excluded.name, 'new description') (name, description) = ROW(excluded.name, 'new description'::text)
WHERE link.description IS NOT NULL; WHERE link.description IS NOT NULL;
`) `)
AssertExec(t, stmt, 1) testutils.AssertExecAndRollback(t, stmt, db, 1)
}) })
} }
func TestInsertModelObject(t *testing.T) { func TestInsertModelObject(t *testing.T) {
cleanUpLinkTable(t)
var expectedSQL = `
INSERT INTO test_sample.link (url, name)
VALUES ('http://www.duckduckgo.com', 'Duck Duck go');
`
linkData := model.Link{ linkData := model.Link{
URL: "http://www.duckduckgo.com", URL: "http://www.duckduckgo.com",
Name: "Duck Duck go", Name: "Duck Duck go",
@ -234,18 +219,15 @@ VALUES ('http://www.duckduckgo.com', 'Duck Duck go');
INSERT(Link.URL, Link.Name). INSERT(Link.URL, Link.Name).
MODEL(linkData) MODEL(linkData)
testutils.AssertDebugStatementSql(t, query, expectedSQL, "http://www.duckduckgo.com", "Duck Duck go") testutils.AssertDebugStatementSql(t, query, `
INSERT INTO test_sample.link (url, name)
VALUES ('http://www.duckduckgo.com', 'Duck Duck go');
`, "http://www.duckduckgo.com", "Duck Duck go")
AssertExec(t, query, 1) testutils.AssertExecAndRollback(t, query, db, 1)
} }
func TestInsertModelObjectEmptyColumnList(t *testing.T) { func TestInsertModelObjectEmptyColumnList(t *testing.T) {
cleanUpLinkTable(t)
var expectedSQL = `
INSERT INTO test_sample.link
VALUES (1000, 'http://www.duckduckgo.com', 'Duck Duck go', NULL);
`
linkData := model.Link{ linkData := model.Link{
ID: 1000, ID: 1000,
URL: "http://www.duckduckgo.com", URL: "http://www.duckduckgo.com",
@ -256,19 +238,16 @@ VALUES (1000, 'http://www.duckduckgo.com', 'Duck Duck go', NULL);
INSERT(). INSERT().
MODEL(linkData) MODEL(linkData)
testutils.AssertDebugStatementSql(t, query, expectedSQL, int32(1000), "http://www.duckduckgo.com", "Duck Duck go", nil) testutils.AssertDebugStatementSql(t, query, `
INSERT INTO test_sample.link
VALUES (1000, 'http://www.duckduckgo.com', 'Duck Duck go', NULL);
`,
int64(1000), "http://www.duckduckgo.com", "Duck Duck go", nil)
AssertExec(t, query, 1) testutils.AssertExecAndRollback(t, query, db, 1)
} }
func TestInsertModelsObject(t *testing.T) { func TestInsertModelsObject(t *testing.T) {
expectedSQL := `
INSERT INTO test_sample.link (url, name)
VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial'),
('http://www.google.com', 'Google'),
('http://www.yahoo.com', 'Yahoo');
`
tutorial := model.Link{ tutorial := model.Link{
URL: "http://www.postgresqltutorial.com", URL: "http://www.postgresqltutorial.com",
Name: "PostgreSQL Tutorial", Name: "PostgreSQL Tutorial",
@ -288,23 +267,20 @@ VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial'),
INSERT(Link.URL, Link.Name). INSERT(Link.URL, Link.Name).
MODELS([]model.Link{tutorial, google, yahoo}) MODELS([]model.Link{tutorial, google, yahoo})
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, testutils.AssertDebugStatementSql(t, stmt, `
INSERT INTO test_sample.link (url, name)
VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial'),
('http://www.google.com', 'Google'),
('http://www.yahoo.com', 'Yahoo');
`,
"http://www.postgresqltutorial.com", "PostgreSQL Tutorial", "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
"http://www.google.com", "Google", "http://www.google.com", "Google",
"http://www.yahoo.com", "Yahoo") "http://www.yahoo.com", "Yahoo")
AssertExec(t, stmt, 3) testutils.AssertExecAndRollback(t, stmt, db, 3)
} }
func TestInsertUsingMutableColumns(t *testing.T) { func TestInsertUsingMutableColumns(t *testing.T) {
var expectedSQL = `
INSERT INTO test_sample.link (url, name, description)
VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT),
('http://www.google.com', 'Google', NULL),
('http://www.google.com', 'Google', NULL),
('http://www.yahoo.com', 'Yahoo', NULL);
`
google := model.Link{ google := model.Link{
URL: "http://www.google.com", URL: "http://www.google.com",
Name: "Google", Name: "Google",
@ -321,22 +297,32 @@ VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT),
MODEL(google). MODEL(google).
MODELS([]model.Link{google, yahoo}) MODELS([]model.Link{google, yahoo})
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, testutils.AssertDebugStatementSql(t, stmt, `
INSERT INTO test_sample.link (url, name, description)
VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT),
('http://www.google.com', 'Google', NULL),
('http://www.google.com', 'Google', NULL),
('http://www.yahoo.com', 'Yahoo', NULL);
`,
"http://www.postgresqltutorial.com", "PostgreSQL Tutorial", "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
"http://www.google.com", "Google", nil, "http://www.google.com", "Google", nil,
"http://www.google.com", "Google", nil, "http://www.google.com", "Google", nil,
"http://www.yahoo.com", "Yahoo", nil) "http://www.yahoo.com", "Yahoo", nil)
AssertExec(t, stmt, 4) testutils.AssertExecAndRollback(t, stmt, db, 4)
} }
func TestInsertQuery(t *testing.T) { func TestInsertQuery(t *testing.T) {
_, err := Link.DELETE(). query := Link.
WHERE(Link.ID.NOT_EQ(Int(0)).AND(Link.Name.EQ(String("Youtube")))). INSERT(Link.URL, Link.Name).
Exec(db) QUERY(
require.NoError(t, err) SELECT(Link.URL, Link.Name).
FROM(Link).
WHERE(Link.ID.EQ(Int(0))),
).
RETURNING(Link.AllColumns)
var expectedSQL = ` testutils.AssertDebugStatementSql(t, query, `
INSERT INTO test_sample.link (url, name) ( INSERT INTO test_sample.link (url, name) (
SELECT link.url AS "link.url", SELECT link.url AS "link.url",
link.name AS "link.name" link.name AS "link.name"
@ -347,38 +333,26 @@ RETURNING link.id AS "link.id",
link.url AS "link.url", link.url AS "link.url",
link.name AS "link.name", link.name AS "link.name",
link.description AS "link.description"; link.description AS "link.description";
` `, int64(0))
query := Link. testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
INSERT(Link.URL, Link.Name). var dest []model.Link
QUERY(
SELECT(Link.URL, Link.Name).
FROM(Link).
WHERE(Link.ID.EQ(Int(0))),
).
RETURNING(Link.AllColumns)
testutils.AssertDebugStatementSql(t, query, expectedSQL, int64(0)) err := query.Query(tx, &dest)
require.NoError(t, err)
dest := []model.Link{} var youtubeLinks []model.Link
err = Link.
SELECT(Link.AllColumns).
WHERE(Link.Name.EQ(String("Youtube"))).
Query(tx, &youtubeLinks)
err = query.Query(db, &dest) require.NoError(t, err)
require.Equal(t, len(youtubeLinks), 2)
require.NoError(t, err) })
youtubeLinks := []model.Link{}
err = Link.
SELECT(Link.AllColumns).
WHERE(Link.Name.EQ(String("Youtube"))).
Query(db, &youtubeLinks)
require.NoError(t, err)
require.Equal(t, len(youtubeLinks), 2)
} }
func TestInsertWithQueryContext(t *testing.T) { func TestInsertWithQueryContext(t *testing.T) {
cleanUpLinkTable(t)
stmt := Link.INSERT(). stmt := Link.INSERT().
VALUES(1100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT). VALUES(1100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
RETURNING(Link.AllColumns) RETURNING(Link.AllColumns)
@ -388,15 +362,15 @@ func TestInsertWithQueryContext(t *testing.T) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
dest := []model.Link{} testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
err := stmt.QueryContext(ctx, db, &dest) dest := []model.Link{}
err := stmt.QueryContext(ctx, tx, &dest)
require.Error(t, err, "context deadline exceeded") require.Error(t, err, "context deadline exceeded")
})
} }
func TestInsertWithExecContext(t *testing.T) { func TestInsertWithExecContext(t *testing.T) {
cleanUpLinkTable(t)
stmt := Link.INSERT(). stmt := Link.INSERT().
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT) VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT)
@ -405,7 +379,7 @@ func TestInsertWithExecContext(t *testing.T) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
_, err := stmt.ExecContext(ctx, db) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
testutils.AssertExecContextErr(ctx, t, stmt, tx, "context deadline exceeded")
require.Error(t, err, "context deadline exceeded") })
} }

View file

@ -12,6 +12,8 @@ import (
) )
func TestLockTable(t *testing.T) { func TestLockTable(t *testing.T) {
skipForCockroachDB(t) // doesn't support
expectedSQL := ` expectedSQL := `
LOCK TABLE dvds.address IN` LOCK TABLE dvds.address IN`
@ -62,6 +64,8 @@ LOCK TABLE dvds.address IN`
} }
func TestLockExecContext(t *testing.T) { func TestLockExecContext(t *testing.T) {
skipForCockroachDB(t)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Microsecond) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Microsecond)
defer cancel() defer cancel()

View file

@ -25,6 +25,24 @@ import (
var db *sql.DB var db *sql.DB
var testRoot string var testRoot string
var source string
const CockroachDB = "COCKROACH_DB"
func init() {
source = os.Getenv("PG_SOURCE")
}
func sourceIsCockroachDB() bool {
return source == CockroachDB
}
func skipForCockroachDB(t *testing.T) {
if sourceIsCockroachDB() {
t.SkipNow()
}
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
defer profile.Start().Stop() defer profile.Start().Stop()
@ -35,8 +53,15 @@ func TestMain(m *testing.M) {
fmt.Printf("\nRunning postgres tests for '%s' driver\n", driverName) fmt.Printf("\nRunning postgres tests for '%s' driver\n", driverName)
func() { func() {
connectionString := dbconfig.PostgresConnectString
if sourceIsCockroachDB() {
connectionString = dbconfig.CockroachConnectString
}
var err error var err error
db, err = sql.Open(driverName, dbconfig.PostgresConnectString) db, err = sql.Open(driverName, connectionString)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
panic("Failed to connect to test db") panic("Failed to connect to test db")
@ -113,9 +138,3 @@ func isPgxDriver() bool {
return false return false
} }
func beginTx(t *testing.T) *sql.Tx {
tx, err := db.Begin()
require.NoError(t, err)
return tx
}

View file

@ -2,6 +2,7 @@ package postgres
import ( import (
"context" "context"
"database/sql"
"testing" "testing"
"time" "time"
@ -85,12 +86,10 @@ func TestRawStatementSelectWithArguments(t *testing.T) {
} }
func TestRawInsert(t *testing.T) { func TestRawInsert(t *testing.T) {
cleanUpLinkTable(t)
stmt := RawStatement(` stmt := RawStatement(`
INSERT INTO test_sample.link (id, url, name, description) INSERT INTO test_sample.link (id, url, name, description)
VALUES (@id1, @url1, @name1, DEFAULT), VALUES (@id1, @url1, @name1, DEFAULT),
(200, @url1, @name1, NULL), (2000, @url1, @name1, NULL),
(@id2, @url2, @name2, DEFAULT), (@id2, @url2, @name2, DEFAULT),
(@id3, @url3, @name3, NULL) (@id3, @url3, @name3, NULL)
RETURNING link.id AS "link.id", RETURNING link.id AS "link.id",
@ -98,45 +97,47 @@ RETURNING link.id AS "link.id",
link.name AS "link.name", link.name AS "link.name",
link.description AS "link.description"`, link.description AS "link.description"`,
RawArgs{ RawArgs{
"@id1": 100, "@url1": "http://www.postgresqltutorial.com", "@name1": "PostgreSQL Tutorial", "@id1": 1000, "@url1": "http://www.postgresqltutorial.com", "@name1": "PostgreSQL Tutorial",
"@id2": 101, "@url2": "http://www.google.com", "@name2": "Google", "@id2": 1010, "@url2": "http://www.google.com", "@name2": "Google",
"@id3": 102, "@url3": "http://www.yahoo.com", "@name3": "Yahoo", "@id3": 1020, "@url3": "http://www.yahoo.com", "@name3": "Yahoo",
}) })
testutils.AssertStatementSql(t, stmt, ` testutils.AssertStatementSql(t, stmt, `
INSERT INTO test_sample.link (id, url, name, description) INSERT INTO test_sample.link (id, url, name, description)
VALUES ($1, $2, $3, DEFAULT), VALUES ($1, $2, $3, DEFAULT),
(200, $2, $3, NULL), (2000, $2, $3, NULL),
($4, $5, $6, DEFAULT), ($4, $5, $6, DEFAULT),
($7, $8, $9, NULL) ($7, $8, $9, NULL)
RETURNING link.id AS "link.id", RETURNING link.id AS "link.id",
link.url AS "link.url", link.url AS "link.url",
link.name AS "link.name", link.name AS "link.name",
link.description AS "link.description"; link.description AS "link.description";
`, 100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", `, 1000, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
101, "http://www.google.com", "Google", 1010, "http://www.google.com", "Google",
102, "http://www.yahoo.com", "Yahoo") 1020, "http://www.yahoo.com", "Yahoo")
testutils.AssertDebugStatementSql(t, stmt, ` testutils.AssertDebugStatementSql(t, stmt, `
INSERT INTO test_sample.link (id, url, name, description) INSERT INTO test_sample.link (id, url, name, description)
VALUES (100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT), VALUES (1000, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT),
(200, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', NULL), (2000, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', NULL),
(101, 'http://www.google.com', 'Google', DEFAULT), (1010, 'http://www.google.com', 'Google', DEFAULT),
(102, 'http://www.yahoo.com', 'Yahoo', NULL) (1020, 'http://www.yahoo.com', 'Yahoo', NULL)
RETURNING link.id AS "link.id", RETURNING link.id AS "link.id",
link.url AS "link.url", link.url AS "link.url",
link.name AS "link.name", link.name AS "link.name",
link.description AS "link.description"; link.description AS "link.description";
`) `)
var links []model2.Link testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
err := stmt.Query(db, &links) var links []model2.Link
require.NoError(t, err) err := stmt.Query(tx, &links)
require.Len(t, links, 4) require.NoError(t, err)
require.Equal(t, links[0].ID, int32(100)) require.Len(t, links, 4)
require.Equal(t, links[1].URL, "http://www.postgresqltutorial.com") require.Equal(t, links[0].ID, int64(1000))
require.Equal(t, links[2].Name, "Google") require.Equal(t, links[1].URL, "http://www.postgresqltutorial.com")
require.Nil(t, links[2].Description) require.Equal(t, links[2].Name, "Google")
require.Nil(t, links[2].Description)
})
} }
func TestRawStatementRows(t *testing.T) { func TestRawStatementRows(t *testing.T) {

View file

@ -1,9 +1,9 @@
package postgres package postgres
import ( import (
"github.com/google/uuid"
"testing" "testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
@ -14,30 +14,6 @@ import (
"github.com/shopspring/decimal" "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(UUID(id)))
testutils.AssertDebugStatementSql(t, query, `
SELECT all_types.uuid AS "all_types.uuid",
all_types.uuid_ptr AS "all_types.uuid_ptr"
FROM test_sample.all_types
WHERE all_types.uuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
`, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")
result := model.AllTypes{}
err := query.Query(db, &result)
require.NoError(t, err)
require.Equal(t, result.UUID, uuid.MustParse("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"))
testutils.AssertDeepEqual(t, result.UUIDPtr, testutils.UUIDPtr("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"))
requireLogged(t, query)
}
func TestExactDecimals(t *testing.T) { func TestExactDecimals(t *testing.T) {
type floats struct { type floats struct {
@ -80,7 +56,7 @@ func TestExactDecimals(t *testing.T) {
t.Run("should insert decimal", func(t *testing.T) { t.Run("should insert decimal", func(t *testing.T) {
insertQuery := Floats.INSERT( insertQuery := Floats.INSERT(
Floats.AllColumns, Floats.MutableColumns,
).MODEL( ).MODEL(
floats{ floats{
Floats: model.Floats{ Floats: model.Floats{
@ -102,7 +78,7 @@ func TestExactDecimals(t *testing.T) {
DecimalPtr: decimal.RequireFromString("3.3333333333333333333"), DecimalPtr: decimal.RequireFromString("3.3333333333333333333"),
}, },
).RETURNING( ).RETURNING(
Floats.AllColumns, Floats.MutableColumns,
) )
testutils.AssertDebugStatementSql(t, insertQuery, ` testutils.AssertDebugStatementSql(t, insertQuery, `
@ -199,7 +175,9 @@ func TestUUIDComplex(t *testing.T) {
}) })
t.Run("single struct", func(t *testing.T) { t.Run("single struct", func(t *testing.T) {
singleQuery := query.WHERE(Person.PersonID.EQ(String("b68dbff6-a87d-11e9-a7f2-98ded00c39c8"))) uuid, err := uuid.Parse("b68dbff6-a87d-11e9-a7f2-98ded00c39c8")
require.NoError(t, err)
singleQuery := query.WHERE(Person.PersonID.EQ(UUID(uuid)))
var dest struct { var dest struct {
model.Person model.Person
@ -207,7 +185,7 @@ func TestUUIDComplex(t *testing.T) {
model.PersonPhone model.PersonPhone
} }
} }
err := singleQuery.Query(db, &dest) err = singleQuery.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
testutils.AssertJSON(t, dest, ` testutils.AssertJSON(t, dest, `
@ -304,7 +282,7 @@ SELECT person.person_id AS "person.person_id",
FROM test_sample.person; FROM test_sample.person;
`) `)
result := []model.Person{} var result []model.Person
err := query.Query(db, &result) err := query.Query(db, &result)
@ -333,7 +311,7 @@ FROM test_sample.person;
`) `)
} }
func TestSelecSelfJoin1(t *testing.T) { func TestSelectSelfJoin1(t *testing.T) {
// clean up // clean up
_, err := Employee.DELETE().WHERE(Employee.EmployeeID.GT(Int(100))).Exec(db) _, err := Employee.DELETE().WHERE(Employee.EmployeeID.GT(Int(100))).Exec(db)
@ -398,7 +376,7 @@ ORDER BY employee.employee_id;
} }
func TestWierdNamesTable(t *testing.T) { func TestWierdNamesTable(t *testing.T) {
stmt := WeirdNamesTable.SELECT(WeirdNamesTable.AllColumns) stmt := WeirdNamesTable.SELECT(WeirdNamesTable.MutableColumns)
testutils.AssertDebugStatementSql(t, stmt, ` testutils.AssertDebugStatementSql(t, stmt, `
SELECT "WEIRD NAMES TABLE".weird_column_name1 AS "WEIRD NAMES TABLE.weird_column_name1", SELECT "WEIRD NAMES TABLE".weird_column_name1 AS "WEIRD NAMES TABLE.weird_column_name1",
@ -420,7 +398,7 @@ SELECT "WEIRD NAMES TABLE".weird_column_name1 AS "WEIRD NAMES TABLE.weird_column
FROM test_sample."WEIRD NAMES TABLE"; FROM test_sample."WEIRD NAMES TABLE";
`) `)
dest := []model.WeirdNamesTable{} var dest []model.WeirdNamesTable
err := stmt.Query(db, &dest) err := stmt.Query(db, &dest)
@ -448,7 +426,7 @@ FROM test_sample."WEIRD NAMES TABLE";
} }
func TestReserwedWordEscape(t *testing.T) { func TestReserwedWordEscape(t *testing.T) {
stmt := SELECT(User.AllColumns). stmt := SELECT(User.MutableColumns).
FROM(User) FROM(User)
//fmt.Println(stmt.DebugSql()) //fmt.Println(stmt.DebugSql())
@ -480,6 +458,7 @@ FROM test_sample."User";
testutils.AssertJSON(t, dest, ` testutils.AssertJSON(t, dest, `
[ [
{ {
"ID": 0,
"Column": "Column", "Column": "Column",
"Check": "CHECK", "Check": "CHECK",
"Ceil": "CEIL", "Ceil": "CEIL",
@ -497,54 +476,3 @@ 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!")
}

View file

@ -2,6 +2,7 @@ package postgres
import ( import (
"context" "context"
"github.com/volatiletech/null/v8"
"testing" "testing"
"time" "time"
@ -1034,6 +1035,44 @@ func TestScanToPrimitiveElementsSlice(t *testing.T) {
require.Len(t, dest[1].Title, 20) require.Len(t, dest[1].Title, 20)
} }
// https://github.com/go-jet/jet/issues/127
func TestValuerTypeDebugSQL(t *testing.T) {
type customer struct {
CustomerID null.Int32 `sql:"primary_key"`
StoreID null.Int16
FirstName null.String
LastName string
Email null.String
AddressID int16
Activebool null.Bool
CreateDate null.Time
LastUpdate null.Time
Active null.Int8
}
stmt := Customer.INSERT().
MODEL(
customer{
CustomerID: null.Int32From(1234),
StoreID: null.Int16From(0),
FirstName: null.StringFrom("Joe"),
LastName: "",
Email: null.StringFromPtr(nil),
AddressID: 1,
Activebool: null.BoolFrom(true),
CreateDate: null.TimeFrom(time.Date(2020, 2, 2, 10, 0, 0, 0, time.UTC)),
LastUpdate: null.TimeFromPtr(nil),
Active: null.Int8From(1),
},
)
testutils.AssertDebugStatementSql(t, stmt, `
INSERT INTO dvds.customer
VALUES (1234, 0, 'Joe', '', NULL, 1, TRUE, '2020-02-02 10:00:00Z', NULL, 1);
`)
testutils.AssertExecAndRollback(t, stmt, db)
}
var address256 = model.Address{ var address256 = model.Address{
AddressID: 256, AddressID: 256,
Address: "1497 Yuzhou Drive", Address: "1497 Yuzhou Drive",

View file

@ -1,6 +1,8 @@
package postgres package postgres
import ( import (
"context"
"github.com/go-jet/jet/v2/qrm"
"testing" "testing"
"time" "time"
@ -24,9 +26,9 @@ FROM dvds.actor
WHERE actor.actor_id = 2; WHERE actor.actor_id = 2;
` `
query := Actor. query := SELECT(Actor.AllColumns).
SELECT(Actor.AllColumns).
DISTINCT(). DISTINCT().
FROM(Actor).
WHERE(Actor.ActorID.EQ(Int(2))) WHERE(Actor.ActorID.EQ(Int(2)))
testutils.AssertDebugStatementSql(t, query, expectedSQL, int64(2)) testutils.AssertDebugStatementSql(t, query, expectedSQL, int64(2))
@ -44,7 +46,6 @@ WHERE actor.actor_id = 2;
} }
testutils.AssertDeepEqual(t, actor, expectedActor) testutils.AssertDeepEqual(t, actor, expectedActor)
requireLogged(t, query) requireLogged(t, query)
} }
@ -166,7 +167,7 @@ SELECT customer.customer_id AS "customer.customer_id",
FROM dvds.customer FROM dvds.customer
ORDER BY customer.customer_id ASC; ORDER BY customer.customer_id ASC;
` `
customers := []model.Customer{} var customers []model.Customer
query := Customer.SELECT(Customer.AllColumns).ORDER_BY(Customer.CustomerID.ASC()) query := Customer.SELECT(Customer.AllColumns).ORDER_BY(Customer.CustomerID.ASC())
@ -416,8 +417,8 @@ FROM dvds.city
INNER JOIN dvds.address ON (address.city_id = city.city_id) INNER JOIN dvds.address ON (address.city_id = city.city_id)
INNER JOIN dvds.customer ON (customer.address_id = address.address_id) INNER JOIN dvds.customer ON (customer.address_id = address.address_id)
WHERE ( WHERE (
(city.city = 'London') (city.city = 'London'::text)
OR (city.city = 'York') OR (city.city = 'York'::text)
) )
ORDER BY city.city_id, address.address_id, customer.customer_id; ORDER BY city.city_id, address.address_id, customer.customer_id;
`, "London", "York") `, "London", "York")
@ -492,7 +493,7 @@ SELECT city.city_id AS "my_city.id",
FROM dvds.city FROM dvds.city
INNER JOIN dvds.address ON (address.city_id = city.city_id) INNER JOIN dvds.address ON (address.city_id = city.city_id)
INNER JOIN dvds.customer ON (customer.address_id = address.address_id) INNER JOIN dvds.customer ON (customer.address_id = address.address_id)
WHERE (city.city = 'London') OR (city.city = 'York') WHERE (city.city = 'London'::text) OR (city.city = 'York'::text)
ORDER BY city.city_id, address.address_id, customer.customer_id; ORDER BY city.city_id, address.address_id, customer.customer_id;
`, "London", "York") `, "London", "York")
@ -550,7 +551,7 @@ SELECT city.city_id AS "city_id",
FROM dvds.city FROM dvds.city
INNER JOIN dvds.address ON (address.city_id = city.city_id) INNER JOIN dvds.address ON (address.city_id = city.city_id)
INNER JOIN dvds.customer ON (customer.address_id = address.address_id) INNER JOIN dvds.customer ON (customer.address_id = address.address_id)
WHERE (city.city = 'London') OR (city.city = 'York') WHERE (city.city = 'London'::text) OR (city.city = 'York'::text)
ORDER BY city.city_id, address.address_id, customer.customer_id; ORDER BY city.city_id, address.address_id, customer.customer_id;
`, "London", "York") `, "London", "York")
@ -607,7 +608,7 @@ SELECT city.city_id AS "city.city_id",
FROM dvds.city FROM dvds.city
INNER JOIN dvds.address ON (address.city_id = city.city_id) INNER JOIN dvds.address ON (address.city_id = city.city_id)
INNER JOIN dvds.customer ON (customer.address_id = address.address_id) INNER JOIN dvds.customer ON (customer.address_id = address.address_id)
WHERE (city.city = 'London') OR (city.city = 'York') WHERE (city.city = 'London'::text) OR (city.city = 'York'::text)
ORDER BY city.city_id, address.address_id, customer.customer_id; ORDER BY city.city_id, address.address_id, customer.customer_id;
`, "London", "York") `, "London", "York")
@ -685,9 +686,6 @@ func TestSelect_WithoutUniqueColumnSelected(t *testing.T) {
err := query.Query(db, &customers) err := query.Query(db, &customers)
require.NoError(t, err) require.NoError(t, err)
//spew.Dump(customers)
require.Equal(t, len(customers), 599) require.Equal(t, len(customers), 599)
} }
@ -770,27 +768,35 @@ ORDER BY customer.customer_id ASC;
testutils.AssertDebugStatementSql(t, query, expectedSQL) testutils.AssertDebugStatementSql(t, query, expectedSQL)
allCustomersAndAddress := []struct { var allCustomersAndAddress []struct {
Address *model.Address Address *model.Address
Customer *model.Customer Customer *model.Customer
}{} }
err := query.Query(db, &allCustomersAndAddress) err := query.Query(db, &allCustomersAndAddress)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(allCustomersAndAddress), 603) require.Equal(t, len(allCustomersAndAddress), 603)
testutils.AssertDeepEqual(t, allCustomersAndAddress[0].Customer, &customer0) if sourceIsCockroachDB() {
require.True(t, allCustomersAndAddress[0].Address != nil) nullsFirst := allCustomersAndAddress[0]
require.True(t, nullsFirst.Customer == nil)
require.True(t, nullsFirst.Address != nil)
lastCustomerAddress := allCustomersAndAddress[len(allCustomersAndAddress)-1] testutils.AssertDeepEqual(t, allCustomersAndAddress[4].Customer, &customer0)
require.True(t, allCustomersAndAddress[0].Address != nil)
} else { // postgres
testutils.AssertDeepEqual(t, allCustomersAndAddress[0].Customer, &customer0)
require.True(t, allCustomersAndAddress[0].Address != nil)
require.True(t, lastCustomerAddress.Customer == nil) nullsLast := allCustomersAndAddress[len(allCustomersAndAddress)-1]
require.True(t, lastCustomerAddress.Address != nil) require.True(t, nullsLast.Customer == nil)
require.True(t, nullsLast.Address != nil)
}
} }
func TestSelectFullCrossJoin(t *testing.T) { func TestSelectCrossJoin(t *testing.T) {
expectedSQL := ` expectedSQL := `
SELECT customer.customer_id AS "customer.customer_id", SELECT customer.customer_id AS "customer.customer_id",
customer.store_id AS "customer.store_id", customer.store_id AS "customer.store_id",
@ -1128,6 +1134,7 @@ ORDER BY film.film_id ASC;
} }
func TestSelectGroupByHaving(t *testing.T) { func TestSelectGroupByHaving(t *testing.T) {
expectedSQL := ` expectedSQL := `
SELECT customer.customer_id AS "customer.customer_id", SELECT customer.customer_id AS "customer.customer_id",
customer.store_id AS "customer.store_id", customer.store_id AS "customer.store_id",
@ -1197,6 +1204,9 @@ ORDER BY customer.customer_id, SUM(payment.amount) ASC;
require.Equal(t, len(dest), 104) require.Equal(t, len(dest), 104)
if sourceIsCockroachDB() {
return // small precision difference in result
}
//testutils.SaveJsonFile(dest, "postgres/testdata/customer_payment_sum.json") //testutils.SaveJsonFile(dest, "postgres/testdata/customer_payment_sum.json")
testutils.AssertJSONFile(t, dest, "./testdata/results/postgres/customer_payment_sum.json") testutils.AssertJSONFile(t, dest, "./testdata/results/postgres/customer_payment_sum.json")
} }
@ -1395,9 +1405,6 @@ ORDER BY payment.payment_date ASC;
err := query.Query(db, &payments) err := query.Query(db, &payments)
require.NoError(t, err) require.NoError(t, err)
//spew.Dump(payments)
require.Equal(t, len(payments), 9) require.Equal(t, len(payments), 9)
testutils.AssertDeepEqual(t, payments[0], model.Payment{ testutils.AssertDeepEqual(t, payments[0], model.Payment{
PaymentID: 17793, PaymentID: 17793,
@ -1531,7 +1538,7 @@ func TestAllSetOperators(t *testing.T) {
func TestSelectWithCase(t *testing.T) { func TestSelectWithCase(t *testing.T) {
expectedQuery := ` expectedQuery := `
SELECT (CASE payment.staff_id WHEN 1 THEN 'ONE' WHEN 2 THEN 'TWO' WHEN 3 THEN 'THREE' ELSE 'OTHER' END) AS "staff_id_num" SELECT (CASE payment.staff_id WHEN 1 THEN 'ONE'::text WHEN 2 THEN 'TWO'::text WHEN 3 THEN 'THREE'::text ELSE 'OTHER'::text END) AS "staff_id_num"
FROM dvds.payment FROM dvds.payment
ORDER BY payment.payment_id ASC ORDER BY payment.payment_id ASC
LIMIT 20; LIMIT 20;
@ -1611,6 +1618,10 @@ FOR`
require.NoError(t, err) require.NoError(t, err)
} }
if sourceIsCockroachDB() {
return // SKIP LOCKED lock wait policy is not supported
}
for lockType, lockTypeStr := range getRowLockTestData() { for lockType, lockTypeStr := range getRowLockTestData() {
query.FOR(lockType.SKIP_LOCKED()) query.FOR(lockType.SKIP_LOCKED())
@ -1660,7 +1671,7 @@ FROM dvds.actor
INNER JOIN dvds.language ON (language.language_id = film.language_id) INNER JOIN dvds.language ON (language.language_id = film.language_id)
INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id) INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id)
INNER JOIN dvds.category ON (category.category_id = film_category.category_id) INNER JOIN dvds.category ON (category.category_id = film_category.category_id)
WHERE ((language.name = 'English') AND (category.name != 'Action')) AND (film.length > 180) WHERE ((language.name = 'English'::text) AND (category.name != 'Action'::text)) AND (film.length > 180)
ORDER BY actor.actor_id ASC, film.film_id ASC; ORDER BY actor.actor_id ASC, film.film_id ASC;
` `
@ -1927,10 +1938,11 @@ func TestSimpleView(t *testing.T) {
query := SELECT( query := SELECT(
view.ActorInfo.AllColumns, view.ActorInfo.AllColumns,
). ).FROM(
FROM(view.ActorInfo). view.ActorInfo,
ORDER_BY(view.ActorInfo.ActorID). ).ORDER_BY(
LIMIT(10) view.ActorInfo.ActorID,
).LIMIT(10)
type ActorInfo struct { type ActorInfo struct {
ActorID int ActorID int
@ -1944,6 +1956,10 @@ func TestSimpleView(t *testing.T) {
err := query.Query(db, &dest) err := query.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
if sourceIsCockroachDB() {
return // skip for cockroach db, FilmInfo is set to '' in ddl
}
testutils.AssertJSON(t, dest[1:2], ` testutils.AssertJSON(t, dest[1:2], `
[ [
{ {
@ -2117,7 +2133,7 @@ FROM dvds.film
language.name AS "language.name", language.name AS "language.name",
language.last_update AS "language.last_update" language.last_update AS "language.last_update"
FROM dvds.language FROM dvds.language
WHERE (language.name NOT IN ('spanish')) AND (film.language_id = language.language_id) WHERE (language.name NOT IN ('spanish'::text)) AND (film.language_id = language.language_id)
) AS films ) AS films
WHERE film.film_id = 1 WHERE film.film_id = 1
ORDER BY film.film_id ORDER BY film.film_id
@ -2162,7 +2178,7 @@ FROM dvds.film,
language.name AS "language.name", language.name AS "language.name",
language.last_update AS "language.last_update" language.last_update AS "language.last_update"
FROM dvds.language FROM dvds.language
WHERE (language.name NOT IN ('spanish')) AND (film.language_id = language.language_id) WHERE (language.name NOT IN ('spanish'::text)) AND (film.language_id = language.language_id)
) AS films ) AS films
WHERE film.film_id = 1 WHERE film.film_id = 1
ORDER BY film.film_id ORDER BY film.film_id
@ -2630,6 +2646,8 @@ func GET_FILM_COUNT(lenFrom, lenTo IntegerExpression) IntegerExpression {
} }
func TestCustomFunctionCall(t *testing.T) { func TestCustomFunctionCall(t *testing.T) {
skipForCockroachDB(t)
stmt := SELECT( stmt := SELECT(
GET_FILM_COUNT(Int(100), Int(120)).AS("film_count"), GET_FILM_COUNT(Int(100), Int(120)).AS("film_count"),
) )
@ -2662,3 +2680,84 @@ SELECT dvds.get_film_count(100, 120) AS "film_count";
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, dest.FilmCount, 165) require.Equal(t, dest.FilmCount, 165)
} }
func TestScanUsingConn(t *testing.T) {
conn, err := db.Conn(context.Background())
require.NoError(t, err)
defer conn.Close()
stmt := SELECT(Actor.AllColumns).
FROM(Actor).
DISTINCT().
WHERE(Actor.ActorID.EQ(Int(2)))
var actor model.Actor
err = stmt.Query(conn, &actor)
require.NoError(t, err)
err = stmt.QueryContext(context.Background(), conn, &actor)
require.NoError(t, err)
testutils.AssertDeepEqual(t, actor, model.Actor{
ActorID: 2,
FirstName: "Nick",
LastName: "Wahlberg",
LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:47:57.62", 2),
})
_, err = stmt.Exec(conn)
require.NoError(t, err)
_, err = stmt.ExecContext(context.Background(), conn)
require.NoError(t, err)
t.Run("ensure qrm.DB still works", func(t *testing.T) {
var qrmDB qrm.DB = db
err = stmt.Query(qrmDB, &actor)
require.NoError(t, err)
err = stmt.QueryContext(context.Background(), qrmDB, &actor)
require.NoError(t, err)
_, err = stmt.Exec(qrmDB)
require.NoError(t, err)
_, err = stmt.ExecContext(context.Background(), qrmDB)
require.NoError(t, err)
})
}
var customer0 = model.Customer{
CustomerID: 1,
StoreID: 1,
FirstName: "Mary",
LastName: "Smith",
Email: testutils.StringPtr("mary.smith@sakilacustomer.org"),
AddressID: 5,
Activebool: true,
CreateDate: *testutils.TimestampWithoutTimeZone("2006-02-14 00:00:00", 0),
LastUpdate: testutils.TimestampWithoutTimeZone("2013-05-26 14:49:45.738", 3),
Active: testutils.Int32Ptr(1),
}
var customer1 = model.Customer{
CustomerID: 2,
StoreID: 1,
FirstName: "Patricia",
LastName: "Johnson",
Email: testutils.StringPtr("patricia.johnson@sakilacustomer.org"),
AddressID: 6,
Activebool: true,
CreateDate: *testutils.TimestampWithoutTimeZone("2006-02-14 00:00:00", 0),
LastUpdate: testutils.TimestampWithoutTimeZone("2013-05-26 14:49:45.738", 3),
Active: testutils.Int32Ptr(1),
}
var lastCustomer = model.Customer{
CustomerID: 599,
StoreID: 2,
FirstName: "Austin",
LastName: "Cintron",
Email: testutils.StringPtr("austin.cintron@sakilacustomer.org"),
AddressID: 605,
Activebool: true,
CreateDate: *testutils.TimestampWithoutTimeZone("2006-02-14 00:00:00", 0),
LastUpdate: testutils.TimestampWithoutTimeZone("2013-05-26 14:49:45.738", 3),
Active: testutils.Int32Ptr(1),
}

View file

@ -2,6 +2,7 @@ package postgres
import ( import (
"context" "context"
"database/sql"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
. "github.com/go-jet/jet/v2/postgres" . "github.com/go-jet/jet/v2/postgres"
model2 "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model" model2 "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model"
@ -14,9 +15,7 @@ import (
) )
func TestUpdateValues(t *testing.T) { func TestUpdateValues(t *testing.T) {
setupLinkTableForUpdateTest(t) t.Run("deprecated update", func(t *testing.T) {
t.Run("deprecated version", func(t *testing.T) {
query := Link. query := Link.
UPDATE(Link.Name, Link.URL). UPDATE(Link.Name, Link.URL).
SET("Bong", "http://bong.com"). SET("Bong", "http://bong.com").
@ -25,31 +24,34 @@ func TestUpdateValues(t *testing.T) {
testutils.AssertDebugStatementSql(t, query, ` testutils.AssertDebugStatementSql(t, query, `
UPDATE test_sample.link UPDATE test_sample.link
SET (name, url) = ('Bong', 'http://bong.com') SET (name, url) = ('Bong', 'http://bong.com')
WHERE link.name = 'Bing'; WHERE link.name = 'Bing'::text;
`, "Bong", "http://bong.com", "Bing") `, "Bong", "http://bong.com", "Bing")
testutils.AssertExec(t, query, db, 1) testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
requireLogged(t, query)
links := []model.Link{} testutils.AssertExec(t, query, tx, 1)
requireLogged(t, query)
selQuery := Link. var links []model.Link
SELECT(Link.AllColumns).
WHERE(Link.Name.IN(String("Bong")))
err := selQuery.Query(db, &links) selQuery := Link.
SELECT(Link.AllColumns).
WHERE(Link.Name.IN(String("Bong")))
require.NoError(t, err) err := selQuery.Query(tx, &links)
require.Equal(t, len(links), 1)
testutils.AssertDeepEqual(t, links[0], model.Link{ require.NoError(t, err)
ID: 204, require.Equal(t, len(links), 1)
URL: "http://bong.com", testutils.AssertDeepEqual(t, links[0], model.Link{
Name: "Bong", ID: 204,
URL: "http://bong.com",
Name: "Bong",
})
requireLogged(t, selQuery)
}) })
requireLogged(t, selQuery)
}) })
t.Run("new version", func(t *testing.T) { t.Run("new type safe update", func(t *testing.T) {
stmt := Link.UPDATE(). stmt := Link.UPDATE().
SET( SET(
Link.Name.SET(String("DuckDuckGo")), Link.Name.SET(String("DuckDuckGo")),
@ -59,18 +61,16 @@ WHERE link.name = 'Bing';
testutils.AssertDebugStatementSql(t, stmt, ` testutils.AssertDebugStatementSql(t, stmt, `
UPDATE test_sample.link UPDATE test_sample.link
SET name = 'DuckDuckGo', SET name = 'DuckDuckGo'::text,
url = 'www.duckduckgo.com' url = 'www.duckduckgo.com'::text
WHERE link.name = 'Yahoo'; WHERE link.name = 'Yahoo'::text;
`) `)
testutils.AssertExec(t, stmt, db, 1) testutils.AssertExecAndRollback(t, stmt, db, 1)
requireLogged(t, stmt) requireLogged(t, stmt)
}) })
} }
func TestUpdateWithSubQueries(t *testing.T) { func TestUpdateWithSubQueries(t *testing.T) {
setupLinkTableForUpdateTest(t)
t.Run("deprecated version", func(t *testing.T) { t.Run("deprecated version", func(t *testing.T) {
query := Link. query := Link.
UPDATE(Link.Name, Link.URL). UPDATE(Link.Name, Link.URL).
@ -82,20 +82,19 @@ func TestUpdateWithSubQueries(t *testing.T) {
). ).
WHERE(Link.Name.EQ(String("Bing"))) WHERE(Link.Name.EQ(String("Bing")))
expectedSQL := ` testutils.AssertDebugStatementSql(t, query, `
UPDATE test_sample.link UPDATE test_sample.link
SET (name, url) = (( SET (name, url) = ((
SELECT 'Bong' SELECT 'Bong'::text
), ( ), (
SELECT link.url AS "link.url" SELECT link.url AS "link.url"
FROM test_sample.link FROM test_sample.link
WHERE link.name = 'Bing' WHERE link.name = 'Bing'::text
)) ))
WHERE link.name = 'Bing'; WHERE link.name = 'Bing'::text;
` `, "Bong", "Bing", "Bing")
testutils.AssertDebugStatementSql(t, query, expectedSQL, "Bong", "Bing", "Bing")
AssertExec(t, query, 1) testutils.AssertExecAndRollback(t, query, db, 1)
requireLogged(t, query) requireLogged(t, query)
}) })
@ -113,50 +112,48 @@ WHERE link.name = 'Bing';
testutils.AssertStatementSql(t, query, ` testutils.AssertStatementSql(t, query, `
UPDATE test_sample.link UPDATE test_sample.link
SET name = $1, SET name = $1::text,
url = ( url = (
SELECT link.url AS "link.url" SELECT link.url AS "link.url"
FROM test_sample.link FROM test_sample.link
WHERE link.name = $2 WHERE link.name = $2::text
) )
WHERE link.name = $3; WHERE link.name = $3::text;
`, "Bong", "Bing", "Bing") `, "Bong", "Bing", "Bing")
_, err := query.Exec(db) testutils.AssertExecAndRollback(t, query, db)
require.NoError(t, err)
requireLogged(t, query) requireLogged(t, query)
}) })
} }
func TestUpdateAndReturning(t *testing.T) { func TestUpdateAndReturning(t *testing.T) {
setupLinkTableForUpdateTest(t)
expectedSQL := `
UPDATE test_sample.link
SET (name, url) = ('DuckDuckGo', 'http://www.duckduckgo.com')
WHERE link.name = 'Ask'
RETURNING link.id AS "link.id",
link.url AS "link.url",
link.name AS "link.name",
link.description AS "link.description";
`
stmt := Link. stmt := Link.
UPDATE(Link.Name, Link.URL). UPDATE(Link.Name, Link.URL).
SET("DuckDuckGo", "http://www.duckduckgo.com"). SET("DuckDuckGo", "http://www.duckduckgo.com").
WHERE(Link.Name.EQ(String("Ask"))). WHERE(Link.Name.EQ(String("Ask"))).
RETURNING(Link.AllColumns) RETURNING(Link.AllColumns)
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, "DuckDuckGo", "http://www.duckduckgo.com", "Ask") testutils.AssertDebugStatementSql(t, stmt, `
UPDATE test_sample.link
SET (name, url) = ('DuckDuckGo', 'http://www.duckduckgo.com')
WHERE link.name = 'Ask'::text
RETURNING link.id AS "link.id",
link.url AS "link.url",
link.name AS "link.name",
link.description AS "link.description";
`, "DuckDuckGo", "http://www.duckduckgo.com", "Ask")
links := []model.Link{} testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
links := []model.Link{}
err := stmt.Query(db, &links) err := stmt.Query(tx, &links)
require.NoError(t, err)
require.Equal(t, len(links), 2)
require.Equal(t, links[0].Name, "DuckDuckGo")
require.Equal(t, links[1].Name, "DuckDuckGo")
requireLogged(t, stmt)
})
require.NoError(t, err)
require.Equal(t, len(links), 2)
require.Equal(t, links[0].Name, "DuckDuckGo")
require.Equal(t, links[1].Name, "DuckDuckGo")
requireLogged(t, stmt)
} }
func TestUpdateWithSelect(t *testing.T) { func TestUpdateWithSelect(t *testing.T) {
@ -170,7 +167,7 @@ func TestUpdateWithSelect(t *testing.T) {
). ).
WHERE(Link.ID.EQ(Int(0))) WHERE(Link.ID.EQ(Int(0)))
expectedSQL := ` testutils.AssertDebugStatementSql(t, stmt, `
UPDATE test_sample.link UPDATE test_sample.link
SET (id, url, name, description) = ( SET (id, url, name, description) = (
SELECT link.id AS "link.id", SELECT link.id AS "link.id",
@ -181,10 +178,9 @@ SET (id, url, name, description) = (
WHERE link.id = 0 WHERE link.id = 0
) )
WHERE link.id = 0; WHERE link.id = 0;
` `, int64(0), int64(0))
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, int64(0), int64(0))
AssertExec(t, stmt, 1) testutils.AssertExecAndRollback(t, stmt, db, 1)
}) })
t.Run("new version", func(t *testing.T) { t.Run("new version", func(t *testing.T) {
@ -210,12 +206,11 @@ SET (url, name, description) = (
WHERE link.id = 0; WHERE link.id = 0;
`, int64(0), int64(0)) `, int64(0), int64(0))
AssertExec(t, stmt, 1) testutils.AssertExecAndRollback(t, stmt, db, 1)
}) })
} }
func TestUpdateWithInvalidSelect(t *testing.T) { func TestUpdateWithInvalidSelect(t *testing.T) {
t.Run("deprecated version", func(t *testing.T) { t.Run("deprecated version", func(t *testing.T) {
stmt := Link.UPDATE(Link.AllColumns). stmt := Link.UPDATE(Link.AllColumns).
SET( SET(
@ -236,7 +231,6 @@ SET (id, url, name, description) = (
WHERE link.id = 0; WHERE link.id = 0;
` `
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, int64(0), int64(0)) testutils.AssertDebugStatementSql(t, stmt, expectedSQL, int64(0), int64(0))
testutils.AssertExecErr(t, stmt, db, "pq: number of columns does not match number of values") testutils.AssertExecErr(t, stmt, db, "pq: number of columns does not match number of values")
}) })
@ -250,8 +244,6 @@ WHERE link.id = 0;
} }
func TestUpdateWithModelData(t *testing.T) { func TestUpdateWithModelData(t *testing.T) {
setupLinkTableForUpdateTest(t)
link := model.Link{ link := model.Link{
ID: 201, ID: 201,
URL: "http://www.duckduckgo.com", URL: "http://www.duckduckgo.com",
@ -261,24 +253,20 @@ func TestUpdateWithModelData(t *testing.T) {
stmt := Link. stmt := Link.
UPDATE(Link.AllColumns). UPDATE(Link.AllColumns).
MODEL(link). MODEL(link).
WHERE(Link.ID.EQ(Int32(link.ID))) WHERE(Link.ID.EQ(Int64(link.ID)))
expectedSQL := ` expectedSQL := `
UPDATE test_sample.link UPDATE test_sample.link
SET (id, url, name, description) = (201, 'http://www.duckduckgo.com', 'DuckDuckGo', NULL) SET (id, url, name, description) = (201, 'http://www.duckduckgo.com', 'DuckDuckGo', NULL)
WHERE link.id = 201::integer; WHERE link.id = 201::bigint;
` `
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, int32(201), "http://www.duckduckgo.com", "DuckDuckGo", nil, int32(201)) testutils.AssertDebugStatementSql(t, stmt, expectedSQL, int64(201), "http://www.duckduckgo.com", "DuckDuckGo", nil, int64(201))
_, err := stmt.Exec(db) testutils.AssertExecAndRollback(t, stmt, db, 1)
require.NoError(t, err)
requireQueryLogged(t, stmt, 1) requireQueryLogged(t, stmt, 1)
} }
func TestUpdateWithModelDataAndPredefinedColumnList(t *testing.T) { func TestUpdateWithModelDataAndPredefinedColumnList(t *testing.T) {
setupLinkTableForUpdateTest(t)
link := model.Link{ link := model.Link{
ID: 201, ID: 201,
URL: "http://www.duckduckgo.com", URL: "http://www.duckduckgo.com",
@ -290,27 +278,24 @@ func TestUpdateWithModelDataAndPredefinedColumnList(t *testing.T) {
stmt := Link. stmt := Link.
UPDATE(updateColumnList). UPDATE(updateColumnList).
MODEL(link). MODEL(link).
WHERE(Link.ID.EQ(Int32(link.ID))) WHERE(Link.ID.EQ(Int64(link.ID)))
var expectedSQL = ` testutils.AssertDebugStatementSql(t, stmt, `
UPDATE test_sample.link UPDATE test_sample.link
SET (description, name, url) = (NULL, 'DuckDuckGo', 'http://www.duckduckgo.com') SET (description, name, url) = (NULL, 'DuckDuckGo', 'http://www.duckduckgo.com')
WHERE link.id = 201::integer; WHERE link.id = 201::bigint;
` `,
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, nil, "DuckDuckGo", "http://www.duckduckgo.com", int32(201)) nil, "DuckDuckGo", "http://www.duckduckgo.com", int64(201))
AssertExec(t, stmt, 1) testutils.AssertExecAndRollback(t, stmt, db, 1)
} }
func TestUpdateWithInvalidModelData(t *testing.T) { func TestUpdateWithInvalidModelData(t *testing.T) {
defer func() { defer func() {
r := recover() r := recover()
require.Equal(t, r, "missing struct field for column : id") require.Equal(t, r, "missing struct field for column : id")
}() }()
setupLinkTableForUpdateTest(t)
link := struct { link := struct {
Ident int Ident int
URL string URL string
@ -323,24 +308,13 @@ func TestUpdateWithInvalidModelData(t *testing.T) {
Name: "DuckDuckGo", Name: "DuckDuckGo",
} }
stmt := Link. _ = Link.
UPDATE(Link.AllColumns). UPDATE(Link.AllColumns).
MODEL(link). MODEL(link). // panics
WHERE(Link.ID.EQ(Int(int64(link.Ident)))) WHERE(Link.ID.EQ(Int(int64(link.Ident))))
var expectedSQL = `
UPDATE test_sample.link
SET (id, url, name, description, rel) = ('http://www.duckduckgo.com', 'DuckDuckGo', NULL, NULL)
WHERE link.id = 201;
`
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, "http://www.duckduckgo.com", "DuckDuckGo", nil, nil, int64(201))
testutils.AssertExecErr(t, stmt, db, "pq: number of columns does not match number of values")
} }
func TestUpdateQueryContext(t *testing.T) { func TestUpdateQueryContext(t *testing.T) {
setupLinkTableForUpdateTest(t)
updateStmt := Link. updateStmt := Link.
UPDATE(Link.Name, Link.URL). UPDATE(Link.Name, Link.URL).
SET("Bong", "http://bong.com"). SET("Bong", "http://bong.com").
@ -351,15 +325,15 @@ func TestUpdateQueryContext(t *testing.T) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
dest := []model.Link{} testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
err := updateStmt.QueryContext(ctx, db, &dest) dest := []model.Link{}
err := updateStmt.QueryContext(ctx, tx, &dest)
require.Error(t, err, "context deadline exceeded") require.Error(t, err, "context deadline exceeded")
})
} }
func TestUpdateExecContext(t *testing.T) { func TestUpdateExecContext(t *testing.T) {
setupLinkTableForUpdateTest(t)
updateStmt := Link. updateStmt := Link.
UPDATE(Link.Name, Link.URL). UPDATE(Link.Name, Link.URL).
SET("Bong", "http://bong.com"). SET("Bong", "http://bong.com").
@ -370,15 +344,10 @@ func TestUpdateExecContext(t *testing.T) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
_, err := updateStmt.ExecContext(ctx, db) testutils.AssertExecContextErr(ctx, t, updateStmt, db, "context deadline exceeded")
require.Error(t, err, "context deadline exceeded")
} }
func TestUpdateFrom(t *testing.T) { func TestUpdateFrom(t *testing.T) {
tx := beginTx(t)
defer tx.Rollback()
stmt := table.Rental.UPDATE(). stmt := table.Rental.UPDATE().
SET( SET(
table.Rental.RentalDate.SET(Timestamp(2020, 2, 2, 0, 0, 0)), table.Rental.RentalDate.SET(Timestamp(2020, 2, 2, 0, 0, 0)),
@ -416,16 +385,17 @@ RETURNING rental.rental_id AS "rental.rental_id",
store.address_id AS "store.address_id"; store.address_id AS "store.address_id";
`) `)
var dest []struct { testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
Rental model2.Rental var dest []struct {
Store model2.Store Rental model2.Rental
} Store model2.Store
}
err := stmt.Query(tx, &dest) err := stmt.Query(tx, &dest)
require.NoError(t, err) require.NoError(t, err)
require.Len(t, dest, 3) require.Len(t, dest, 3)
testutils.AssertJSON(t, dest[0], ` testutils.AssertJSON(t, dest[0], `
{ {
"Rental": { "Rental": {
"RentalID": 4, "RentalID": 4,
@ -444,24 +414,5 @@ RETURNING rental.rental_id AS "rental.rental_id",
} }
} }
`) `)
} })
func setupLinkTableForUpdateTest(t *testing.T) {
cleanUpLinkTable(t)
_, err := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(200, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
VALUES(201, "http://www.ask.com", "Ask", DEFAULT).
VALUES(202, "http://www.ask.com", "Ask", DEFAULT).
VALUES(203, "http://www.yahoo.com", "Yahoo", DEFAULT).
VALUES(204, "http://www.bing.com", "Bing", DEFAULT).
Exec(db)
require.NoError(t, err)
}
func cleanUpLinkTable(t *testing.T) {
_, err := Link.DELETE().WHERE(Link.ID.GT(Int(0))).Exec(db)
require.NoError(t, err)
} }

View file

@ -1,57 +0,0 @@
package postgres
import (
"github.com/go-jet/jet/v2/internal/jet"
"github.com/go-jet/jet/v2/internal/testutils"
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model"
"github.com/stretchr/testify/require"
"testing"
)
func AssertExec(t *testing.T, stmt jet.Statement, rowsAffected int64) {
res, err := stmt.Exec(db)
require.NoError(t, err)
rows, err := res.RowsAffected()
require.NoError(t, err)
require.Equal(t, rows, rowsAffected)
}
var customer0 = model.Customer{
CustomerID: 1,
StoreID: 1,
FirstName: "Mary",
LastName: "Smith",
Email: testutils.StringPtr("mary.smith@sakilacustomer.org"),
AddressID: 5,
Activebool: true,
CreateDate: *testutils.TimestampWithoutTimeZone("2006-02-14 00:00:00", 0),
LastUpdate: testutils.TimestampWithoutTimeZone("2013-05-26 14:49:45.738", 3),
Active: testutils.Int32Ptr(1),
}
var customer1 = model.Customer{
CustomerID: 2,
StoreID: 1,
FirstName: "Patricia",
LastName: "Johnson",
Email: testutils.StringPtr("patricia.johnson@sakilacustomer.org"),
AddressID: 6,
Activebool: true,
CreateDate: *testutils.TimestampWithoutTimeZone("2006-02-14 00:00:00", 0),
LastUpdate: testutils.TimestampWithoutTimeZone("2013-05-26 14:49:45.738", 3),
Active: testutils.Int32Ptr(1),
}
var lastCustomer = model.Customer{
CustomerID: 599,
StoreID: 2,
FirstName: "Austin",
LastName: "Cintron",
Email: testutils.StringPtr("austin.cintron@sakilacustomer.org"),
AddressID: 605,
Activebool: true,
CreateDate: *testutils.TimestampWithoutTimeZone("2006-02-14 00:00:00", 0),
LastUpdate: testutils.TimestampWithoutTimeZone("2013-05-26 14:49:45.738", 3),
Active: testutils.Int32Ptr(1),
}

View file

@ -106,9 +106,11 @@ func TestWithStatementDeleteAndInsert(t *testing.T) {
removeDiscontinuedOrders.AS( removeDiscontinuedOrders.AS(
OrderDetails.DELETE(). OrderDetails.DELETE().
WHERE(OrderDetails.ProductID.IN( WHERE(OrderDetails.ProductID.IN(
SELECT(Products.ProductID). SELECT(
FROM(Products). Products.ProductID,
WHERE(Products.Discontinued.EQ(Int(1)))), ).FROM(
Products,
).WHERE(Products.Discontinued.EQ(Int(1)))),
).RETURNING(OrderDetails.ProductID), ).RETURNING(OrderDetails.ProductID),
), ),
updateDiscontinuedPrice.AS( updateDiscontinuedPrice.AS(
@ -121,7 +123,13 @@ func TestWithStatementDeleteAndInsert(t *testing.T) {
), ),
logDiscontinuedProducts.AS( logDiscontinuedProducts.AS(
ProductLogs.INSERT(ProductLogs.AllColumns). ProductLogs.INSERT(ProductLogs.AllColumns).
QUERY(SELECT(updateDiscontinuedPrice.AllColumns()).FROM(updateDiscontinuedPrice)). QUERY(
SELECT(
updateDiscontinuedPrice.AllColumns(),
).FROM(
updateDiscontinuedPrice,
),
).
RETURNING( RETURNING(
ProductLogs.ProductID, ProductLogs.ProductID,
ProductLogs.ProductName, ProductLogs.ProductName,
@ -384,7 +392,7 @@ WITH cte1 AS (
SELECT territories.territory_id AS "territories.territory_id", SELECT territories.territory_id AS "territories.territory_id",
territories.territory_description AS "territories.territory_description", territories.territory_description AS "territories.territory_description",
territories.region_id AS "territories.region_id", territories.region_id AS "territories.region_id",
$1 AS "custom_column_1" $1::text AS "custom_column_1"
FROM northwind.territories FROM northwind.territories
ORDER BY territories.territory_id ASC ORDER BY territories.territory_id ASC
),cte2 AS ( ),cte2 AS (
@ -392,7 +400,7 @@ WITH cte1 AS (
cte1."territories.territory_description" AS "territories.territory_description", cte1."territories.territory_description" AS "territories.territory_description",
cte1."territories.region_id" AS "territories.region_id", cte1."territories.region_id" AS "territories.region_id",
cte1.custom_column_1 AS "custom_column_1", cte1.custom_column_1 AS "custom_column_1",
$2 AS "custom_column_2" $2::text AS "custom_column_2"
FROM cte1 FROM cte1
) )
SELECT cte2."territories.territory_id" AS "territories.territory_id", SELECT cte2."territories.territory_id" AS "territories.territory_id",
@ -485,7 +493,7 @@ func TestRecursiveWithStatement(t *testing.T) {
Employees, Employees,
).WHERE( ).WHERE(
Employees.EmployeeID.EQ(Int(2)), Employees.EmployeeID.EQ(Int(2)),
).UNION( ).UNION_ALL(
SELECT( SELECT(
Employees.AllColumns, Employees.AllColumns,
).FROM( ).FROM(
@ -790,13 +798,13 @@ WITH suppliers_fax AS (
suppliers_fax."suppliers.contact_name" AS "suppliers.contact_name", suppliers_fax."suppliers.contact_name" AS "suppliers.contact_name",
suppliers_fax."suppliers.country" AS "suppliers.country" suppliers_fax."suppliers.country" AS "suppliers.country"
FROM suppliers_fax FROM suppliers_fax
WHERE suppliers_fax."suppliers.country" NOT IN ('US', 'Australia') WHERE suppliers_fax."suppliers.country" NOT IN ('US'::text, 'Australia'::text)
) )
SELECT not_from_us_or_aus."suppliers.supplier_id" AS "suppliers.supplier_id", SELECT not_from_us_or_aus."suppliers.supplier_id" AS "suppliers.supplier_id",
not_from_us_or_aus."suppliers.contact_name" AS "suppliers.contact_name", not_from_us_or_aus."suppliers.contact_name" AS "suppliers.contact_name",
not_from_us_or_aus."suppliers.country" AS "suppliers.country" not_from_us_or_aus."suppliers.country" AS "suppliers.country"
FROM not_from_us_or_aus FROM not_from_us_or_aus
WHERE not_from_us_or_aus."suppliers.contact_name" != 'John'; WHERE not_from_us_or_aus."suppliers.contact_name" != 'John'::text;
`) `)
var dest []model.Suppliers var dest []model.Suppliers

View file

@ -1,16 +1,17 @@
package sqlite package sqlite
import ( import (
"github.com/go-jet/jet/v2/generator/sqlite"
"github.com/go-jet/jet/v2/internal/testutils"
"github.com/go-jet/jet/v2/tests/.gentestdata/sqlite/sakila/model"
"github.com/go-jet/jet/v2/tests/internal/utils/repo"
"github.com/stretchr/testify/require"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"reflect" "reflect"
"testing" "testing"
"github.com/go-jet/jet/v2/generator/sqlite"
"github.com/go-jet/jet/v2/internal/testutils"
"github.com/go-jet/jet/v2/tests/.gentestdata/sqlite/sakila/model"
"github.com/go-jet/jet/v2/tests/internal/utils/repo"
"github.com/stretchr/testify/require"
) )
func TestGeneratedModel(t *testing.T) { func TestGeneratedModel(t *testing.T) {
@ -183,6 +184,16 @@ func (a ActorTable) FromSchema(schemaName string) *ActorTable {
return newActorTable(schemaName, a.TableName(), a.Alias()) return newActorTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new ActorTable with assigned table prefix
func (a ActorTable) WithPrefix(prefix string) *ActorTable {
return newActorTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new ActorTable with assigned table suffix
func (a ActorTable) WithSuffix(suffix string) *ActorTable {
return newActorTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newActorTable(schemaName, tableName, alias string) *ActorTable { func newActorTable(schemaName, tableName, alias string) *ActorTable {
return &ActorTable{ return &ActorTable{
actorTable: newActorTableImpl(schemaName, tableName, alias), actorTable: newActorTableImpl(schemaName, tableName, alias),
@ -264,6 +275,16 @@ func (a FilmListTable) FromSchema(schemaName string) *FilmListTable {
return newFilmListTable(schemaName, a.TableName(), a.Alias()) return newFilmListTable(schemaName, a.TableName(), a.Alias())
} }
// WithPrefix creates new FilmListTable with assigned table prefix
func (a FilmListTable) WithPrefix(prefix string) *FilmListTable {
return newFilmListTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
}
// WithSuffix creates new FilmListTable with assigned table suffix
func (a FilmListTable) WithSuffix(suffix string) *FilmListTable {
return newFilmListTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
}
func newFilmListTable(schemaName, tableName, alias string) *FilmListTable { func newFilmListTable(schemaName, tableName, alias string) *FilmListTable {
return &FilmListTable{ return &FilmListTable{
filmListTable: newFilmListTableImpl(schemaName, tableName, alias), filmListTable: newFilmListTableImpl(schemaName, tableName, alias),

View file

@ -2,6 +2,7 @@ package sqlite
import ( import (
"context" "context"
"database/sql"
"math/rand" "math/rand"
"testing" "testing"
@ -15,9 +16,6 @@ import (
) )
func TestInsertValues(t *testing.T) { func TestInsertValues(t *testing.T) {
tx := beginSampleDBTx(t)
defer tx.Rollback()
insertQuery := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description). insertQuery := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil). VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil).
VALUES(101, "http://www.google.com", "Google", "Search engine"). VALUES(101, "http://www.google.com", "Google", "Search engine").
@ -32,31 +30,32 @@ VALUES (?, ?, ?, ?),
101, "http://www.google.com", "Google", "Search engine", 101, "http://www.google.com", "Google", "Search engine",
102, "http://www.yahoo.com", "Yahoo", nil) 102, "http://www.yahoo.com", "Yahoo", nil)
_, err := insertQuery.Exec(tx) testutils.ExecuteInTxAndRollback(t, sampleDB, func(tx *sql.Tx) {
require.NoError(t, err) testutils.AssertExec(t, insertQuery, tx)
requireLogged(t, insertQuery) requireLogged(t, insertQuery)
insertedLinks := []model.Link{} var insertedLinks []model.Link
err = SELECT(Link.AllColumns). err := SELECT(Link.AllColumns).
FROM(Link). FROM(Link).
WHERE(Link.ID.GT_EQ(Int(100))). WHERE(Link.ID.GT_EQ(Int(100))).
ORDER_BY(Link.ID). ORDER_BY(Link.ID).
Query(tx, &insertedLinks) Query(tx, &insertedLinks)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(insertedLinks), 3) require.Equal(t, len(insertedLinks), 3)
testutils.AssertDeepEqual(t, insertedLinks[0], postgreTutorial) testutils.AssertDeepEqual(t, insertedLinks[0], postgreTutorial)
testutils.AssertDeepEqual(t, insertedLinks[1], model.Link{ testutils.AssertDeepEqual(t, insertedLinks[1], model.Link{
ID: 101, ID: 101,
URL: "http://www.google.com", URL: "http://www.google.com",
Name: "Google", Name: "Google",
Description: testutils.StringPtr("Search engine"), Description: testutils.StringPtr("Search engine"),
}) })
testutils.AssertDeepEqual(t, insertedLinks[2], model.Link{ testutils.AssertDeepEqual(t, insertedLinks[2], model.Link{
ID: 102, ID: 102,
URL: "http://www.yahoo.com", URL: "http://www.yahoo.com",
Name: "Yahoo", Name: "Yahoo",
})
}) })
} }
@ -67,41 +66,35 @@ var postgreTutorial = model.Link{
} }
func TestInsertEmptyColumnList(t *testing.T) { func TestInsertEmptyColumnList(t *testing.T) {
tx := beginSampleDBTx(t)
defer tx.Rollback()
expectedSQL := `
INSERT INTO link
VALUES (100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', NULL);
`
stmt := Link.INSERT(). stmt := Link.INSERT().
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil) VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil)
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, testutils.AssertDebugStatementSql(t, stmt, `
INSERT INTO link
VALUES (100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', NULL);
`,
100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil) 100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil)
_, err := stmt.Exec(tx) testutils.ExecuteInTxAndRollback(t, sampleDB, func(tx *sql.Tx) {
require.NoError(t, err) _, err := stmt.Exec(tx)
requireLogged(t, stmt) require.NoError(t, err)
requireLogged(t, stmt)
insertedLinks := []model.Link{} var insertedLinks []model.Link
err = SELECT(Link.AllColumns). err = SELECT(Link.AllColumns).
FROM(Link). FROM(Link).
WHERE(Link.ID.GT_EQ(Int(100))). WHERE(Link.ID.GT_EQ(Int(100))).
ORDER_BY(Link.ID). ORDER_BY(Link.ID).
Query(tx, &insertedLinks) Query(tx, &insertedLinks)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(insertedLinks), 1) require.Equal(t, len(insertedLinks), 1)
testutils.AssertDeepEqual(t, insertedLinks[0], postgreTutorial) testutils.AssertDeepEqual(t, insertedLinks[0], postgreTutorial)
})
} }
func TestInsertModelObject(t *testing.T) { func TestInsertModelObject(t *testing.T) {
tx := beginSampleDBTx(t)
defer tx.Rollback()
linkData := model.Link{ linkData := model.Link{
URL: "http://www.duckduckgo.com", URL: "http://www.duckduckgo.com",
Name: "Duck Duck go", Name: "Duck Duck go",
@ -115,19 +108,13 @@ INSERT INTO link (url, name)
VALUES ('http://www.duckduckgo.com', 'Duck Duck go'); VALUES ('http://www.duckduckgo.com', 'Duck Duck go');
`, "http://www.duckduckgo.com", "Duck Duck go") `, "http://www.duckduckgo.com", "Duck Duck go")
_, err := query.Exec(tx) testutils.ExecuteInTxAndRollback(t, sampleDB, func(tx *sql.Tx) {
require.NoError(t, err) _, err := query.Exec(tx)
require.NoError(t, err)
})
} }
func TestInsertModelObjectEmptyColumnList(t *testing.T) { func TestInsertModelObjectEmptyColumnList(t *testing.T) {
tx := beginSampleDBTx(t)
defer tx.Rollback()
var expectedSQL = `
INSERT INTO link
VALUES (1000, 'http://www.duckduckgo.com', 'Duck Duck go', NULL);
`
linkData := model.Link{ linkData := model.Link{
ID: 1000, ID: 1000,
URL: "http://www.duckduckgo.com", URL: "http://www.duckduckgo.com",
@ -138,23 +125,18 @@ VALUES (1000, 'http://www.duckduckgo.com', 'Duck Duck go', NULL);
INSERT(). INSERT().
MODEL(linkData) MODEL(linkData)
testutils.AssertDebugStatementSql(t, query, expectedSQL, int32(1000), "http://www.duckduckgo.com", "Duck Duck go", nil) testutils.AssertDebugStatementSql(t, query, `
INSERT INTO link
VALUES (1000, 'http://www.duckduckgo.com', 'Duck Duck go', NULL);
`, int32(1000), "http://www.duckduckgo.com", "Duck Duck go", nil)
_, err := query.Exec(tx) testutils.ExecuteInTxAndRollback(t, sampleDB, func(tx *sql.Tx) {
require.NoError(t, err) _, err := query.Exec(tx)
require.NoError(t, err)
})
} }
func TestInsertModelsObject(t *testing.T) { func TestInsertModelsObject(t *testing.T) {
tx := beginSampleDBTx(t)
defer tx.Rollback()
expectedSQL := `
INSERT INTO link (url, name)
VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial'),
('http://www.google.com', 'Google'),
('http://www.yahoo.com', 'Yahoo');
`
tutorial := model.Link{ tutorial := model.Link{
URL: "http://www.postgresqltutorial.com", URL: "http://www.postgresqltutorial.com",
Name: "PostgreSQL Tutorial", Name: "PostgreSQL Tutorial",
@ -176,27 +158,20 @@ VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial'),
yahoo, yahoo,
}) })
testutils.AssertDebugStatementSql(t, query, expectedSQL, testutils.AssertDebugStatementSql(t, query, `
INSERT INTO link (url, name)
VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial'),
('http://www.google.com', 'Google'),
('http://www.yahoo.com', 'Yahoo');
`,
"http://www.postgresqltutorial.com", "PostgreSQL Tutorial", "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
"http://www.google.com", "Google", "http://www.google.com", "Google",
"http://www.yahoo.com", "Yahoo") "http://www.yahoo.com", "Yahoo")
_, err := query.Exec(tx) testutils.AssertExecAndRollback(t, query, sampleDB)
require.NoError(t, err)
} }
func TestInsertUsingMutableColumns(t *testing.T) { func TestInsertUsingMutableColumns(t *testing.T) {
tx := beginSampleDBTx(t)
defer tx.Rollback()
var expectedSQL = `
INSERT INTO link (url, name, description)
VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', NULL),
('http://www.google.com', 'Google', NULL),
('http://www.google.com', 'Google', NULL),
('http://www.yahoo.com', 'Yahoo', NULL);
`
google := model.Link{ google := model.Link{
URL: "http://www.google.com", URL: "http://www.google.com",
Name: "Google", Name: "Google",
@ -213,20 +188,22 @@ VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', NULL),
MODEL(google). MODEL(google).
MODELS([]model.Link{google, yahoo}) MODELS([]model.Link{google, yahoo})
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, testutils.AssertDebugStatementSql(t, stmt, `
INSERT INTO link (url, name, description)
VALUES ('http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', NULL),
('http://www.google.com', 'Google', NULL),
('http://www.google.com', 'Google', NULL),
('http://www.yahoo.com', 'Yahoo', NULL);
`,
"http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil,
"http://www.google.com", "Google", nil, "http://www.google.com", "Google", nil,
"http://www.google.com", "Google", nil, "http://www.google.com", "Google", nil,
"http://www.yahoo.com", "Yahoo", nil) "http://www.yahoo.com", "Yahoo", nil)
_, err := stmt.Exec(tx) testutils.AssertExecAndRollback(t, stmt, sampleDB)
require.NoError(t, err)
} }
func TestInsertQuery(t *testing.T) { func TestInsertQuery(t *testing.T) {
tx := beginSampleDBTx(t)
defer tx.Rollback()
var expectedSQL = ` var expectedSQL = `
INSERT INTO link (url, name) INSERT INTO link (url, name)
SELECT link.url AS "link.url", SELECT link.url AS "link.url",
@ -242,24 +219,22 @@ WHERE link.id = 24;
) )
testutils.AssertDebugStatementSql(t, query, expectedSQL, int64(24)) testutils.AssertDebugStatementSql(t, query, expectedSQL, int64(24))
testutils.ExecuteInTxAndRollback(t, sampleDB, func(tx *sql.Tx) {
_, err := query.Exec(tx)
require.NoError(t, err)
_, err := query.Exec(tx) var youtubeLinks []model.Link
require.NoError(t, err) err = Link.
SELECT(Link.AllColumns).
WHERE(Link.Name.EQ(String("Bing"))).
Query(tx, &youtubeLinks)
youtubeLinks := []model.Link{} require.NoError(t, err)
err = Link. require.Equal(t, len(youtubeLinks), 2)
SELECT(Link.AllColumns). })
WHERE(Link.Name.EQ(String("Bing"))).
Query(tx, &youtubeLinks)
require.NoError(t, err)
require.Equal(t, len(youtubeLinks), 2)
} }
func TestInsert_DEFAULT_VALUES_RETURNING(t *testing.T) { func TestInsert_DEFAULT_VALUES_RETURNING(t *testing.T) {
tx := beginSampleDBTx(t)
defer tx.Rollback()
stmt := Link.INSERT(). stmt := Link.INSERT().
DEFAULT_VALUES(). DEFAULT_VALUES().
RETURNING(Link.AllColumns) RETURNING(Link.AllColumns)
@ -273,24 +248,23 @@ RETURNING link.id AS "link.id",
link.description AS "link.description"; link.description AS "link.description";
`) `)
var link model.Link testutils.ExecuteInTxAndRollback(t, sampleDB, func(tx *sql.Tx) {
err := stmt.Query(tx, &link) var link model.Link
require.NoError(t, err) err := stmt.Query(tx, &link)
require.NoError(t, err)
require.EqualValues(t, link, model.Link{ require.EqualValues(t, link, model.Link{
ID: 25, ID: 25,
URL: "www.", URL: "www.",
Name: "_", Name: "_",
Description: nil, Description: nil,
})
}) })
} }
func TestInsertOnConflict(t *testing.T) { func TestInsertOnConflict(t *testing.T) {
t.Run("do nothing", func(t *testing.T) { t.Run("do nothing", func(t *testing.T) {
tx := beginSampleDBTx(t)
defer tx.Rollback()
link := model.Link{ID: rand.Int31()} link := model.Link{ID: rand.Int31()}
stmt := Link.INSERT(Link.AllColumns). stmt := Link.INSERT(Link.AllColumns).
@ -304,14 +278,11 @@ VALUES (?, ?, ?, ?),
(?, ?, ?, ?) (?, ?, ?, ?)
ON CONFLICT (id) DO NOTHING; ON CONFLICT (id) DO NOTHING;
`) `)
testutils.AssertExec(t, stmt, tx, 1) testutils.AssertExecAndRollback(t, stmt, sampleDB, 1)
requireLogged(t, stmt) requireLogged(t, stmt)
}) })
t.Run("do update", func(t *testing.T) { t.Run("do update", func(t *testing.T) {
tx := beginSampleDBTx(t)
defer tx.Rollback()
stmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description). stmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(21, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil). VALUES(21, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil).
VALUES(22, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil). VALUES(22, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil).
@ -336,14 +307,11 @@ RETURNING link.id AS "link.id",
link.description AS "link.description"; link.description AS "link.description";
`) `)
testutils.AssertExec(t, stmt, tx) testutils.AssertExecAndRollback(t, stmt, sampleDB)
requireLogged(t, stmt) requireLogged(t, stmt)
}) })
t.Run("do update complex", func(t *testing.T) { t.Run("do update complex", func(t *testing.T) {
tx := beginSampleDBTx(t)
defer tx.Rollback()
stmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description). stmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(21, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil). VALUES(21, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", nil).
ON_CONFLICT(Link.ID). ON_CONFLICT(Link.ID).
@ -370,7 +338,7 @@ ON CONFLICT (id) WHERE (id * 2) > 10 DO UPDATE
WHERE link.description IS NOT NULL; WHERE link.description IS NOT NULL;
`) `)
testutils.AssertExec(t, stmt, tx) testutils.AssertExecAndRollback(t, stmt, sampleDB)
requireLogged(t, stmt) requireLogged(t, stmt)
}) })
} }
@ -384,7 +352,7 @@ func TestInsertContextDeadlineExceeded(t *testing.T) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
dest := []model.Link{} var dest []model.Link
err := stmt.QueryContext(ctx, sampleDB, &dest) err := stmt.QueryContext(ctx, sampleDB, &dest)
require.Error(t, err, "context deadline exceeded") require.Error(t, err, "context deadline exceeded")

View file

@ -35,6 +35,7 @@ func TestMain(m *testing.M) {
var err error var err error
db, err = sql.Open("sqlite3", "file:"+dbconfig.SakilaDBPath) db, err = sql.Open("sqlite3", "file:"+dbconfig.SakilaDBPath)
throw.OnError(err) throw.OnError(err)
defer db.Close()
_, err = db.Exec(fmt.Sprintf("ATTACH DATABASE '%s' as 'chinook';", dbconfig.ChinookDBPath)) _, err = db.Exec(fmt.Sprintf("ATTACH DATABASE '%s' as 'chinook';", dbconfig.ChinookDBPath))
throw.OnError(err) throw.OnError(err)
@ -42,8 +43,6 @@ func TestMain(m *testing.M) {
sampleDB, err = sql.Open("sqlite3", dbconfig.TestSampleDBPath) sampleDB, err = sql.Open("sqlite3", dbconfig.TestSampleDBPath)
throw.OnError(err) throw.OnError(err)
defer db.Close()
ret := m.Run() ret := m.Run()
if ret != 0 { if ret != 0 {

@ -1 +1 @@
Subproject commit 895bf5760d055c717df77c3b872af276f34d06f1 Subproject commit fdb0cc598d2b534310d2b559ce9a2f75b5507c56