commit
24857bc067
41 changed files with 692 additions and 183 deletions
|
|
@ -2,10 +2,9 @@
|
||||||
|
|
||||||
[](https://circleci.com/gh/go-jet/jet/tree/develop)
|
[](https://circleci.com/gh/go-jet/jet/tree/develop)
|
||||||
[](https://codecov.io/gh/go-jet/jet)
|
[](https://codecov.io/gh/go-jet/jet)
|
||||||
[](https://goreportcard.com/report/github.com/go-jet/jet)
|
[](https://goreportcard.com/report/github.com/go-jet/jet/v2)
|
||||||
[](http://godoc.org/github.com/go-jet/jet)
|
[](http://godoc.org/github.com/go-jet/jet/v2)
|
||||||
[](https://github.com/go-jet/jet/v2/releases)
|
[](https://github.com/go-jet/jet/releases)
|
||||||
[](https://gitter.im/go-jet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
||||||
30
doc.go
30
doc.go
|
|
@ -3,44 +3,45 @@ Package jet is a complete solution for efficient and high performance database a
|
||||||
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, MariaDB and SQLite. Future releases will add support for additional databases.
|
||||||
|
|
||||||
|
# Installation
|
||||||
Installation
|
|
||||||
|
|
||||||
|
|
||||||
Use the command bellow to add jet as a dependency into go.mod project:
|
Use the command bellow to add jet as a dependency into go.mod project:
|
||||||
|
|
||||||
$ go get -u github.com/go-jet/jet/v2
|
$ 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:
|
1. (Go1.16+) Install jet generator using go install:
|
||||||
go install github.com/go-jet/jet/v2/cmd/jet@latest
|
go install github.com/go-jet/jet/v2/cmd/jet@latest
|
||||||
|
|
||||||
2) Install jet generator to GOPATH/bin folder:
|
2. Install jet generator to GOPATH/bin folder:
|
||||||
cd $GOPATH/src/ && GO111MODULE=off go get -u github.com/go-jet/jet/cmd/jet
|
cd $GOPATH/src/ && GO111MODULE=off go get -u github.com/go-jet/jet/cmd/jet
|
||||||
|
|
||||||
3) Install jet generator into specific folder:
|
3. Install jet generator into specific folder:
|
||||||
git clone https://github.com/go-jet/jet.git
|
git clone https://github.com/go-jet/jet.git
|
||||||
cd jet && go build -o dir_path ./cmd/jet
|
cd jet && go build -o dir_path ./cmd/jet
|
||||||
|
|
||||||
Make sure that the destination folder is added to the PATH environment variable.
|
Make sure that the destination folder is added to the PATH environment variable.
|
||||||
|
|
||||||
|
# Usage
|
||||||
Usage
|
|
||||||
|
|
||||||
|
|
||||||
Jet requires already defined database schema(with tables, enums etc), so that jet generator can generate SQL Builder
|
Jet requires already defined database schema(with tables, enums etc), so that jet generator can generate SQL Builder
|
||||||
and Model files. File generation is very fast, and can be added as every pre-build step.
|
and Model files. File generation is very fast, and can be added as every pre-build step.
|
||||||
Sample command:
|
Sample command:
|
||||||
|
|
||||||
jet -dsn=postgresql://user:pass@localhost:5432/jetdb -schema=dvds -path=./.gen
|
jet -dsn=postgresql://user:pass@localhost:5432/jetdb -schema=dvds -path=./.gen
|
||||||
|
|
||||||
Before we can write SQL queries in Go, we need to import generated SQL builder and model types:
|
Before we can write SQL queries in Go, we need to import generated SQL builder and model types:
|
||||||
|
|
||||||
import . "some_path/.gen/jetdb/dvds/table"
|
import . "some_path/.gen/jetdb/dvds/table"
|
||||||
import "some_path/.gen/jetdb/dvds/model"
|
import "some_path/.gen/jetdb/dvds/model"
|
||||||
|
|
||||||
To write postgres SQL queries we import:
|
To write postgres SQL queries we import:
|
||||||
|
|
||||||
. "github.com/go-jet/jet/v2/postgres" // Dot import is used so that Go code resemble as much as native SQL. It is not mandatory.
|
. "github.com/go-jet/jet/v2/postgres" // Dot import is used so that Go code resemble as much as native SQL. It is not mandatory.
|
||||||
|
|
||||||
Then we can write the SQL query:
|
Then we can write the SQL query:
|
||||||
|
|
||||||
// sub-query
|
// sub-query
|
||||||
rRatingFilms :=
|
rRatingFilms :=
|
||||||
SELECT(
|
SELECT(
|
||||||
|
|
@ -72,6 +73,7 @@ Then we can write the SQL query:
|
||||||
)
|
)
|
||||||
|
|
||||||
Now we can run the statement and store the result into desired destination:
|
Now we can run the statement and store the result into desired destination:
|
||||||
|
|
||||||
var dest []struct {
|
var dest []struct {
|
||||||
model.Film
|
model.Film
|
||||||
|
|
||||||
|
|
@ -81,9 +83,11 @@ Now we can run the statement and store the result into desired destination:
|
||||||
err := stmt.Query(db, &dest)
|
err := stmt.Query(db, &dest)
|
||||||
|
|
||||||
We can print a statement to see SQL query and arguments sent to postgres server:
|
We can print a statement to see SQL query and arguments sent to postgres server:
|
||||||
|
|
||||||
fmt.Println(stmt.Sql())
|
fmt.Println(stmt.Sql())
|
||||||
|
|
||||||
Output:
|
Output:
|
||||||
|
|
||||||
SELECT "rFilms"."film.film_id" AS "film.film_id",
|
SELECT "rFilms"."film.film_id" AS "film.film_id",
|
||||||
"rFilms"."film.title" AS "film.title",
|
"rFilms"."film.title" AS "film.title",
|
||||||
"rFilms"."film.rating" AS "film.rating",
|
"rFilms"."film.rating" AS "film.rating",
|
||||||
|
|
|
||||||
|
|
@ -26,80 +26,6 @@ import (
|
||||||
|
|
||||||
var {{tableTemplate.InstanceName}} = new{{tableTemplate.TypeName}}("{{schemaName}}", "{{.Name}}", "")
|
var {{tableTemplate.InstanceName}} = new{{tableTemplate.TypeName}}("{{schemaName}}", "{{.Name}}", "")
|
||||||
|
|
||||||
type {{tableTemplate.TypeName}} struct {
|
|
||||||
{{dialect.PackageName}}.Table
|
|
||||||
|
|
||||||
//Columns
|
|
||||||
{{- range $i, $c := .Columns}}
|
|
||||||
{{- $field := columnField $c}}
|
|
||||||
{{$field.Name}} {{dialect.PackageName}}.Column{{$field.Type}}
|
|
||||||
{{- end}}
|
|
||||||
|
|
||||||
AllColumns {{dialect.PackageName}}.ColumnList
|
|
||||||
MutableColumns {{dialect.PackageName}}.ColumnList
|
|
||||||
}
|
|
||||||
|
|
||||||
// AS creates new {{tableTemplate.TypeName}} with assigned alias
|
|
||||||
func (a {{tableTemplate.TypeName}}) AS(alias string) {{tableTemplate.TypeName}} {
|
|
||||||
return new{{tableTemplate.TypeName}}(a.SchemaName(), a.TableName(), alias)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schema creates new {{tableTemplate.TypeName}} with assigned schema name
|
|
||||||
func (a {{tableTemplate.TypeName}}) FromSchema(schemaName string) {{tableTemplate.TypeName}} {
|
|
||||||
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}} {
|
|
||||||
var (
|
|
||||||
{{- range $i, $c := .Columns}}
|
|
||||||
{{- $field := columnField $c}}
|
|
||||||
{{$field.Name}}Column = {{dialect.PackageName}}.{{$field.Type}}Column("{{$c.Name}}")
|
|
||||||
{{- end}}
|
|
||||||
allColumns = {{dialect.PackageName}}.ColumnList{ {{template "column-list" .Columns}} }
|
|
||||||
mutableColumns = {{dialect.PackageName}}.ColumnList{ {{template "column-list" .MutableColumns}} }
|
|
||||||
)
|
|
||||||
|
|
||||||
return {{tableTemplate.TypeName}}{
|
|
||||||
Table: {{dialect.PackageName}}.NewTable(schemaName, tableName, alias, allColumns...),
|
|
||||||
|
|
||||||
//Columns
|
|
||||||
{{- range $i, $c := .Columns}}
|
|
||||||
{{- $field := columnField $c}}
|
|
||||||
{{$field.Name}}: {{$field.Name}}Column,
|
|
||||||
{{- end}}
|
|
||||||
|
|
||||||
AllColumns: allColumns,
|
|
||||||
MutableColumns: mutableColumns,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
var tableSQLBuilderTemplateWithEXCLUDED = `
|
|
||||||
{{define "column-list" -}}
|
|
||||||
{{- range $i, $c := . }}
|
|
||||||
{{- $field := columnField $c}}
|
|
||||||
{{- if gt $i 0 }}, {{end}}{{$field.Name}}Column
|
|
||||||
{{- end}}
|
|
||||||
{{- end}}
|
|
||||||
|
|
||||||
package {{package}}
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/go-jet/jet/v2/{{dialect.PackageName}}"
|
|
||||||
)
|
|
||||||
|
|
||||||
var {{tableTemplate.InstanceName}} = new{{tableTemplate.TypeName}}("{{schemaName}}", "{{.Name}}", "")
|
|
||||||
|
|
||||||
type {{structImplName}} struct {
|
type {{structImplName}} struct {
|
||||||
{{dialect.PackageName}}.Table
|
{{dialect.PackageName}}.Table
|
||||||
|
|
||||||
|
|
@ -116,7 +42,7 @@ type {{structImplName}} struct {
|
||||||
type {{tableTemplate.TypeName}} struct {
|
type {{tableTemplate.TypeName}} struct {
|
||||||
{{structImplName}}
|
{{structImplName}}
|
||||||
|
|
||||||
EXCLUDED {{structImplName}}
|
{{toUpper insertedRowAlias}} {{structImplName}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AS creates new {{tableTemplate.TypeName}} with assigned alias
|
// AS creates new {{tableTemplate.TypeName}} with assigned alias
|
||||||
|
|
@ -142,7 +68,7 @@ func (a {{tableTemplate.TypeName}}) WithSuffix(suffix string) *{{tableTemplate.T
|
||||||
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),
|
||||||
EXCLUDED: new{{tableTemplate.TypeName}}Impl("", "excluded", ""),
|
{{toUpper insertedRowAlias}}: new{{tableTemplate.TypeName}}Impl("", "{{insertedRowAlias}}", ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,29 +120,29 @@ func processTableSQLBuilder(fileTypes, dirPath string,
|
||||||
|
|
||||||
for _, tableMetaData := range tablesMetaData {
|
for _, tableMetaData := range tablesMetaData {
|
||||||
|
|
||||||
var tableSQLBuilderTemplate TableSQLBuilder
|
var tableSQLBuilder TableSQLBuilder
|
||||||
|
|
||||||
if fileTypes == "view" {
|
if fileTypes == "view" {
|
||||||
tableSQLBuilderTemplate = sqlBuilderTemplate.View(tableMetaData)
|
tableSQLBuilder = sqlBuilderTemplate.View(tableMetaData)
|
||||||
} else {
|
} else {
|
||||||
tableSQLBuilderTemplate = sqlBuilderTemplate.Table(tableMetaData)
|
tableSQLBuilder = sqlBuilderTemplate.Table(tableMetaData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tableSQLBuilderTemplate.Skip {
|
if tableSQLBuilder.Skip {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tableSQLBuilderPath := path.Join(dirPath, tableSQLBuilderTemplate.Path)
|
tableSQLBuilderPath := path.Join(dirPath, tableSQLBuilder.Path)
|
||||||
|
|
||||||
err := utils.EnsureDirPath(tableSQLBuilderPath)
|
err := utils.EnsureDirPath(tableSQLBuilderPath)
|
||||||
throw.OnError(err)
|
throw.OnError(err)
|
||||||
|
|
||||||
text, err := generateTemplate(
|
text, err := generateTemplate(
|
||||||
autoGenWarningTemplate+getTableSQLBuilderTemplate(dialect),
|
autoGenWarningTemplate+tableSQLBuilderTemplate,
|
||||||
tableMetaData,
|
tableMetaData,
|
||||||
template.FuncMap{
|
template.FuncMap{
|
||||||
"package": func() string {
|
"package": func() string {
|
||||||
return tableSQLBuilderTemplate.PackageName()
|
return tableSQLBuilder.PackageName()
|
||||||
},
|
},
|
||||||
"dialect": func() jet.Dialect {
|
"dialect": func() jet.Dialect {
|
||||||
return dialect
|
return dialect
|
||||||
|
|
@ -151,29 +151,33 @@ func processTableSQLBuilder(fileTypes, dirPath string,
|
||||||
return schemaMetaData.Name
|
return schemaMetaData.Name
|
||||||
},
|
},
|
||||||
"tableTemplate": func() TableSQLBuilder {
|
"tableTemplate": func() TableSQLBuilder {
|
||||||
return tableSQLBuilderTemplate
|
return tableSQLBuilder
|
||||||
},
|
},
|
||||||
"structImplName": func() string { // postgres only
|
"structImplName": func() string { // postgres only
|
||||||
structName := tableSQLBuilderTemplate.TypeName
|
structName := tableSQLBuilder.TypeName
|
||||||
return string(strings.ToLower(structName)[0]) + structName[1:]
|
return string(strings.ToLower(structName)[0]) + structName[1:]
|
||||||
},
|
},
|
||||||
"columnField": func(columnMetaData metadata.Column) TableSQLBuilderColumn {
|
"columnField": func(columnMetaData metadata.Column) TableSQLBuilderColumn {
|
||||||
return tableSQLBuilderTemplate.Column(columnMetaData)
|
return tableSQLBuilder.Column(columnMetaData)
|
||||||
|
},
|
||||||
|
"toUpper": strings.ToUpper,
|
||||||
|
"insertedRowAlias": func() string {
|
||||||
|
return insertedRowAlias(dialect)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
throw.OnError(err)
|
throw.OnError(err)
|
||||||
|
|
||||||
err = utils.SaveGoFile(tableSQLBuilderPath, tableSQLBuilderTemplate.FileName, text)
|
err = utils.SaveGoFile(tableSQLBuilderPath, tableSQLBuilder.FileName, text)
|
||||||
throw.OnError(err)
|
throw.OnError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTableSQLBuilderTemplate(dialect jet.Dialect) string {
|
func insertedRowAlias(dialect jet.Dialect) string {
|
||||||
if dialect.Name() == "PostgreSQL" || dialect.Name() == "SQLite" {
|
if dialect.Name() == "MySQL" {
|
||||||
return tableSQLBuilderTemplateWithEXCLUDED
|
return "new"
|
||||||
}
|
}
|
||||||
|
|
||||||
return tableSQLBuilderTemplate
|
return "excluded"
|
||||||
}
|
}
|
||||||
|
|
||||||
func processTableModels(fileTypes, modelDirPath string, tablesMetaData []metadata.Table, modelTemplate Model) {
|
func processTableModels(fileTypes, modelDirPath string, tablesMetaData []metadata.Table, modelTemplate Model) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package jet
|
package jet
|
||||||
|
|
||||||
//BoolExpression interface
|
// BoolExpression interface
|
||||||
type BoolExpression interface {
|
type BoolExpression interface {
|
||||||
Expression
|
Expression
|
||||||
|
|
||||||
|
|
@ -84,22 +84,18 @@ func (b *boolInterfaceImpl) IS_NOT_UNKNOWN() BoolExpression {
|
||||||
return newPostfixBoolOperatorExpression(b.parent, "IS NOT UNKNOWN")
|
return newPostfixBoolOperatorExpression(b.parent, "IS NOT UNKNOWN")
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------//
|
|
||||||
func newBinaryBoolOperatorExpression(lhs, rhs Expression, operator string, additionalParams ...Expression) BoolExpression {
|
func newBinaryBoolOperatorExpression(lhs, rhs Expression, operator string, additionalParams ...Expression) BoolExpression {
|
||||||
return BoolExp(NewBinaryOperatorExpression(lhs, rhs, operator, additionalParams...))
|
return BoolExp(NewBinaryOperatorExpression(lhs, rhs, operator, additionalParams...))
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------//
|
|
||||||
func newPrefixBoolOperatorExpression(expression Expression, operator string) BoolExpression {
|
func newPrefixBoolOperatorExpression(expression Expression, operator string) BoolExpression {
|
||||||
return BoolExp(newPrefixOperatorExpression(expression, operator))
|
return BoolExp(newPrefixOperatorExpression(expression, operator))
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------//
|
|
||||||
func newPostfixBoolOperatorExpression(expression Expression, operator string) BoolExpression {
|
func newPostfixBoolOperatorExpression(expression Expression, operator string) BoolExpression {
|
||||||
return BoolExp(newPostfixOperatorExpression(expression, operator))
|
return BoolExp(newPostfixOperatorExpression(expression, operator))
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------//
|
|
||||||
type boolExpressionWrapper struct {
|
type boolExpressionWrapper struct {
|
||||||
boolInterfaceImpl
|
boolInterfaceImpl
|
||||||
Expression
|
Expression
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,35 @@ type ClauseWithProjections interface {
|
||||||
Projections() ProjectionList
|
Projections() ProjectionList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OptimizerHint provides a way to optimize query execution per-statement basis
|
||||||
|
type OptimizerHint string
|
||||||
|
|
||||||
|
type optimizerHints []OptimizerHint
|
||||||
|
|
||||||
|
func (o optimizerHints) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
|
if len(o) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("/*+")
|
||||||
|
for i, hint := range o {
|
||||||
|
if i > 0 {
|
||||||
|
out.WriteByte(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString(string(hint))
|
||||||
|
}
|
||||||
|
out.WriteString("*/")
|
||||||
|
}
|
||||||
|
|
||||||
// ClauseSelect struct
|
// ClauseSelect struct
|
||||||
type ClauseSelect struct {
|
type ClauseSelect struct {
|
||||||
Distinct bool
|
Distinct bool
|
||||||
DistinctOnColumns []ColumnExpression
|
DistinctOnColumns []ColumnExpression
|
||||||
ProjectionList []Projection
|
ProjectionList []Projection
|
||||||
|
|
||||||
|
// MySQL only
|
||||||
|
OptimizerHints optimizerHints
|
||||||
}
|
}
|
||||||
|
|
||||||
// Projections returns list of projections for select clause
|
// Projections returns list of projections for select clause
|
||||||
|
|
@ -32,6 +56,7 @@ func (s *ClauseSelect) Projections() ProjectionList {
|
||||||
func (s *ClauseSelect) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
func (s *ClauseSelect) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
out.NewLine()
|
out.NewLine()
|
||||||
out.WriteString("SELECT")
|
out.WriteString("SELECT")
|
||||||
|
s.OptimizerHints.Serialize(statementType, out, options...)
|
||||||
|
|
||||||
if s.Distinct {
|
if s.Distinct {
|
||||||
out.WriteString("DISTINCT")
|
out.WriteString("DISTINCT")
|
||||||
|
|
@ -286,12 +311,16 @@ func (s *ClauseSetStmtOperator) Serialize(statementType StatementType, out *SQLB
|
||||||
// ClauseUpdate struct
|
// ClauseUpdate struct
|
||||||
type ClauseUpdate struct {
|
type ClauseUpdate struct {
|
||||||
Table SerializerTable
|
Table SerializerTable
|
||||||
|
|
||||||
|
// MySQL only
|
||||||
|
OptimizerHints optimizerHints
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize serializes clause into SQLBuilder
|
// Serialize serializes clause into SQLBuilder
|
||||||
func (u *ClauseUpdate) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
func (u *ClauseUpdate) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
out.NewLine()
|
out.NewLine()
|
||||||
out.WriteString("UPDATE")
|
out.WriteString("UPDATE")
|
||||||
|
u.OptimizerHints.Serialize(statementType, out, options...)
|
||||||
|
|
||||||
if utils.IsNil(u.Table) {
|
if utils.IsNil(u.Table) {
|
||||||
panic("jet: table to update is nil")
|
panic("jet: table to update is nil")
|
||||||
|
|
@ -342,6 +371,9 @@ func (s *SetClause) Serialize(statementType StatementType, out *SQLBuilder, opti
|
||||||
type ClauseInsert struct {
|
type ClauseInsert struct {
|
||||||
Table SerializerTable
|
Table SerializerTable
|
||||||
Columns []Column
|
Columns []Column
|
||||||
|
|
||||||
|
// MySQL only
|
||||||
|
OptimizerHints optimizerHints
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetColumns gets list of columns for insert
|
// GetColumns gets list of columns for insert
|
||||||
|
|
@ -355,13 +387,15 @@ func (i *ClauseInsert) GetColumns() []Column {
|
||||||
|
|
||||||
// Serialize serializes clause into SQLBuilder
|
// Serialize serializes clause into SQLBuilder
|
||||||
func (i *ClauseInsert) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
func (i *ClauseInsert) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
out.NewLine()
|
|
||||||
out.WriteString("INSERT INTO")
|
|
||||||
|
|
||||||
if utils.IsNil(i.Table) {
|
if utils.IsNil(i.Table) {
|
||||||
panic("jet: table is nil for INSERT clause")
|
panic("jet: table is nil for INSERT clause")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out.NewLine()
|
||||||
|
out.WriteString("INSERT")
|
||||||
|
i.OptimizerHints.Serialize(statementType, out, options...)
|
||||||
|
out.WriteString("INTO")
|
||||||
|
|
||||||
i.Table.serialize(statementType, out)
|
i.Table.serialize(statementType, out)
|
||||||
|
|
||||||
if len(i.Columns) > 0 {
|
if len(i.Columns) > 0 {
|
||||||
|
|
@ -392,6 +426,7 @@ func (v *ClauseValuesQuery) Serialize(statementType StatementType, out *SQLBuild
|
||||||
// ClauseValues struct
|
// ClauseValues struct
|
||||||
type ClauseValues struct {
|
type ClauseValues struct {
|
||||||
Rows [][]Serializer
|
Rows [][]Serializer
|
||||||
|
As string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize serializes clause into SQLBuilder
|
// Serialize serializes clause into SQLBuilder
|
||||||
|
|
@ -417,6 +452,12 @@ func (v *ClauseValues) Serialize(statementType StatementType, out *SQLBuilder, o
|
||||||
|
|
||||||
out.WriteByte(')')
|
out.WriteByte(')')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(v.As) > 0 {
|
||||||
|
out.WriteString("AS")
|
||||||
|
out.WriteIdentifier(v.As)
|
||||||
|
}
|
||||||
|
|
||||||
out.DecreaseIdent(7)
|
out.DecreaseIdent(7)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -442,17 +483,17 @@ func (v *ClauseQuery) Serialize(statementType StatementType, out *SQLBuilder, op
|
||||||
// ClauseDelete struct
|
// ClauseDelete struct
|
||||||
type ClauseDelete struct {
|
type ClauseDelete struct {
|
||||||
Table SerializerTable
|
Table SerializerTable
|
||||||
|
|
||||||
|
// MySQL only
|
||||||
|
OptimizerHints optimizerHints
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize serializes clause into SQLBuilder
|
// Serialize serializes clause into SQLBuilder
|
||||||
func (d *ClauseDelete) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
func (d *ClauseDelete) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
out.NewLine()
|
out.NewLine()
|
||||||
out.WriteString("DELETE FROM")
|
out.WriteString("DELETE")
|
||||||
|
d.OptimizerHints.Serialize(statementType, out, options...)
|
||||||
if d.Table == nil {
|
out.WriteString("FROM")
|
||||||
panic("jet: nil table in DELETE clause")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Table.serialize(statementType, out, FallTrough(options)...)
|
d.Table.serialize(statementType, out, FallTrough(options)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +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)))
|
|
||||||
//
|
//
|
||||||
|
// 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,
|
||||||
|
|
@ -16,8 +16,8 @@ func (cl ColumnList) SET(expression Expression) ColumnAssigment {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Except will create new column list in which columns contained in list of 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)
|
|
||||||
//
|
//
|
||||||
|
// 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{}
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,7 @@ type ColumnTimez interface {
|
||||||
Column
|
Column
|
||||||
|
|
||||||
From(subQuery SelectTable) ColumnTimez
|
From(subQuery SelectTable) ColumnTimez
|
||||||
|
SET(timeExp TimezExpression) ColumnAssigment
|
||||||
}
|
}
|
||||||
|
|
||||||
type timezColumnImpl struct {
|
type timezColumnImpl struct {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package jet
|
package jet
|
||||||
|
|
||||||
//FloatExpression is interface for SQL float columns
|
// FloatExpression is interface for SQL float columns
|
||||||
type FloatExpression interface {
|
type FloatExpression interface {
|
||||||
Expression
|
Expression
|
||||||
numericExpression
|
numericExpression
|
||||||
|
|
|
||||||
|
|
@ -120,17 +120,14 @@ func (i *integerInterfaceImpl) BIT_SHIFT_RIGHT(intExpression IntegerExpression)
|
||||||
return newBinaryIntegerOperatorExpression(i.parent, intExpression, ">>")
|
return newBinaryIntegerOperatorExpression(i.parent, intExpression, ">>")
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------//
|
|
||||||
func newBinaryIntegerOperatorExpression(lhs, rhs IntegerExpression, operator string) IntegerExpression {
|
func newBinaryIntegerOperatorExpression(lhs, rhs IntegerExpression, operator string) IntegerExpression {
|
||||||
return IntExp(NewBinaryOperatorExpression(lhs, rhs, operator))
|
return IntExp(NewBinaryOperatorExpression(lhs, rhs, operator))
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------//
|
|
||||||
func newPrefixIntegerOperatorExpression(expression IntegerExpression, operator string) IntegerExpression {
|
func newPrefixIntegerOperatorExpression(expression IntegerExpression, operator string) IntegerExpression {
|
||||||
return IntExp(newPrefixOperatorExpression(expression, operator))
|
return IntExp(newPrefixOperatorExpression(expression, operator))
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------//
|
|
||||||
type integerExpressionWrapper struct {
|
type integerExpressionWrapper struct {
|
||||||
integerInterfaceImpl
|
integerInterfaceImpl
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ func Uint64(value uint64) IntegerExpression {
|
||||||
return intLiteral(value)
|
return intLiteral(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------//
|
// ---------------------------------------------------//
|
||||||
type boolLiteralExpression struct {
|
type boolLiteralExpression struct {
|
||||||
boolInterfaceImpl
|
boolInterfaceImpl
|
||||||
literalExpressionImpl
|
literalExpressionImpl
|
||||||
|
|
@ -134,7 +134,7 @@ func Bool(value bool) BoolExpression {
|
||||||
return &boolLiteralExpression
|
return &boolLiteralExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------//
|
// ---------------------------------------------------//
|
||||||
type floatLiteral struct {
|
type floatLiteral struct {
|
||||||
floatInterfaceImpl
|
floatInterfaceImpl
|
||||||
literalExpressionImpl
|
literalExpressionImpl
|
||||||
|
|
@ -160,7 +160,7 @@ func Decimal(value string) FloatExpression {
|
||||||
return &floatLiteral
|
return &floatLiteral
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------//
|
// ---------------------------------------------------//
|
||||||
type stringLiteral struct {
|
type stringLiteral struct {
|
||||||
stringInterfaceImpl
|
stringInterfaceImpl
|
||||||
literalExpressionImpl
|
literalExpressionImpl
|
||||||
|
|
@ -351,7 +351,7 @@ func (n *nullLiteral) serialize(statement StatementType, out *SQLBuilder, option
|
||||||
out.WriteString("NULL")
|
out.WriteString("NULL")
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------//
|
// --------------------------------------------------//
|
||||||
type starLiteral struct {
|
type starLiteral struct {
|
||||||
ExpressionInterfaceImpl
|
ExpressionInterfaceImpl
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Statement is common interface for all statements(SELECT, INSERT, UPDATE, DELETE, LOCK)
|
// Statement is common interface for all statements(SELECT, INSERT, UPDATE, DELETE, LOCK)
|
||||||
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{})
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ func (s *stringInterfaceImpl) NOT_REGEXP_LIKE(pattern StringExpression, caseSens
|
||||||
return newBinaryBoolOperatorExpression(s.parent, pattern, StringNotRegexpLikeOperator, Bool(len(caseSensitive) > 0 && caseSensitive[0]))
|
return newBinaryBoolOperatorExpression(s.parent, pattern, StringNotRegexpLikeOperator, Bool(len(caseSensitive) > 0 && caseSensitive[0]))
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------//
|
// ---------------------------------------------------//
|
||||||
func newBinaryStringOperatorExpression(lhs, rhs Expression, operator string) StringExpression {
|
func newBinaryStringOperatorExpression(lhs, rhs Expression, operator string) StringExpression {
|
||||||
return StringExp(NewBinaryOperatorExpression(lhs, rhs, operator))
|
return StringExp(NewBinaryOperatorExpression(lhs, rhs, operator))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ func (c *castImpl) AS_CHAR(length ...int) StringExpression {
|
||||||
return StringExp(c.AS("CHAR"))
|
return StringExp(c.AS("CHAR"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AS_DATE casts expression AS DATE type
|
// AS_DATE casts expression AS DATE type
|
||||||
func (c *castImpl) AS_DATE() DateExpression {
|
func (c *castImpl) AS_DATE() DateExpression {
|
||||||
return DateExp(c.AS("DATE"))
|
return DateExp(c.AS("DATE"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import "github.com/go-jet/jet/v2/internal/jet"
|
||||||
type DeleteStatement interface {
|
type DeleteStatement interface {
|
||||||
Statement
|
Statement
|
||||||
|
|
||||||
|
OPTIMIZER_HINTS(hints ...OptimizerHint) DeleteStatement
|
||||||
|
|
||||||
USING(tables ...ReadableTable) DeleteStatement
|
USING(tables ...ReadableTable) DeleteStatement
|
||||||
WHERE(expression BoolExpression) DeleteStatement
|
WHERE(expression BoolExpression) DeleteStatement
|
||||||
ORDER_BY(orderByClauses ...OrderByClause) DeleteStatement
|
ORDER_BY(orderByClauses ...OrderByClause) DeleteStatement
|
||||||
|
|
@ -15,7 +17,7 @@ type DeleteStatement interface {
|
||||||
type deleteStatementImpl struct {
|
type deleteStatementImpl struct {
|
||||||
jet.SerializerStatement
|
jet.SerializerStatement
|
||||||
|
|
||||||
Delete jet.ClauseStatementBegin
|
Delete jet.ClauseDelete
|
||||||
Using jet.ClauseFrom
|
Using jet.ClauseFrom
|
||||||
Where jet.ClauseWhere
|
Where jet.ClauseWhere
|
||||||
OrderBy jet.ClauseOrderBy
|
OrderBy jet.ClauseOrderBy
|
||||||
|
|
@ -29,17 +31,22 @@ func newDeleteStatement(table Table) DeleteStatement {
|
||||||
&newDelete.Using,
|
&newDelete.Using,
|
||||||
&newDelete.Where,
|
&newDelete.Where,
|
||||||
&newDelete.OrderBy,
|
&newDelete.OrderBy,
|
||||||
&newDelete.Limit)
|
&newDelete.Limit,
|
||||||
|
)
|
||||||
|
|
||||||
newDelete.Delete.Name = "DELETE FROM"
|
newDelete.Delete.Table = table
|
||||||
newDelete.Using.Name = "USING"
|
newDelete.Using.Name = "USING"
|
||||||
newDelete.Delete.Tables = append(newDelete.Delete.Tables, table)
|
|
||||||
newDelete.Where.Mandatory = true
|
newDelete.Where.Mandatory = true
|
||||||
newDelete.Limit.Count = -1
|
newDelete.Limit.Count = -1
|
||||||
|
|
||||||
return newDelete
|
return newDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *deleteStatementImpl) OPTIMIZER_HINTS(hints ...OptimizerHint) DeleteStatement {
|
||||||
|
d.Delete.OptimizerHints = hints
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
func (d *deleteStatementImpl) USING(tables ...ReadableTable) DeleteStatement {
|
func (d *deleteStatementImpl) USING(tables ...ReadableTable) DeleteStatement {
|
||||||
d.Using.Tables = readableTablesToSerializerList(tables)
|
d.Using.Tables = readableTablesToSerializerList(tables)
|
||||||
return d
|
return d
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,8 @@ 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 function retrieves subfields such as year or hour from date/time values
|
||||||
// EXTRACT(DAY, User.CreatedAt)
|
//
|
||||||
|
// EXTRACT(DAY, User.CreatedAt)
|
||||||
func EXTRACT(field unitType, from Expression) IntegerExpression {
|
func EXTRACT(field unitType, from Expression) IntegerExpression {
|
||||||
return IntExp(jet.EXTRACT(string(field), from))
|
return IntExp(jet.EXTRACT(string(field), from))
|
||||||
}
|
}
|
||||||
|
|
@ -261,10 +262,22 @@ func UNIX_TIMESTAMP(str StringExpression) TimestampExpression {
|
||||||
return jet.NewTimestampFunc("UNIX_TIMESTAMP", str)
|
return jet.NewTimestampFunc("UNIX_TIMESTAMP", str)
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------- Comparison operators ---------------//
|
// --------------- Conditional Expressions Functions -------------//
|
||||||
|
|
||||||
// EXISTS checks for existence of the rows in subQuery
|
// EXISTS checks for existence of the rows in subQuery
|
||||||
var EXISTS = jet.EXISTS
|
var EXISTS = jet.EXISTS
|
||||||
|
|
||||||
// CASE create CASE operator with optional list of expressions
|
// CASE create CASE operator with optional list of expressions
|
||||||
var CASE = jet.CASE
|
var CASE = jet.CASE
|
||||||
|
|
||||||
|
// COALESCE function returns the first of its arguments that is not null.
|
||||||
|
var COALESCE = jet.COALESCE
|
||||||
|
|
||||||
|
// NULLIF function returns a null value if value1 equals value2; otherwise it returns value1.
|
||||||
|
var NULLIF = jet.NULLIF
|
||||||
|
|
||||||
|
// GREATEST selects the largest value from a list of expressions, or null if any of the expressions is null.
|
||||||
|
var GREATEST = jet.GREATEST
|
||||||
|
|
||||||
|
// LEAST selects the smallest value from a list of expressions, or null if any of the expressions is null.
|
||||||
|
var LEAST = jet.LEAST
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,15 @@ import "github.com/go-jet/jet/v2/internal/jet"
|
||||||
type InsertStatement interface {
|
type InsertStatement interface {
|
||||||
Statement
|
Statement
|
||||||
|
|
||||||
|
OPTIMIZER_HINTS(hints ...OptimizerHint) InsertStatement
|
||||||
|
|
||||||
// Insert row of values
|
// Insert row of values
|
||||||
VALUES(value interface{}, values ...interface{}) InsertStatement
|
VALUES(value interface{}, values ...interface{}) InsertStatement
|
||||||
// Insert row of values, where value for each column is extracted from filed of structure data.
|
// Insert row of values, where value for each column is extracted from filed of structure data.
|
||||||
// If data is not struct or there is no field for every column selected, this method will panic.
|
// If data is not struct or there is no field for every column selected, this method will panic.
|
||||||
MODEL(data interface{}) InsertStatement
|
MODEL(data interface{}) InsertStatement
|
||||||
MODELS(data interface{}) InsertStatement
|
MODELS(data interface{}) InsertStatement
|
||||||
|
AS_NEW() InsertStatement
|
||||||
|
|
||||||
ON_DUPLICATE_KEY_UPDATE(assigments ...ColumnAssigment) InsertStatement
|
ON_DUPLICATE_KEY_UPDATE(assigments ...ColumnAssigment) InsertStatement
|
||||||
|
|
||||||
|
|
@ -21,7 +24,10 @@ type InsertStatement interface {
|
||||||
func newInsertStatement(table Table, columns []jet.Column) InsertStatement {
|
func newInsertStatement(table Table, columns []jet.Column) InsertStatement {
|
||||||
newInsert := &insertStatementImpl{}
|
newInsert := &insertStatementImpl{}
|
||||||
newInsert.SerializerStatement = jet.NewStatementImpl(Dialect, jet.InsertStatementType, newInsert,
|
newInsert.SerializerStatement = jet.NewStatementImpl(Dialect, jet.InsertStatementType, newInsert,
|
||||||
&newInsert.Insert, &newInsert.ValuesQuery, &newInsert.OnDuplicateKey)
|
&newInsert.Insert,
|
||||||
|
&newInsert.ValuesQuery,
|
||||||
|
&newInsert.OnDuplicateKey,
|
||||||
|
)
|
||||||
|
|
||||||
newInsert.Insert.Table = table
|
newInsert.Insert.Table = table
|
||||||
newInsert.Insert.Columns = columns
|
newInsert.Insert.Columns = columns
|
||||||
|
|
@ -37,6 +43,11 @@ type insertStatementImpl struct {
|
||||||
OnDuplicateKey onDuplicateKeyUpdateClause
|
OnDuplicateKey onDuplicateKeyUpdateClause
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (is *insertStatementImpl) OPTIMIZER_HINTS(hints ...OptimizerHint) InsertStatement {
|
||||||
|
is.Insert.OptimizerHints = hints
|
||||||
|
return is
|
||||||
|
}
|
||||||
|
|
||||||
func (is *insertStatementImpl) VALUES(value interface{}, values ...interface{}) InsertStatement {
|
func (is *insertStatementImpl) VALUES(value interface{}, values ...interface{}) InsertStatement {
|
||||||
is.ValuesQuery.Rows = append(is.ValuesQuery.Rows, jet.UnwindRowFromValues(value, values))
|
is.ValuesQuery.Rows = append(is.ValuesQuery.Rows, jet.UnwindRowFromValues(value, values))
|
||||||
return is
|
return is
|
||||||
|
|
@ -52,6 +63,11 @@ func (is *insertStatementImpl) MODELS(data interface{}) InsertStatement {
|
||||||
return is
|
return is
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (is *insertStatementImpl) AS_NEW() InsertStatement {
|
||||||
|
is.ValuesQuery.As = "new"
|
||||||
|
return is
|
||||||
|
}
|
||||||
|
|
||||||
func (is *insertStatementImpl) ON_DUPLICATE_KEY_UPDATE(assigments ...ColumnAssigment) InsertStatement {
|
func (is *insertStatementImpl) ON_DUPLICATE_KEY_UPDATE(assigments ...ColumnAssigment) InsertStatement {
|
||||||
is.OnDuplicateKey = assigments
|
is.OnDuplicateKey = assigments
|
||||||
return is
|
return is
|
||||||
|
|
@ -79,7 +95,7 @@ func (s onDuplicateKeyUpdateClause) Serialize(statementType jet.StatementType, o
|
||||||
out.NewLine()
|
out.NewLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
jet.Serialize(assigment, statementType, out, jet.ShortName.WithFallTrough(options)...)
|
jet.Serialize(assigment, statementType, out, jet.FallTrough(options)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
out.DecreaseIdent(24)
|
out.DecreaseIdent(24)
|
||||||
|
|
|
||||||
|
|
@ -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
|
//
|
||||||
// value parameter has to be a number.
|
// In a case of MICROSECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR unit type
|
||||||
|
// value parameter has to be a number.
|
||||||
// INTERVAL(1, DAY)
|
// INTERVAL(1, DAY)
|
||||||
// In a case of other unit types, value should be string with appropriate format.
|
// In a case of other unit types, value should be string with appropriate format.
|
||||||
// INTERVAL("10:08:50", HOUR_SECOND)
|
// INTERVAL("10:08:50", HOUR_SECOND)
|
||||||
func INTERVAL(value interface{}, unitType unitType) Interval {
|
func INTERVAL(value interface{}, unitType unitType) Interval {
|
||||||
switch unitType {
|
switch unitType {
|
||||||
|
|
|
||||||
19
mysql/optimizer_hints.go
Normal file
19
mysql/optimizer_hints.go
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-jet/jet/v2/internal/jet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OptimizerHint provides a way to optimize query execution per-statement basis
|
||||||
|
type OptimizerHint = jet.OptimizerHint
|
||||||
|
|
||||||
|
// MAX_EXECUTION_TIME limits statement execution time
|
||||||
|
func MAX_EXECUTION_TIME(miliseconds int) OptimizerHint {
|
||||||
|
return OptimizerHint(fmt.Sprintf("MAX_EXECUTION_TIME(%d)", miliseconds))
|
||||||
|
}
|
||||||
|
|
||||||
|
// QB_NAME assigns name to query block
|
||||||
|
func QB_NAME(name string) OptimizerHint {
|
||||||
|
return OptimizerHint(fmt.Sprintf("QB_NAME(%s)", name))
|
||||||
|
}
|
||||||
|
|
@ -40,6 +40,8 @@ type SelectStatement interface {
|
||||||
jet.HasProjections
|
jet.HasProjections
|
||||||
Expression
|
Expression
|
||||||
|
|
||||||
|
OPTIMIZER_HINTS(hints ...OptimizerHint) SelectStatement
|
||||||
|
|
||||||
DISTINCT() SelectStatement
|
DISTINCT() SelectStatement
|
||||||
FROM(tables ...ReadableTable) SelectStatement
|
FROM(tables ...ReadableTable) SelectStatement
|
||||||
WHERE(expression BoolExpression) SelectStatement
|
WHERE(expression BoolExpression) SelectStatement
|
||||||
|
|
@ -65,9 +67,19 @@ func SELECT(projection Projection, projections ...Projection) SelectStatement {
|
||||||
|
|
||||||
func newSelectStatement(table ReadableTable, projections []Projection) SelectStatement {
|
func newSelectStatement(table ReadableTable, projections []Projection) SelectStatement {
|
||||||
newSelect := &selectStatementImpl{}
|
newSelect := &selectStatementImpl{}
|
||||||
newSelect.ExpressionStatement = jet.NewExpressionStatementImpl(Dialect, jet.SelectStatementType, newSelect, &newSelect.Select,
|
newSelect.ExpressionStatement = jet.NewExpressionStatementImpl(Dialect, jet.SelectStatementType, newSelect,
|
||||||
&newSelect.From, &newSelect.Where, &newSelect.GroupBy, &newSelect.Having, &newSelect.Window, &newSelect.OrderBy,
|
&newSelect.Select,
|
||||||
&newSelect.Limit, &newSelect.Offset, &newSelect.For, &newSelect.ShareLock)
|
&newSelect.From,
|
||||||
|
&newSelect.Where,
|
||||||
|
&newSelect.GroupBy,
|
||||||
|
&newSelect.Having,
|
||||||
|
&newSelect.Window,
|
||||||
|
&newSelect.OrderBy,
|
||||||
|
&newSelect.Limit,
|
||||||
|
&newSelect.Offset,
|
||||||
|
&newSelect.For,
|
||||||
|
&newSelect.ShareLock,
|
||||||
|
)
|
||||||
|
|
||||||
newSelect.Select.ProjectionList = projections
|
newSelect.Select.ProjectionList = projections
|
||||||
if table != nil {
|
if table != nil {
|
||||||
|
|
@ -100,6 +112,11 @@ type selectStatementImpl struct {
|
||||||
ShareLock jet.ClauseOptional
|
ShareLock jet.ClauseOptional
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *selectStatementImpl) OPTIMIZER_HINTS(hints ...OptimizerHint) SelectStatement {
|
||||||
|
s.Select.OptimizerHints = hints
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func (s *selectStatementImpl) DISTINCT() SelectStatement {
|
func (s *selectStatementImpl) DISTINCT() SelectStatement {
|
||||||
s.Select.Distinct = true
|
s.Select.Distinct = true
|
||||||
return s
|
return s
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import "github.com/go-jet/jet/v2/internal/jet"
|
||||||
type UpdateStatement interface {
|
type UpdateStatement interface {
|
||||||
jet.Statement
|
jet.Statement
|
||||||
|
|
||||||
|
OPTIMIZER_HINTS(hints ...OptimizerHint) UpdateStatement
|
||||||
|
|
||||||
SET(value interface{}, values ...interface{}) UpdateStatement
|
SET(value interface{}, values ...interface{}) UpdateStatement
|
||||||
MODEL(data interface{}) UpdateStatement
|
MODEL(data interface{}) UpdateStatement
|
||||||
|
|
||||||
|
|
@ -36,6 +38,11 @@ func newUpdateStatement(table Table, columns []jet.Column) UpdateStatement {
|
||||||
return update
|
return update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *updateStatementImpl) OPTIMIZER_HINTS(hints ...OptimizerHint) UpdateStatement {
|
||||||
|
u.Update.OptimizerHints = hints
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
func (u *updateStatementImpl) SET(value interface{}, values ...interface{}) UpdateStatement {
|
func (u *updateStatementImpl) SET(value interface{}, values ...interface{}) UpdateStatement {
|
||||||
columnAssigment, isColumnAssigment := value.(ColumnAssigment)
|
columnAssigment, isColumnAssigment := value.(ColumnAssigment)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ type DeleteStatement interface {
|
||||||
type deleteStatementImpl struct {
|
type deleteStatementImpl struct {
|
||||||
jet.SerializerStatement
|
jet.SerializerStatement
|
||||||
|
|
||||||
Delete jet.ClauseStatementBegin
|
Delete jet.ClauseDelete
|
||||||
Using jet.ClauseFrom
|
Using jet.ClauseFrom
|
||||||
Where jet.ClauseWhere
|
Where jet.ClauseWhere
|
||||||
Returning jet.ClauseReturning
|
Returning jet.ClauseReturning
|
||||||
|
|
@ -28,8 +28,7 @@ func newDeleteStatement(table WritableTable) DeleteStatement {
|
||||||
&newDelete.Where,
|
&newDelete.Where,
|
||||||
&newDelete.Returning)
|
&newDelete.Returning)
|
||||||
|
|
||||||
newDelete.Delete.Name = "DELETE FROM"
|
newDelete.Delete.Table = table
|
||||||
newDelete.Delete.Tables = append(newDelete.Delete.Tables, table)
|
|
||||||
newDelete.Using.Name = "USING"
|
newDelete.Using.Name = "USING"
|
||||||
newDelete.Where.Mandatory = true
|
newDelete.Where.Mandatory = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,7 @@ var reservedWords = []string{
|
||||||
"PRIMARY",
|
"PRIMARY",
|
||||||
"REFERENCES",
|
"REFERENCES",
|
||||||
"RETURNING",
|
"RETURNING",
|
||||||
|
"RIGHT",
|
||||||
"SELECT",
|
"SELECT",
|
||||||
"SESSION_USER",
|
"SESSION_USER",
|
||||||
"SOME",
|
"SOME",
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ type NumericExpression = jet.NumericExpression
|
||||||
// IntegerExpression interface
|
// IntegerExpression interface
|
||||||
type IntegerExpression = jet.IntegerExpression
|
type IntegerExpression = jet.IntegerExpression
|
||||||
|
|
||||||
//FloatExpression is interface
|
// FloatExpression is interface
|
||||||
type FloatExpression = jet.FloatExpression
|
type FloatExpression = jet.FloatExpression
|
||||||
|
|
||||||
// TimeExpression interface
|
// TimeExpression interface
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,8 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// EXTRACT function retrieves subfields such as year or hour from date/time values
|
// EXTRACT function retrieves subfields such as year or hour from date/time values
|
||||||
// EXTRACT(DAY, User.CreatedAt)
|
//
|
||||||
|
// EXTRACT(DAY, User.CreatedAt)
|
||||||
func EXTRACT(field unit, from Expression) FloatExpression {
|
func EXTRACT(field unit, from Expression) FloatExpression {
|
||||||
return FloatExp(jet.EXTRACT(unitToString(field), from))
|
return FloatExp(jet.EXTRACT(unitToString(field), from))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,8 @@ 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.
|
||||||
// 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 {
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,16 @@ func String(value string) StringExpression {
|
||||||
return CAST(jet.String(value)).AS_TEXT()
|
return CAST(jet.String(value)).AS_TEXT()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Json creates new json literal expression
|
||||||
|
func Json(value interface{}) StringExpression {
|
||||||
|
switch value.(type) {
|
||||||
|
case string, []byte:
|
||||||
|
default:
|
||||||
|
panic("Bytea parameter value has to be of the type string or []byte")
|
||||||
|
}
|
||||||
|
return StringExp(CAST(jet.Literal(value)).AS("json"))
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,11 @@ func TestBytea(t *testing.T) {
|
||||||
assertSerialize(t, Bytea([]byte("Some byte array")), `$1::bytea`, []byte("Some byte array"))
|
assertSerialize(t, Bytea([]byte("Some byte array")), `$1::bytea`, []byte("Some byte array"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJson(t *testing.T) {
|
||||||
|
assertSerialize(t, Json("{\"key\": \"value\"}"), `$1::json`, "{\"key\": \"value\"}")
|
||||||
|
assertSerialize(t, Json([]byte("{\"key\": \"value\"}")), `$1::json`, []byte("{\"key\": \"value\"}"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestDate(t *testing.T) {
|
func TestDate(t *testing.T) {
|
||||||
assertSerialize(t, Date(2014, time.January, 2), `$1::date`, "2014-01-02")
|
assertSerialize(t, Date(2014, time.January, 2), `$1::date`, "2014-01-02")
|
||||||
assertSerialize(t, DateT(time.Now()), `$1::date`)
|
assertSerialize(t, DateT(time.Now()), `$1::date`)
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ type SelectStatement interface {
|
||||||
AsTable(alias string) SelectTable
|
AsTable(alias string) SelectTable
|
||||||
}
|
}
|
||||||
|
|
||||||
//SELECT creates new SelectStatement with list of projections
|
// SELECT creates new SelectStatement with list of projections
|
||||||
func SELECT(projection Projection, projections ...Projection) SelectStatement {
|
func SELECT(projection Projection, projections ...Projection) SelectStatement {
|
||||||
return newSelectStatement(nil, append([]Projection{projection}, projections...))
|
return newSelectStatement(nil, append([]Projection{projection}, projections...))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ type ColumnDateTime = jet.ColumnTimestamp
|
||||||
// DateTimeColumn creates named timestamp column
|
// DateTimeColumn creates named timestamp column
|
||||||
var DateTimeColumn = jet.TimestampColumn
|
var DateTimeColumn = jet.TimestampColumn
|
||||||
|
|
||||||
//ColumnTimestamp is interface of SQL timestamp columns.
|
// ColumnTimestamp is interface of SQL timestamp columns.
|
||||||
type ColumnTimestamp = jet.ColumnTimestamp
|
type ColumnTimestamp = jet.ColumnTimestamp
|
||||||
|
|
||||||
// TimestampColumn creates named timestamp column
|
// TimestampColumn creates named timestamp column
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ type DeleteStatement interface {
|
||||||
type deleteStatementImpl struct {
|
type deleteStatementImpl struct {
|
||||||
jet.SerializerStatement
|
jet.SerializerStatement
|
||||||
|
|
||||||
Delete jet.ClauseStatementBegin
|
Delete jet.ClauseDelete
|
||||||
Where jet.ClauseWhere
|
Where jet.ClauseWhere
|
||||||
OrderBy jet.ClauseOrderBy
|
OrderBy jet.ClauseOrderBy
|
||||||
Limit jet.ClauseLimit
|
Limit jet.ClauseLimit
|
||||||
|
|
@ -32,8 +32,7 @@ func newDeleteStatement(table Table) DeleteStatement {
|
||||||
&newDelete.Returning,
|
&newDelete.Returning,
|
||||||
)
|
)
|
||||||
|
|
||||||
newDelete.Delete.Name = "DELETE FROM"
|
newDelete.Delete.Table = table
|
||||||
newDelete.Delete.Tables = append(newDelete.Delete.Tables, table)
|
|
||||||
newDelete.Where.Mandatory = true
|
newDelete.Where.Mandatory = true
|
||||||
newDelete.Limit.Count = -1
|
newDelete.Limit.Count = -1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -342,7 +342,13 @@ func UNIX_TIMESTAMP(str StringExpression) TimestampExpression {
|
||||||
return jet.NewTimestampFunc("UNIX_TIMESTAMP", str)
|
return jet.NewTimestampFunc("UNIX_TIMESTAMP", str)
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------- Comparison operators ---------------//
|
// --------------- Conditional Expressions Functions -------------//
|
||||||
|
|
||||||
|
// COALESCE function returns the first of its arguments that is not null.
|
||||||
|
var COALESCE = jet.COALESCE
|
||||||
|
|
||||||
|
// NULLIF function returns a null value if value1 equals value2; otherwise it returns value1.
|
||||||
|
var NULLIF = jet.NULLIF
|
||||||
|
|
||||||
// EXISTS checks for existence of the rows in subQuery
|
// EXISTS checks for existence of the rows in subQuery
|
||||||
var EXISTS = jet.EXISTS
|
var EXISTS = jet.EXISTS
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ type SelectStatement interface {
|
||||||
AsTable(alias string) SelectTable
|
AsTable(alias string) SelectTable
|
||||||
}
|
}
|
||||||
|
|
||||||
//SELECT creates new SelectStatement with list of projections
|
// SELECT creates new SelectStatement with list of projections
|
||||||
func SELECT(projection Projection, projections ...Projection) SelectStatement {
|
func SELECT(projection Projection, projections ...Projection) SelectStatement {
|
||||||
return newSelectStatement(nil, append([]Projection{projection}, projections...))
|
return newSelectStatement(nil, append([]Projection{projection}, projections...))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -98,3 +99,22 @@ WHERE (staff.staff_id != ?) AND (rental.rental_id < ?);
|
||||||
|
|
||||||
testutils.AssertExecAndRollback(t, stmt, db)
|
testutils.AssertExecAndRollback(t, stmt, db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeleteOptimizerHints(t *testing.T) {
|
||||||
|
|
||||||
|
stmt := Link.DELETE().
|
||||||
|
OPTIMIZER_HINTS(QB_NAME("deleteIns"), "MRR(link)").
|
||||||
|
WHERE(
|
||||||
|
Link.Name.IN(String("Gmail"), String("Outlook")),
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
DELETE /*+ QB_NAME(deleteIns) MRR(link) */ FROM test_sample.link
|
||||||
|
WHERE link.name IN ('Gmail', 'Outlook');
|
||||||
|
`)
|
||||||
|
|
||||||
|
testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
|
||||||
|
_, err := stmt.Exec(tx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ import (
|
||||||
|
|
||||||
var Actor = newActorTable("dvds", "actor", "")
|
var Actor = newActorTable("dvds", "actor", "")
|
||||||
|
|
||||||
type ActorTable struct {
|
type actorTable struct {
|
||||||
mysql.Table
|
mysql.Table
|
||||||
|
|
||||||
//Columns
|
//Columns
|
||||||
|
|
@ -251,27 +251,40 @@ type ActorTable struct {
|
||||||
MutableColumns mysql.ColumnList
|
MutableColumns mysql.ColumnList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ActorTable struct {
|
||||||
|
actorTable
|
||||||
|
|
||||||
|
NEW actorTable
|
||||||
|
}
|
||||||
|
|
||||||
// AS creates new ActorTable with assigned alias
|
// AS creates new ActorTable with assigned alias
|
||||||
func (a ActorTable) AS(alias string) ActorTable {
|
func (a ActorTable) AS(alias string) *ActorTable {
|
||||||
return newActorTable(a.SchemaName(), a.TableName(), alias)
|
return newActorTable(a.SchemaName(), a.TableName(), alias)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schema creates new ActorTable with assigned schema name
|
// Schema creates new ActorTable with assigned schema name
|
||||||
func (a ActorTable) FromSchema(schemaName string) ActorTable {
|
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
|
// WithPrefix creates new ActorTable with assigned table prefix
|
||||||
func (a ActorTable) WithPrefix(prefix string) ActorTable {
|
func (a ActorTable) WithPrefix(prefix string) *ActorTable {
|
||||||
return newActorTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
return newActorTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSuffix creates new ActorTable with assigned table suffix
|
// WithSuffix creates new ActorTable with assigned table suffix
|
||||||
func (a ActorTable) WithSuffix(suffix string) ActorTable {
|
func (a ActorTable) WithSuffix(suffix string) *ActorTable {
|
||||||
return newActorTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
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{
|
||||||
|
actorTable: newActorTableImpl(schemaName, tableName, alias),
|
||||||
|
NEW: newActorTableImpl("", "new", ""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newActorTableImpl(schemaName, tableName, alias string) actorTable {
|
||||||
var (
|
var (
|
||||||
ActorIDColumn = mysql.IntegerColumn("actor_id")
|
ActorIDColumn = mysql.IntegerColumn("actor_id")
|
||||||
FirstNameColumn = mysql.StringColumn("first_name")
|
FirstNameColumn = mysql.StringColumn("first_name")
|
||||||
|
|
@ -281,7 +294,7 @@ func newActorTable(schemaName, tableName, alias string) ActorTable {
|
||||||
mutableColumns = mysql.ColumnList{FirstNameColumn, LastNameColumn, LastUpdateColumn}
|
mutableColumns = mysql.ColumnList{FirstNameColumn, LastNameColumn, LastUpdateColumn}
|
||||||
)
|
)
|
||||||
|
|
||||||
return ActorTable{
|
return actorTable{
|
||||||
Table: mysql.NewTable(schemaName, tableName, alias, allColumns...),
|
Table: mysql.NewTable(schemaName, tableName, alias, allColumns...),
|
||||||
|
|
||||||
//Columns
|
//Columns
|
||||||
|
|
@ -334,7 +347,7 @@ import (
|
||||||
|
|
||||||
var ActorInfo = newActorInfoTable("dvds", "actor_info", "")
|
var ActorInfo = newActorInfoTable("dvds", "actor_info", "")
|
||||||
|
|
||||||
type ActorInfoTable struct {
|
type actorInfoTable struct {
|
||||||
mysql.Table
|
mysql.Table
|
||||||
|
|
||||||
//Columns
|
//Columns
|
||||||
|
|
@ -347,27 +360,40 @@ type ActorInfoTable struct {
|
||||||
MutableColumns mysql.ColumnList
|
MutableColumns mysql.ColumnList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ActorInfoTable struct {
|
||||||
|
actorInfoTable
|
||||||
|
|
||||||
|
NEW actorInfoTable
|
||||||
|
}
|
||||||
|
|
||||||
// AS creates new ActorInfoTable with assigned alias
|
// AS creates new ActorInfoTable with assigned alias
|
||||||
func (a ActorInfoTable) AS(alias string) ActorInfoTable {
|
func (a ActorInfoTable) AS(alias string) *ActorInfoTable {
|
||||||
return newActorInfoTable(a.SchemaName(), a.TableName(), alias)
|
return newActorInfoTable(a.SchemaName(), a.TableName(), alias)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schema creates new ActorInfoTable with assigned schema name
|
// Schema creates new ActorInfoTable with assigned schema name
|
||||||
func (a ActorInfoTable) FromSchema(schemaName string) ActorInfoTable {
|
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
|
// WithPrefix creates new ActorInfoTable with assigned table prefix
|
||||||
func (a ActorInfoTable) WithPrefix(prefix string) ActorInfoTable {
|
func (a ActorInfoTable) WithPrefix(prefix string) *ActorInfoTable {
|
||||||
return newActorInfoTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
return newActorInfoTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSuffix creates new ActorInfoTable with assigned table suffix
|
// WithSuffix creates new ActorInfoTable with assigned table suffix
|
||||||
func (a ActorInfoTable) WithSuffix(suffix string) ActorInfoTable {
|
func (a ActorInfoTable) WithSuffix(suffix string) *ActorInfoTable {
|
||||||
return newActorInfoTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
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{
|
||||||
|
actorInfoTable: newActorInfoTableImpl(schemaName, tableName, alias),
|
||||||
|
NEW: newActorInfoTableImpl("", "new", ""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newActorInfoTableImpl(schemaName, tableName, alias string) actorInfoTable {
|
||||||
var (
|
var (
|
||||||
ActorIDColumn = mysql.IntegerColumn("actor_id")
|
ActorIDColumn = mysql.IntegerColumn("actor_id")
|
||||||
FirstNameColumn = mysql.StringColumn("first_name")
|
FirstNameColumn = mysql.StringColumn("first_name")
|
||||||
|
|
@ -377,7 +403,7 @@ func newActorInfoTable(schemaName, tableName, alias string) ActorInfoTable {
|
||||||
mutableColumns = mysql.ColumnList{ActorIDColumn, FirstNameColumn, LastNameColumn, FilmInfoColumn}
|
mutableColumns = mysql.ColumnList{ActorIDColumn, FirstNameColumn, LastNameColumn, FilmInfoColumn}
|
||||||
)
|
)
|
||||||
|
|
||||||
return ActorInfoTable{
|
return actorInfoTable{
|
||||||
Table: mysql.NewTable(schemaName, tableName, alias, allColumns...),
|
Table: mysql.NewTable(schemaName, tableName, alias, allColumns...),
|
||||||
|
|
||||||
//Columns
|
//Columns
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ func TestInsertOnDuplicateKey(t *testing.T) {
|
||||||
INSERT INTO test_sample.link
|
INSERT INTO test_sample.link
|
||||||
VALUES (?, ?, ?, DEFAULT),
|
VALUES (?, ?, ?, DEFAULT),
|
||||||
(?, ?, ?, DEFAULT)
|
(?, ?, ?, DEFAULT)
|
||||||
ON DUPLICATE KEY UPDATE id = (id + ?),
|
ON DUPLICATE KEY UPDATE id = (link.id + ?),
|
||||||
name = ?;
|
name = ?;
|
||||||
`, randId, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
|
`, randId, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
|
||||||
randId, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
|
randId, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial",
|
||||||
|
|
@ -283,6 +283,65 @@ ON DUPLICATE KEY UPDATE id = (id + ?),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInsertOnDuplicateKeyUpdateNEW(t *testing.T) {
|
||||||
|
skipForMariaDB(t)
|
||||||
|
|
||||||
|
randId := rand.Int31()
|
||||||
|
|
||||||
|
stmt := Link.INSERT().
|
||||||
|
MODELS([]model.Link{
|
||||||
|
{
|
||||||
|
ID: randId,
|
||||||
|
URL: "https://www.postgresqltutorial.com",
|
||||||
|
Name: "PostgreSQL Tutorial",
|
||||||
|
Description: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: randId,
|
||||||
|
URL: "https://www.yahoo.com",
|
||||||
|
Name: "Yahoo",
|
||||||
|
Description: testutils.StringPtr("web portal and search engine"),
|
||||||
|
},
|
||||||
|
}).AS_NEW().
|
||||||
|
ON_DUPLICATE_KEY_UPDATE(
|
||||||
|
Link.ID.SET(Link.ID.ADD(Int(11))),
|
||||||
|
Link.URL.SET(Link.NEW.URL),
|
||||||
|
Link.Name.SET(Link.NEW.Name),
|
||||||
|
Link.Description.SET(Link.NEW.Description),
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
|
INSERT INTO test_sample.link
|
||||||
|
VALUES (?, ?, ?, ?),
|
||||||
|
(?, ?, ?, ?) AS new
|
||||||
|
ON DUPLICATE KEY UPDATE id = (link.id + ?),
|
||||||
|
url = new.url,
|
||||||
|
name = new.name,
|
||||||
|
description = new.description;
|
||||||
|
`)
|
||||||
|
|
||||||
|
testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
|
||||||
|
_, err := stmt.Exec(tx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
stmt := SELECT(Link.AllColumns).
|
||||||
|
FROM(Link).
|
||||||
|
WHERE(Link.ID.EQ(Int32(randId + 11)))
|
||||||
|
|
||||||
|
var dest model.Link
|
||||||
|
|
||||||
|
err = stmt.Query(tx, &dest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testutils.AssertDeepEqual(t, dest, model.Link{
|
||||||
|
ID: randId + 11,
|
||||||
|
URL: "https://www.yahoo.com",
|
||||||
|
Name: "Yahoo",
|
||||||
|
Description: testutils.StringPtr("web portal and search engine"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestInsertWithQueryContext(t *testing.T) {
|
func TestInsertWithQueryContext(t *testing.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)
|
||||||
|
|
@ -311,3 +370,23 @@ func TestInsertWithExecContext(t *testing.T) {
|
||||||
|
|
||||||
require.Error(t, err, "context deadline exceeded")
|
require.Error(t, err, "context deadline exceeded")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInsertOptimizerHints(t *testing.T) {
|
||||||
|
|
||||||
|
stmt := Link.INSERT(Link.MutableColumns).
|
||||||
|
OPTIMIZER_HINTS(QB_NAME("qbIns"), "NO_ICP(link)").
|
||||||
|
MODEL(model.Link{
|
||||||
|
URL: "http://www.google.com",
|
||||||
|
Name: "Google",
|
||||||
|
})
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
INSERT /*+ QB_NAME(qbIns) NO_ICP(link) */ INTO test_sample.link (url, name, description)
|
||||||
|
VALUES ('http://www.google.com', 'Google', NULL);
|
||||||
|
`)
|
||||||
|
|
||||||
|
testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
|
||||||
|
_, err := stmt.Exec(tx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1103,3 +1103,111 @@ func TestScanIntoCustomBaseTypes(t *testing.T) {
|
||||||
|
|
||||||
require.Equal(t, testutils.ToJSON(films), testutils.ToJSON(myFilms))
|
require.Equal(t, testutils.ToJSON(films), testutils.ToJSON(myFilms))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConditionalFunctions(t *testing.T) {
|
||||||
|
stmt := SELECT(
|
||||||
|
EXISTS(
|
||||||
|
Film.SELECT(Film.FilmID).WHERE(Film.RentalDuration.GT(Int(100))),
|
||||||
|
).AS("exists"),
|
||||||
|
CASE(Film.Length.GT(Int(120))).
|
||||||
|
WHEN(Bool(true)).THEN(String("long film")).
|
||||||
|
ELSE(String("short film")).AS("case"),
|
||||||
|
COALESCE(Film.Description, String("none")).AS("coalesce"),
|
||||||
|
NULLIF(Film.ReleaseYear, Int(200)).AS("null_if"),
|
||||||
|
GREATEST(Film.RentalDuration, Int(4), Int(5)).AS("greatest"),
|
||||||
|
LEAST(Film.RentalDuration, Int(7), Int(6)).AS("least"),
|
||||||
|
).FROM(
|
||||||
|
Film,
|
||||||
|
).WHERE(
|
||||||
|
Film.FilmID.LT(Int(5)),
|
||||||
|
).ORDER_BY(
|
||||||
|
Film.FilmID,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT (EXISTS (
|
||||||
|
SELECT film.film_id AS "film.film_id"
|
||||||
|
FROM dvds.film
|
||||||
|
WHERE film.rental_duration > 100
|
||||||
|
)) AS "exists",
|
||||||
|
(CASE (film.length > 120) WHEN TRUE THEN 'long film' ELSE 'short film' END) AS "case",
|
||||||
|
COALESCE(film.description, 'none') AS "coalesce",
|
||||||
|
NULLIF(film.release_year, 200) AS "null_if",
|
||||||
|
GREATEST(film.rental_duration, 4, 5) AS "greatest",
|
||||||
|
LEAST(film.rental_duration, 7, 6) AS "least"
|
||||||
|
FROM dvds.film
|
||||||
|
WHERE film.film_id < 5
|
||||||
|
ORDER BY film.film_id;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var res []struct {
|
||||||
|
Exists string
|
||||||
|
Case string
|
||||||
|
Coalesce string
|
||||||
|
NullIf string
|
||||||
|
Greatest string
|
||||||
|
Least string
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &res)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testutils.AssertJSON(t, res, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Exists": "0",
|
||||||
|
"Case": "short film",
|
||||||
|
"Coalesce": "A Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies",
|
||||||
|
"NullIf": "2006",
|
||||||
|
"Greatest": "6",
|
||||||
|
"Least": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Exists": "0",
|
||||||
|
"Case": "short film",
|
||||||
|
"Coalesce": "A Astounding Epistle of a Database Administrator And a Explorer who must Find a Car in Ancient China",
|
||||||
|
"NullIf": "2006",
|
||||||
|
"Greatest": "5",
|
||||||
|
"Least": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Exists": "0",
|
||||||
|
"Case": "short film",
|
||||||
|
"Coalesce": "A Astounding Reflection of a Lumberjack And a Car who must Sink a Lumberjack in A Baloon Factory",
|
||||||
|
"NullIf": "2006",
|
||||||
|
"Greatest": "7",
|
||||||
|
"Least": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Exists": "0",
|
||||||
|
"Case": "short film",
|
||||||
|
"Coalesce": "A Fanciful Documentary of a Frisbee And a Lumberjack who must Chase a Monkey in A Shark Tank",
|
||||||
|
"NullIf": "2006",
|
||||||
|
"Greatest": "5",
|
||||||
|
"Least": "5"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectOptimizerHints(t *testing.T) {
|
||||||
|
|
||||||
|
stmt := SELECT(Actor.AllColumns).
|
||||||
|
OPTIMIZER_HINTS(MAX_EXECUTION_TIME(1), QB_NAME("mainQueryBlock"), "NO_ICP(actor)").
|
||||||
|
DISTINCT().
|
||||||
|
FROM(Actor)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT /*+ MAX_EXECUTION_TIME(1) QB_NAME(mainQueryBlock) NO_ICP(actor) */ DISTINCT actor.actor_id AS "actor.actor_id",
|
||||||
|
actor.first_name AS "actor.first_name",
|
||||||
|
actor.last_name AS "actor.last_name",
|
||||||
|
actor.last_update AS "actor.last_update"
|
||||||
|
FROM dvds.actor;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var actors []model.Actor
|
||||||
|
|
||||||
|
err := stmt.QueryContext(context.Background(), db, &actors)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, actors, 200)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"database/sql"
|
"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"
|
||||||
"github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/model"
|
"github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/model"
|
||||||
. "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/table"
|
. "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/table"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
@ -260,10 +260,10 @@ func TestUpdateExecContext(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateWithJoin(t *testing.T) {
|
func TestUpdateWithJoin(t *testing.T) {
|
||||||
statement := table.Staff.INNER_JOIN(table.Address, table.Address.AddressID.EQ(table.Staff.AddressID)).
|
statement := Staff.INNER_JOIN(Address, Address.AddressID.EQ(Staff.AddressID)).
|
||||||
UPDATE(table.Staff.LastName).
|
UPDATE(Staff.LastName).
|
||||||
SET(String("New staff name")).
|
SET(String("New staff name")).
|
||||||
WHERE(table.Staff.StaffID.EQ(Int(1)))
|
WHERE(Staff.StaffID.EQ(Int(1)))
|
||||||
|
|
||||||
testutils.AssertStatementSql(t, statement, `
|
testutils.AssertStatementSql(t, statement, `
|
||||||
UPDATE dvds.staff
|
UPDATE dvds.staff
|
||||||
|
|
@ -274,3 +274,29 @@ WHERE staff.staff_id = ?;
|
||||||
|
|
||||||
testutils.AssertExecAndRollback(t, statement, db)
|
testutils.AssertExecAndRollback(t, statement, db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpdateOptimizerHints(t *testing.T) {
|
||||||
|
|
||||||
|
stmt := Link.UPDATE(Link.AllColumns).
|
||||||
|
OPTIMIZER_HINTS(QB_NAME("qbInsert"), "MRR(link)").
|
||||||
|
MODEL(model.Link{
|
||||||
|
ID: 501,
|
||||||
|
URL: "http://www.duckduckgo.com",
|
||||||
|
Name: "DuckDuckGo",
|
||||||
|
}).
|
||||||
|
WHERE(Link.Name.EQ(String("Bing")))
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
UPDATE /*+ QB_NAME(qbInsert) MRR(link) */ test_sample.link
|
||||||
|
SET id = 501,
|
||||||
|
url = 'http://www.duckduckgo.com',
|
||||||
|
name = 'DuckDuckGo',
|
||||||
|
description = NULL
|
||||||
|
WHERE link.name = 'Bing';
|
||||||
|
`)
|
||||||
|
|
||||||
|
testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
|
||||||
|
_, err := stmt.Exec(tx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1257,6 +1257,28 @@ LIMIT $6;
|
||||||
requireLogged(t, query)
|
requireLogged(t, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJsonLiteral(t *testing.T) {
|
||||||
|
stmt := AllTypes.UPDATE().
|
||||||
|
SET(AllTypes.JSON.SET(Json(`{"firstName": "John", "lastName": "Doe"}`))).
|
||||||
|
WHERE(AllTypes.SmallInt.EQ(Int(14))).
|
||||||
|
RETURNING(AllTypes.JSON)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
UPDATE test_sample.all_types
|
||||||
|
SET json = '{"firstName": "John", "lastName": "Doe"}'::json
|
||||||
|
WHERE all_types.small_int = 14
|
||||||
|
RETURNING all_types.json AS "all_types.json";
|
||||||
|
`)
|
||||||
|
|
||||||
|
testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
|
||||||
|
var res model.AllTypes
|
||||||
|
|
||||||
|
err := stmt.Query(tx, &res)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, res.JSON, `{"firstName": "John", "lastName": "Doe"}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var allTypesRow0 = model.AllTypes{
|
var allTypesRow0 = model.AllTypes{
|
||||||
SmallIntPtr: testutils.Int16Ptr(14),
|
SmallIntPtr: testutils.Int16Ptr(14),
|
||||||
SmallInt: 14,
|
SmallInt: 14,
|
||||||
|
|
|
||||||
|
|
@ -2723,6 +2723,92 @@ func TestScanUsingConn(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConditionalFunctions(t *testing.T) {
|
||||||
|
stmt := SELECT(
|
||||||
|
EXISTS(
|
||||||
|
Film.SELECT(Film.FilmID).WHERE(Film.RentalDuration.GT(Int(100))),
|
||||||
|
).AS("exists"),
|
||||||
|
CASE(Film.Length.GT(Int(120))).
|
||||||
|
WHEN(Bool(true)).THEN(String("long film")).
|
||||||
|
ELSE(String("short film")).AS("case"),
|
||||||
|
COALESCE(Film.Description, String("none")).AS("coalesce"),
|
||||||
|
NULLIF(Film.ReleaseYear, Int(200)).AS("null_if"),
|
||||||
|
GREATEST(Film.RentalDuration, Int(4), Int(5)).AS("greatest"),
|
||||||
|
LEAST(Film.RentalDuration, Int(7), Int(6)).AS("least"),
|
||||||
|
).FROM(
|
||||||
|
Film,
|
||||||
|
).WHERE(
|
||||||
|
Film.FilmID.LT(Int(5)),
|
||||||
|
).ORDER_BY(
|
||||||
|
Film.FilmID,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT (EXISTS (
|
||||||
|
SELECT film.film_id AS "film.film_id"
|
||||||
|
FROM dvds.film
|
||||||
|
WHERE film.rental_duration > 100
|
||||||
|
)) AS "exists",
|
||||||
|
(CASE (film.length > 120) WHEN TRUE::boolean THEN 'long film'::text ELSE 'short film'::text END) AS "case",
|
||||||
|
COALESCE(film.description, 'none'::text) AS "coalesce",
|
||||||
|
NULLIF(film.release_year, 200) AS "null_if",
|
||||||
|
GREATEST(film.rental_duration, 4, 5) AS "greatest",
|
||||||
|
LEAST(film.rental_duration, 7, 6) AS "least"
|
||||||
|
FROM dvds.film
|
||||||
|
WHERE film.film_id < 5
|
||||||
|
ORDER BY film.film_id;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var res []struct {
|
||||||
|
Exists bool
|
||||||
|
Case string
|
||||||
|
Coalesce string
|
||||||
|
NullIf string
|
||||||
|
Greatest string
|
||||||
|
Least string
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &res)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testutils.AssertJSON(t, res, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Exists": false,
|
||||||
|
"Case": "short film",
|
||||||
|
"Coalesce": "A Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies",
|
||||||
|
"NullIf": "2006",
|
||||||
|
"Greatest": "6",
|
||||||
|
"Least": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Exists": false,
|
||||||
|
"Case": "short film",
|
||||||
|
"Coalesce": "A Astounding Epistle of a Database Administrator And a Explorer who must Find a Car in Ancient China",
|
||||||
|
"NullIf": "2006",
|
||||||
|
"Greatest": "5",
|
||||||
|
"Least": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Exists": false,
|
||||||
|
"Case": "short film",
|
||||||
|
"Coalesce": "A Astounding Reflection of a Lumberjack And a Car who must Sink a Lumberjack in A Baloon Factory",
|
||||||
|
"NullIf": "2006",
|
||||||
|
"Greatest": "7",
|
||||||
|
"Least": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Exists": false,
|
||||||
|
"Case": "short film",
|
||||||
|
"Coalesce": "A Fanciful Documentary of a Frisbee And a Lumberjack who must Chase a Monkey in A Shark Tank",
|
||||||
|
"NullIf": "2006",
|
||||||
|
"Greatest": "5",
|
||||||
|
"Least": "5"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
var customer0 = model.Customer{
|
var customer0 = model.Customer{
|
||||||
CustomerID: 1,
|
CustomerID: 1,
|
||||||
StoreID: 1,
|
StoreID: 1,
|
||||||
|
|
|
||||||
|
|
@ -810,3 +810,75 @@ func TestScanNumericToNumber(t *testing.T) {
|
||||||
require.Equal(t, number.Float32, float32(1.234568e+09))
|
require.Equal(t, number.Float32, float32(1.234568e+09))
|
||||||
require.Equal(t, number.Float64, float64(1.234567890111e+09))
|
require.Equal(t, number.Float64, float64(1.234567890111e+09))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConditionalFunctions(t *testing.T) {
|
||||||
|
stmt := SELECT(
|
||||||
|
EXISTS(
|
||||||
|
Film.SELECT(Film.FilmID).WHERE(Film.RentalDuration.GT(Int(5))),
|
||||||
|
).AS("exists"),
|
||||||
|
CASE(Film.Length.GT(Int(120))).
|
||||||
|
WHEN(Bool(true)).THEN(String("long film")).
|
||||||
|
ELSE(String("short film")).AS("case"),
|
||||||
|
COALESCE(Film.Description, String("none")).AS("coalesce"),
|
||||||
|
NULLIF(Film.ReleaseYear, Int(200)).AS("null_if"),
|
||||||
|
).FROM(
|
||||||
|
Film,
|
||||||
|
).WHERE(
|
||||||
|
Film.FilmID.LT(Int(5)),
|
||||||
|
).ORDER_BY(
|
||||||
|
Film.FilmID,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT (EXISTS (
|
||||||
|
SELECT film.film_id AS "film.film_id"
|
||||||
|
FROM film
|
||||||
|
WHERE film.rental_duration > 5
|
||||||
|
)) AS "exists",
|
||||||
|
(CASE (film.length > 120) WHEN TRUE THEN 'long film' ELSE 'short film' END) AS "case",
|
||||||
|
COALESCE(film.description, 'none') AS "coalesce",
|
||||||
|
NULLIF(film.release_year, 200) AS "null_if"
|
||||||
|
FROM film
|
||||||
|
WHERE film.film_id < 5
|
||||||
|
ORDER BY film.film_id;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var res []struct {
|
||||||
|
Exists bool
|
||||||
|
Case string
|
||||||
|
Coalesce string
|
||||||
|
NullIf string
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &res)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testutils.AssertJSON(t, res, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Exists": true,
|
||||||
|
"Case": "short film",
|
||||||
|
"Coalesce": "A Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies",
|
||||||
|
"NullIf": "2006"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Exists": true,
|
||||||
|
"Case": "short film",
|
||||||
|
"Coalesce": "A Astounding Epistle of a Database Administrator And a Explorer who must Find a Car in Ancient China",
|
||||||
|
"NullIf": "2006"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Exists": true,
|
||||||
|
"Case": "short film",
|
||||||
|
"Coalesce": "A Astounding Reflection of a Lumberjack And a Car who must Sink a Lumberjack in A Baloon Factory",
|
||||||
|
"NullIf": "2006"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Exists": true,
|
||||||
|
"Case": "short film",
|
||||||
|
"Coalesce": "A Fanciful Documentary of a Frisbee And a Lumberjack who must Chase a Monkey in A Shark Tank",
|
||||||
|
"NullIf": "2006"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue