├── _config.yml ├── cmd └── jet │ └── version.go ├── .gitattributes ├── examples └── quick-start │ ├── diagram.png │ ├── .gen │ └── jetdb │ │ └── dvds │ │ ├── model │ │ ├── category.go │ │ ├── language.go │ │ ├── actor.go │ │ ├── film_actor.go │ │ ├── film_category.go │ │ ├── film.go │ │ └── mpaa_rating.go │ │ ├── enum │ │ └── mpaa_rating.go │ │ └── table │ │ ├── category.go │ │ ├── language.go │ │ └── film_actor.go │ └── README.md ├── .gitmodules ├── .github ├── dependabot.yml ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── code_scanner.yml │ └── codeql-analysis.yml ├── internal ├── utils │ ├── ptr │ │ └── ptr.go │ ├── throw │ │ └── throw.go │ ├── is │ │ └── is.go │ ├── strslice │ │ └── strslice.go │ ├── errfmt │ │ └── errfmt.go │ ├── must │ │ └── must.go │ ├── semantic │ │ └── version.go │ ├── filesys │ │ └── filesys.go │ ├── datetime │ │ └── duration.go │ └── dbidentifier │ │ └── dbidentifier.go ├── jet │ ├── string_or_blob_expression.go │ ├── keyword.go │ ├── numeric_expression.go │ ├── clause_test.go │ ├── cast_test.go │ ├── column_test.go │ ├── column_list_assigment.go │ ├── enum_value.go │ ├── column_assigment.go │ ├── values.go │ ├── column_list_test.go │ ├── utils_test.go │ ├── table_test.go │ ├── window_func_test.go │ ├── raw_statement.go │ ├── cast.go │ ├── operators_test.go │ ├── select_lock.go │ ├── expression_test.go │ ├── select_table.go │ ├── projection_test.go │ ├── group_by_clause.go │ ├── logger.go │ ├── order_set_aggregate_functions.go │ ├── order_by_clause.go │ ├── time_expression_test.go │ ├── sql_builder_test.go │ ├── timez_expression_test.go │ └── alias.go ├── 3rdparty │ ├── pq │ │ ├── format_timestamp.go │ │ └── format_timestamp_test.go │ └── snaker │ │ └── snaker_test.go └── testutils │ └── time_utils.go ├── generator ├── metadata │ ├── enum_meta_data.go │ ├── schema_meta_data.go │ ├── table_meta_data.go │ ├── column_meta_data.go │ └── dialect_query_set.go ├── template │ ├── format.go │ ├── sql_builder_template_test.go │ ├── model_template_test.go │ ├── format_test.go │ └── generator_template.go └── sqlite │ └── sqlite_generator.go ├── postgres ├── lateral_test.go ├── statement.go ├── operators.go ├── keywords.go ├── lateral.go ├── select_table.go ├── delete_statement_test.go ├── columns_test.go ├── conflict_action.go ├── lock_statement_test.go ├── values.go ├── clause_test.go ├── types.go ├── delete_statement.go ├── array_columns.go ├── update_statement_test.go ├── set_statement_test.go ├── lock_statement.go ├── clause.go ├── functions_test.go ├── with_statement.go ├── insert_statement.go └── cast_test.go ├── mysql ├── functions_test.go ├── statement.go ├── operators.go ├── lock_statement_test.go ├── lateral.go ├── optimizer_hints.go ├── select_table.go ├── set_statement_test.go ├── cast_test.go ├── delete_statement_test.go ├── values.go ├── types.go ├── lock_statement.go ├── cast.go ├── columns.go ├── delete_statement.go ├── with_statement.go ├── literal_test.go ├── utils_test.go ├── update_statement.go ├── select_json.go ├── expressions_test.go └── dialect_test.go ├── tests ├── init │ └── Readme.md ├── mysql │ ├── update_dvds_test.go │ ├── lock_test.go │ ├── cast_test.go │ └── sample_test.go ├── internal │ └── utils │ │ ├── file │ │ └── file.go │ │ └── repo │ │ └── repo.go ├── sqlite │ ├── cast_test.go │ └── delete_test.go ├── Readme.md ├── docker-compose.yaml ├── postgres │ └── lock_test.go └── dbconfig │ └── dbconfig.go ├── sqlite ├── statement.go ├── operators.go ├── cast_test.go ├── select_table.go ├── set_statement_test.go ├── delete_statement_test.go ├── values.go ├── cast.go ├── types.go ├── delete_statement.go ├── update_statement_test.go ├── literal_test.go ├── columns.go ├── expressions_test.go ├── update_statement.go ├── dialect_test.go ├── utils_test.go ├── literal.go ├── on_conflict_clause.go └── with_statement.go ├── .gitignore ├── .golangci.yml ├── NOTICE ├── qrm ├── type_stack.go └── db.go └── go.mod /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-tactile -------------------------------------------------------------------------------- /cmd/jet/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const version = "v2.14.0" 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | *.sql linguist-detectable=false 3 | *.json linguist-detectable=false -------------------------------------------------------------------------------- /examples/quick-start/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-jet/jet/HEAD/examples/quick-start/diagram.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/testdata"] 2 | path = tests/testdata 3 | url = https://github.com/go-jet/jet-test-data 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | schedule: 6 | interval: daily 7 | -------------------------------------------------------------------------------- /internal/utils/ptr/ptr.go: -------------------------------------------------------------------------------- 1 | package ptr 2 | 3 | // Of returns the address of any given parameter 4 | func Of[T any](value T) *T { 5 | return &value 6 | } 7 | -------------------------------------------------------------------------------- /internal/utils/throw/throw.go: -------------------------------------------------------------------------------- 1 | package throw 2 | 3 | // OnError will panic if err is not nill 4 | func OnError(err error) { 5 | if err != nil { 6 | panic(err) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ko_fi: gojetdev 4 | custom: ["mempool.space/address/bc1qtjhxe8mqx0yzff2l0f6stjpjj92kgwr0a53wxv", "etherscan.io/address/0xe98e4535C744c617e8E45828B63fDFf9367E3574"] 5 | -------------------------------------------------------------------------------- /generator/metadata/enum_meta_data.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | // Enum metadata struct 4 | type Enum struct { 5 | Name string `sql:"primary_key"` 6 | Comment string 7 | Values []string 8 | } 9 | -------------------------------------------------------------------------------- /internal/jet/string_or_blob_expression.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | // StringOrBlobExpression is common interface for all string and blob expressions 4 | type StringOrBlobExpression interface { 5 | Expression 6 | 7 | isStringOrBlob() 8 | } 9 | -------------------------------------------------------------------------------- /internal/utils/is/is.go: -------------------------------------------------------------------------------- 1 | package is 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // Nil check if v is nil 8 | func Nil(v interface{}) bool { 9 | return v == nil || (reflect.ValueOf(v).Kind() == reflect.Ptr && reflect.ValueOf(v).IsNil()) 10 | } 11 | -------------------------------------------------------------------------------- /postgres/lateral_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "testing" 4 | 5 | func TestLATERAL(t *testing.T) { 6 | assertSerialize(t, 7 | LATERAL( 8 | SELECT(Int(1)), 9 | ).AS("lat1"), 10 | 11 | `LATERAL ( 12 | SELECT $1 13 | ) AS lat1`) 14 | } 15 | -------------------------------------------------------------------------------- /mysql/functions_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/uuid" 7 | ) 8 | 9 | func TestUUIDToBin(t *testing.T) { 10 | assertSerialize(t, UUID_TO_BIN(String(uuid.Nil.String())), `uuid_to_bin(?)`, uuid.Nil.String()) 11 | } 12 | -------------------------------------------------------------------------------- /internal/utils/strslice/strslice.go: -------------------------------------------------------------------------------- 1 | package strslice 2 | 3 | // Contains checks if slice of strings contains a string 4 | func Contains(strings []string, contains string) bool { 5 | for _, str := range strings { 6 | if str == contains { 7 | return true 8 | } 9 | } 10 | 11 | return false 12 | } 13 | -------------------------------------------------------------------------------- /tests/init/Readme.md: -------------------------------------------------------------------------------- 1 | 2 | The `init` command can be used to initialize test databases on the local host machine, if needed. 3 | Update [dbconfig](../dbconfig/dbconfig.go) with your local database parameters. 4 | 5 | The recommended way to initialize test databases is by a docker container. 6 | See tests [Readme.md](../Readme.md). -------------------------------------------------------------------------------- /internal/jet/keyword.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | const ( 4 | // DEFAULT is jet equivalent of SQL DEFAULT 5 | DEFAULT Keyword = "DEFAULT" 6 | ) 7 | 8 | // Keyword type 9 | type Keyword string 10 | 11 | func (k Keyword) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { 12 | out.WriteString(string(k)) 13 | } 14 | -------------------------------------------------------------------------------- /mysql/statement.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/jet" 5 | ) 6 | 7 | // RawStatement creates new sql statements from raw query and optional map of named arguments 8 | func RawStatement(rawQuery string, namedArguments ...RawArgs) jet.SerializerStatement { 9 | return jet.RawStatement(Dialect, rawQuery, namedArguments...) 10 | } 11 | -------------------------------------------------------------------------------- /sqlite/statement.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/jet" 5 | ) 6 | 7 | // RawStatement creates new sql statements from raw query and optional map of named arguments 8 | func RawStatement(rawQuery string, namedArguments ...RawArgs) jet.SerializerStatement { 9 | return jet.RawStatement(Dialect, rawQuery, namedArguments...) 10 | } 11 | -------------------------------------------------------------------------------- /mysql/operators.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // NOT returns negation of bool expression result 6 | var NOT = jet.NOT 7 | 8 | // BIT_NOT inverts every bit in integer expression result 9 | var BIT_NOT = jet.BIT_NOT 10 | 11 | // DISTINCT operator can be used to return distinct values of expr 12 | var DISTINCT = jet.DISTINCT 13 | -------------------------------------------------------------------------------- /postgres/statement.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/jet" 5 | ) 6 | 7 | // RawStatement creates new sql statements from raw query and optional map of named arguments 8 | func RawStatement(rawQuery string, namedArguments ...RawArgs) jet.SerializerStatement { 9 | return jet.RawStatement(Dialect, rawQuery, namedArguments...) 10 | } 11 | -------------------------------------------------------------------------------- /sqlite/operators.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // NOT returns negation of bool expression result 6 | var NOT = jet.NOT 7 | 8 | // BIT_NOT inverts every bit in integer expression result 9 | var BIT_NOT = jet.BIT_NOT 10 | 11 | // DISTINCT operator can be used to return distinct values of expr 12 | var DISTINCT = jet.DISTINCT 13 | -------------------------------------------------------------------------------- /internal/utils/errfmt/errfmt.go: -------------------------------------------------------------------------------- 1 | package errfmt 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/utils/is" 5 | "strings" 6 | ) 7 | 8 | // Trace returns well formatted wrapped error trace string 9 | func Trace(err error) string { 10 | if is.Nil(err) { 11 | return "" 12 | } 13 | return "Error trace:\n" + " - " + strings.Replace(err.Error(), ": ", ":\n - ", -1) 14 | } 15 | -------------------------------------------------------------------------------- /postgres/operators.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // NOT returns negation of bool expression result 6 | var NOT = jet.NOT 7 | 8 | // BIT_NOT inverts every bit in integer expression result 9 | var BIT_NOT = jet.BIT_NOT 10 | 11 | // DISTINCT operator can be used to return distinct values of expr 12 | var DISTINCT = jet.DISTINCT 13 | -------------------------------------------------------------------------------- /internal/jet/numeric_expression.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | // NumericExpression is common interface for all integer and float expressions 4 | type NumericExpression interface { 5 | Expression 6 | numericExpression 7 | } 8 | 9 | type numericExpression interface { 10 | isNumericExpression() 11 | } 12 | 13 | type numericExpressionImpl struct{} 14 | 15 | func (n *numericExpressionImpl) isNumericExpression() {} 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Idea 15 | .idea 16 | *.iml 17 | 18 | # Test files 19 | gen 20 | .gentestdata 21 | .tests/testdata/ 22 | .gen 23 | .docker 24 | .env 25 | .tempTestDir 26 | .gentestdata3 -------------------------------------------------------------------------------- /examples/quick-start/.gen/jetdb/dvds/model/category.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package model 9 | 10 | import ( 11 | "time" 12 | ) 13 | 14 | type Category struct { 15 | CategoryID int32 `sql:"primary_key"` 16 | Name string 17 | LastUpdate time.Time 18 | } 19 | -------------------------------------------------------------------------------- /examples/quick-start/.gen/jetdb/dvds/model/language.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package model 9 | 10 | import ( 11 | "time" 12 | ) 13 | 14 | type Language struct { 15 | LanguageID int32 `sql:"primary_key"` 16 | Name string 17 | LastUpdate time.Time 18 | } 19 | -------------------------------------------------------------------------------- /internal/jet/clause_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "testing" 6 | ) 7 | 8 | func TestClauseSelect_Serialize(t *testing.T) { 9 | defer func() { 10 | r := recover() 11 | require.Equal(t, r, "jet: SELECT clause has to have at least one projection") 12 | }() 13 | 14 | selectClause := &ClauseSelect{} 15 | selectClause.Serialize(SelectStatementType, &SQLBuilder{}) 16 | } 17 | -------------------------------------------------------------------------------- /examples/quick-start/.gen/jetdb/dvds/model/actor.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package model 9 | 10 | import ( 11 | "time" 12 | ) 13 | 14 | type Actor struct { 15 | ActorID int32 `sql:"primary_key"` 16 | FirstName string 17 | LastName string 18 | LastUpdate time.Time 19 | } 20 | -------------------------------------------------------------------------------- /generator/template/format.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import "regexp" 4 | 5 | // Returns the provided string as golang comment without ascii control characters 6 | func formatGolangComment(comment string) string { 7 | if len(comment) == 0 { 8 | return "" 9 | } 10 | 11 | // Format as colang comment and remove ascii control characters from string 12 | return "// " + regexp.MustCompile(`[[:cntrl:]]+`).ReplaceAllString(comment, "") 13 | } 14 | -------------------------------------------------------------------------------- /examples/quick-start/.gen/jetdb/dvds/model/film_actor.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package model 9 | 10 | import ( 11 | "time" 12 | ) 13 | 14 | type FilmActor struct { 15 | ActorID int16 `sql:"primary_key"` 16 | FilmID int16 `sql:"primary_key"` 17 | LastUpdate time.Time 18 | } 19 | -------------------------------------------------------------------------------- /internal/jet/cast_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCastAS(t *testing.T) { 8 | assertClauseSerialize(t, NewCastImpl(Int(1)).AS("boolean"), "CAST($1 AS boolean)", int64(1)) 9 | assertClauseSerialize(t, NewCastImpl(table2Col3).AS("real"), "CAST(table2.col3 AS real)") 10 | assertClauseSerialize(t, NewCastImpl(table2Col3.ADD(table2Col3)).AS("integer"), "CAST((table2.col3 + table2.col3) AS integer)") 11 | } 12 | -------------------------------------------------------------------------------- /internal/jet/column_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import "testing" 4 | 5 | func TestColumn(t *testing.T) { 6 | column := NewColumnImpl("col", "", nil) 7 | 8 | assertClauseSerialize(t, column, "col") 9 | column.setTableName("table1") 10 | assertClauseSerialize(t, column, "table1.col") 11 | assertProjectionSerialize(t, column, `table1.col AS "table1.col"`) 12 | assertProjectionSerialize(t, column.AS("alias1"), `table1.col AS "alias1"`) 13 | } 14 | -------------------------------------------------------------------------------- /examples/quick-start/.gen/jetdb/dvds/model/film_category.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package model 9 | 10 | import ( 11 | "time" 12 | ) 13 | 14 | type FilmCategory struct { 15 | FilmID int16 `sql:"primary_key"` 16 | CategoryID int16 `sql:"primary_key"` 17 | LastUpdate time.Time 18 | } 19 | -------------------------------------------------------------------------------- /generator/metadata/schema_meta_data.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | // Schema struct 4 | type Schema struct { 5 | Name string 6 | TablesMetaData []Table 7 | ViewsMetaData []Table 8 | EnumsMetaData []Enum 9 | } 10 | 11 | // IsEmpty returns true if schema info does not contain any table, views or enums metadata 12 | func (s Schema) IsEmpty() bool { 13 | return len(s.TablesMetaData) == 0 && len(s.ViewsMetaData) == 0 && len(s.EnumsMetaData) == 0 14 | } 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: missing feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | -------------------------------------------------------------------------------- /mysql/lock_statement_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "testing" 4 | 5 | func TestLockRead(t *testing.T) { 6 | assertStatementSql(t, table2.LOCK().READ(), ` 7 | LOCK TABLES db.table2 READ; 8 | `) 9 | } 10 | 11 | func TestLockWrite(t *testing.T) { 12 | assertStatementSql(t, table2.LOCK().WRITE(), ` 13 | LOCK TABLES db.table2 WRITE; 14 | `) 15 | } 16 | 17 | func TestUNLOCK_TABLES(t *testing.T) { 18 | assertStatementSql(t, UNLOCK_TABLES(), ` 19 | UNLOCK TABLES; 20 | `) 21 | } 22 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | # The default concurrency value is the number of available CPU. 3 | concurrency: 4 4 | # Timeout for analysis, e.g. 30s, 5m. 5 | # Default: 1m 6 | timeout: 30m 7 | # Exit code when at least one issue was found. 8 | # Default: 1 9 | issues-exit-code: 2 10 | # Include test files or not. 11 | # Default: true 12 | tests: false 13 | 14 | issues: 15 | exclude-dirs: 16 | - tests 17 | exclude-files: 18 | - "_test.go" 19 | - "testutils.go" 20 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Jet 2 | Copyright 2019 Goran Bjelanovic 3 | 4 | 5 | This product contains a modified portion of 'godropbox' which can be obtained at: 6 | https://github.com/dropbox/godropbox/tree/master/database/sqlbuilder (BSD-3) 7 | 8 | 9 | This product contains a modified portion of 'snaker' which can be obtained at: 10 | https://github.com/serenize/snaker (MIT) 11 | 12 | 13 | This product contains `FormatTimestamp` function from 'pq' which can be obtained at: 14 | https://github.com/lib/pq (MIT) 15 | -------------------------------------------------------------------------------- /examples/quick-start/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Quick start example 3 | 4 | This package contains sample usage for Jet framework. 5 | 6 | Jet generated files of interest are in `./gen` folder. 7 | 8 | `quick-start.go` - contains code explained at main [README.md](../../README.md#quick-start), 9 | with a difference of redirecting json output to files(`dest.json` and `dest2.json`) rather then to a 10 | standard output. 11 | 12 | `./gen`, `dest.json` and `dest2.json` - added into git for presentation purposes. 13 | -------------------------------------------------------------------------------- /postgres/keywords.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | const ( 6 | // DEFAULT is jet equivalent of SQL DEFAULT 7 | DEFAULT = jet.DEFAULT 8 | ) 9 | 10 | var ( 11 | // NULL is jet equivalent of SQL NULL 12 | NULL = jet.NULL 13 | // STAR is jet equivalent of SQL * 14 | STAR = jet.STAR 15 | // PLUS_INFINITY is jet equivalent for sql infinity 16 | PLUS_INFINITY = jet.PLUS_INFINITY 17 | // MINUS_INFINITY is jet equivalent for sql -infinity 18 | MINUS_INFINITY = jet.MINUS_INFINITY 19 | ) 20 | -------------------------------------------------------------------------------- /sqlite/cast_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCAST(t *testing.T) { 8 | assertSerialize(t, CAST(Float(11.22)).AS("bigint"), `CAST(? AS bigint)`) 9 | assertSerialize(t, CAST(Int(22)).AS_TEXT(), `CAST(? AS TEXT)`) 10 | assertSerialize(t, CAST(Int(22)).AS_NUMERIC(), `CAST(? AS NUMERIC)`) 11 | assertSerialize(t, CAST(String("22")).AS_INTEGER(), `CAST(? AS INTEGER)`) 12 | assertSerialize(t, CAST(String("22.2")).AS_REAL(), `CAST(? AS REAL)`) 13 | assertSerialize(t, CAST(String("blob")).AS_BLOB(), `CAST(? AS BLOB)`) 14 | } 15 | -------------------------------------------------------------------------------- /internal/jet/column_list_assigment.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | type expressionOrColumnList interface { 4 | Serializer 5 | isExpressionOrColumnList() 6 | } 7 | 8 | type columnListAssigment []ColumnAssigment 9 | 10 | func (c columnListAssigment) isColumnAssignment() {} 11 | 12 | func (c columnListAssigment) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { 13 | for i, columnAssigment := range c { 14 | if i > 0 { 15 | out.WriteString(",") 16 | out.NewLine() 17 | } 18 | 19 | columnAssigment.serialize(statement, out, options...) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /internal/jet/enum_value.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | type enumValue struct { 4 | ExpressionInterfaceImpl 5 | stringInterfaceImpl 6 | 7 | name string 8 | } 9 | 10 | // NewEnumValue creates new named enum value 11 | func NewEnumValue(name string) StringExpression { 12 | enumValue := &enumValue{name: name} 13 | 14 | enumValue.ExpressionInterfaceImpl.Root = enumValue 15 | enumValue.stringInterfaceImpl.root = enumValue 16 | 17 | return enumValue 18 | } 19 | 20 | func (e enumValue) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { 21 | out.insertConstantArgument(e.name) 22 | } 23 | -------------------------------------------------------------------------------- /mysql/lateral.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // LATERAL derived tables constructor from select statement 6 | func LATERAL(selectStmt SelectStatement) lateralImpl { 7 | return lateralImpl{ 8 | selectStmt: selectStmt, 9 | } 10 | } 11 | 12 | type lateralImpl struct { 13 | selectStmt SelectStatement 14 | } 15 | 16 | func (l lateralImpl) AS(alias string) SelectTable { 17 | subQuery := &selectTableImpl{ 18 | SelectTable: jet.NewLateral(l.selectStmt, alias), 19 | } 20 | 21 | subQuery.readableTableInterfaceImpl.root = subQuery 22 | 23 | return subQuery 24 | } 25 | -------------------------------------------------------------------------------- /mysql/optimizer_hints.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-jet/jet/v2/internal/jet" 6 | ) 7 | 8 | // OptimizerHint provides a way to optimize query execution per-statement basis 9 | type OptimizerHint = jet.OptimizerHint 10 | 11 | // MAX_EXECUTION_TIME limits statement execution time 12 | func MAX_EXECUTION_TIME(miliseconds int) OptimizerHint { 13 | return OptimizerHint(fmt.Sprintf("MAX_EXECUTION_TIME(%d)", miliseconds)) 14 | } 15 | 16 | // QB_NAME assigns name to query block 17 | func QB_NAME(name string) OptimizerHint { 18 | return OptimizerHint(fmt.Sprintf("QB_NAME(%s)", name)) 19 | } 20 | -------------------------------------------------------------------------------- /postgres/lateral.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // LATERAL derived tables constructor from select statement 6 | func LATERAL(selectStmt SelectStatement) lateralImpl { 7 | return lateralImpl{ 8 | selectStmt: selectStmt, 9 | } 10 | } 11 | 12 | type lateralImpl struct { 13 | selectStmt SelectStatement 14 | } 15 | 16 | func (l lateralImpl) AS(alias string) SelectTable { 17 | subQuery := &selectTableImpl{ 18 | SelectTable: jet.NewLateral(l.selectStmt, alias), 19 | } 20 | 21 | subQuery.readableTableInterfaceImpl.root = subQuery 22 | 23 | return subQuery 24 | } 25 | -------------------------------------------------------------------------------- /internal/jet/column_assigment.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | // ColumnAssigment is interface wrapper around column assignment 4 | type ColumnAssigment interface { 5 | Serializer 6 | isColumnAssignment() 7 | } 8 | 9 | type columnAssigmentImpl struct { 10 | column ColumnSerializer 11 | toAssign Serializer 12 | } 13 | 14 | func (a columnAssigmentImpl) isColumnAssignment() {} 15 | 16 | func (a columnAssigmentImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { 17 | a.column.serialize(statement, out, ShortName.WithFallTrough(options)...) 18 | out.WriteString("=") 19 | a.toAssign.serialize(statement, out, FallTrough(options)...) 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Environment (please complete the following information):** 14 | - OS: [e.g. linux, windows, macosx] 15 | - Database: [e.g. postgres, mysql, sqlite] 16 | - Database driver: [e.g. pq, pgx] 17 | - Jet version [e.g. 2.6.0 or branch name] 18 | 19 | **Code snippet** 20 | Query statement and model files of interest. 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | -------------------------------------------------------------------------------- /mysql/select_table.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // SelectTable is interface for MySQL sub-queries 6 | type SelectTable interface { 7 | readableTable 8 | jet.SelectTable 9 | } 10 | 11 | type selectTableImpl struct { 12 | jet.SelectTable 13 | readableTableInterfaceImpl 14 | } 15 | 16 | func newSelectTable(selectStmt jet.SerializerHasProjections, alias string, columnAliases []jet.ColumnExpression) SelectTable { 17 | subQuery := &selectTableImpl{ 18 | SelectTable: jet.NewSelectTable(selectStmt, alias, columnAliases), 19 | } 20 | 21 | subQuery.readableTableInterfaceImpl.root = subQuery 22 | 23 | return subQuery 24 | } 25 | -------------------------------------------------------------------------------- /sqlite/select_table.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // SelectTable is interface for MySQL sub-queries 6 | type SelectTable interface { 7 | readableTable 8 | jet.SelectTable 9 | } 10 | 11 | type selectTableImpl struct { 12 | jet.SelectTable 13 | readableTableInterfaceImpl 14 | } 15 | 16 | func newSelectTable(selectStmt jet.SerializerHasProjections, alias string, columnAliases []jet.ColumnExpression) SelectTable { 17 | subQuery := &selectTableImpl{ 18 | SelectTable: jet.NewSelectTable(selectStmt, alias, columnAliases), 19 | } 20 | 21 | subQuery.readableTableInterfaceImpl.root = subQuery 22 | 23 | return subQuery 24 | } 25 | -------------------------------------------------------------------------------- /sqlite/set_statement_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSelectSets(t *testing.T) { 8 | select1 := SELECT(table1ColBool).FROM(table1) 9 | select2 := SELECT(table2ColBool).FROM(table2) 10 | 11 | assertStatementSql(t, select1.UNION(select2), ` 12 | 13 | SELECT table1.col_bool AS "table1.col_bool" 14 | FROM db.table1 15 | 16 | UNION 17 | 18 | SELECT table2.col_bool AS "table2.col_bool" 19 | FROM db.table2; 20 | `) 21 | assertStatementSql(t, select1.UNION_ALL(select2), ` 22 | 23 | SELECT table1.col_bool AS "table1.col_bool" 24 | FROM db.table1 25 | 26 | UNION ALL 27 | 28 | SELECT table2.col_bool AS "table2.col_bool" 29 | FROM db.table2; 30 | `) 31 | } 32 | -------------------------------------------------------------------------------- /examples/quick-start/.gen/jetdb/dvds/enum/mpaa_rating.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package enum 9 | 10 | import "github.com/go-jet/jet/v2/postgres" 11 | 12 | var MpaaRating = &struct { 13 | G postgres.StringExpression 14 | Pg postgres.StringExpression 15 | Pg13 postgres.StringExpression 16 | R postgres.StringExpression 17 | Nc17 postgres.StringExpression 18 | }{ 19 | G: postgres.NewEnumValue("G"), 20 | Pg: postgres.NewEnumValue("PG"), 21 | Pg13: postgres.NewEnumValue("PG-13"), 22 | R: postgres.NewEnumValue("R"), 23 | Nc17: postgres.NewEnumValue("NC-17"), 24 | } 25 | -------------------------------------------------------------------------------- /qrm/type_stack.go: -------------------------------------------------------------------------------- 1 | package qrm 2 | 3 | import "reflect" 4 | 5 | type typeStack []*reflect.Type 6 | 7 | func newTypeStack() typeStack { 8 | stack := make(typeStack, 0, 20) 9 | return stack 10 | } 11 | 12 | func (s *typeStack) isEmpty() bool { 13 | return len(*s) == 0 14 | } 15 | 16 | func (s *typeStack) push(t *reflect.Type) { 17 | *s = append(*s, t) 18 | } 19 | 20 | func (s *typeStack) pop() bool { 21 | if s.isEmpty() { 22 | return false 23 | } 24 | *s = (*s)[:len(*s)-1] 25 | return true 26 | } 27 | 28 | func (s *typeStack) contains(t *reflect.Type) bool { 29 | if s.isEmpty() { 30 | return false 31 | } 32 | 33 | for _, typ := range *s { 34 | if *typ == *t { 35 | return true 36 | } 37 | } 38 | 39 | return false 40 | } 41 | -------------------------------------------------------------------------------- /examples/quick-start/.gen/jetdb/dvds/model/film.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package model 9 | 10 | import ( 11 | "github.com/lib/pq" 12 | "time" 13 | ) 14 | 15 | type Film struct { 16 | FilmID int32 `sql:"primary_key"` 17 | Title string 18 | Description *string 19 | ReleaseYear *int32 20 | LanguageID int16 21 | RentalDuration int16 22 | RentalRate float64 23 | Length *int16 24 | ReplacementCost float64 25 | Rating *MpaaRating 26 | LastUpdate time.Time 27 | SpecialFeatures *pq.StringArray 28 | Fulltext string 29 | } 30 | -------------------------------------------------------------------------------- /mysql/set_statement_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSelectSets(t *testing.T) { 8 | select1 := SELECT(table1ColBool).FROM(table1) 9 | select2 := SELECT(table2ColBool).FROM(table2) 10 | 11 | assertStatementSql(t, select1.UNION(select2), ` 12 | ( 13 | SELECT table1.col_bool AS "table1.col_bool" 14 | FROM db.table1 15 | ) 16 | UNION 17 | ( 18 | SELECT table2.col_bool AS "table2.col_bool" 19 | FROM db.table2 20 | ); 21 | `) 22 | assertStatementSql(t, select1.UNION_ALL(select2), ` 23 | ( 24 | SELECT table1.col_bool AS "table1.col_bool" 25 | FROM db.table1 26 | ) 27 | UNION ALL 28 | ( 29 | SELECT table2.col_bool AS "table2.col_bool" 30 | FROM db.table2 31 | ); 32 | `) 33 | } 34 | -------------------------------------------------------------------------------- /postgres/select_table.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // SelectTable is interface for postgres temporary tables like sub-queries, VALUES, CTEs etc... 6 | type SelectTable interface { 7 | readableTable 8 | jet.SelectTable 9 | } 10 | 11 | type selectTableImpl struct { 12 | jet.SelectTable 13 | readableTableInterfaceImpl 14 | } 15 | 16 | func newSelectTable(serializerWithProjections jet.SerializerHasProjections, alias string, columnAliases []jet.ColumnExpression) SelectTable { 17 | subQuery := &selectTableImpl{ 18 | SelectTable: jet.NewSelectTable(serializerWithProjections, alias, columnAliases), 19 | } 20 | 21 | subQuery.readableTableInterfaceImpl.root = subQuery 22 | 23 | return subQuery 24 | } 25 | -------------------------------------------------------------------------------- /tests/mysql/update_dvds_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/go-jet/jet/v2/internal/testutils" 7 | . "github.com/go-jet/jet/v2/mysql" 8 | . "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table" 9 | ) 10 | 11 | func TestUpdateWithJoin(t *testing.T) { 12 | statement := Staff.INNER_JOIN(Address, Address.AddressID.EQ(Staff.AddressID)). 13 | UPDATE(Staff.LastName). 14 | SET(String("New staff name")). 15 | WHERE(Staff.StaffID.EQ(Int(1))) 16 | 17 | testutils.AssertStatementSql(t, statement, ` 18 | UPDATE dvds.staff 19 | INNER JOIN dvds.address ON (address.address_id = staff.address_id) 20 | SET last_name = ? 21 | WHERE staff.staff_id = ?; 22 | `, "New staff name", int64(1)) 23 | 24 | testutils.AssertExecAndRollback(t, statement, db) 25 | } 26 | -------------------------------------------------------------------------------- /postgres/delete_statement_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDeleteUnconditionally(t *testing.T) { 8 | assertStatementSqlErr(t, table1.DELETE(), `jet: WHERE clause not set`) 9 | assertStatementSqlErr(t, table1.DELETE().WHERE(nil), `jet: WHERE clause not set`) 10 | } 11 | 12 | func TestDeleteWithWhere(t *testing.T) { 13 | assertStatementSql(t, table1.DELETE().WHERE(table1Col1.EQ(Int(1))), ` 14 | DELETE FROM db.table1 15 | WHERE table1.col1 = $1; 16 | `, int64(1)) 17 | } 18 | 19 | func TestDeleteWithWhereAndReturning(t *testing.T) { 20 | assertStatementSql(t, table1.DELETE().WHERE(table1Col1.EQ(Int(1))).RETURNING(table1Col1), ` 21 | DELETE FROM db.table1 22 | WHERE table1.col1 = $1 23 | RETURNING table1.col1 AS "table1.col1"; 24 | `, int64(1)) 25 | } 26 | -------------------------------------------------------------------------------- /sqlite/delete_statement_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDeleteUnconditionally(t *testing.T) { 8 | assertStatementSqlErr(t, table1.DELETE(), `jet: WHERE clause not set`) 9 | assertStatementSqlErr(t, table1.DELETE().WHERE(nil), `jet: WHERE clause not set`) 10 | } 11 | 12 | func TestDeleteWithWhere(t *testing.T) { 13 | assertStatementSql(t, table1.DELETE().WHERE(table1Col1.EQ(Int(1))), ` 14 | DELETE FROM db.table1 15 | WHERE table1.col1 = ?; 16 | `, int64(1)) 17 | } 18 | 19 | func TestDeleteWithWhereOrderByLimit(t *testing.T) { 20 | assertStatementSql(t, table1.DELETE().WHERE(table1Col1.EQ(Int(1))).ORDER_BY(table1Col1).LIMIT(1), ` 21 | DELETE FROM db.table1 22 | WHERE table1.col1 = ? 23 | ORDER BY table1.col1 24 | LIMIT ?; 25 | `, int64(1), int64(1)) 26 | } 27 | -------------------------------------------------------------------------------- /internal/jet/values.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | // Values hold a set of one or more rows 4 | type Values []RowExpression 5 | 6 | func (v Values) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { 7 | out.WriteByte('(') 8 | out.IncreaseIdent(5) 9 | 10 | out.NewLine() 11 | out.WriteString("VALUES") 12 | 13 | for rowIndex, row := range v { 14 | if rowIndex > 0 { 15 | out.WriteString(",") 16 | out.NewLine() 17 | } else { 18 | out.IncreaseIdent(7) 19 | } 20 | 21 | row.serialize(statement, out, options...) 22 | } 23 | out.DecreaseIdent(7) 24 | out.DecreaseIdent(5) 25 | out.NewLine() 26 | out.WriteByte(')') 27 | } 28 | 29 | func (v Values) projections() ProjectionList { 30 | if len(v) == 0 { 31 | return nil 32 | } 33 | 34 | return v[0].projections() 35 | } 36 | -------------------------------------------------------------------------------- /tests/internal/utils/file/file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | ) 9 | 10 | // Exists expects file to exist on path constructed from pathElems and returns content of the file 11 | func Exists(t *testing.T, pathElems ...string) (fileContent string) { 12 | modelFilePath := filepath.Join(pathElems...) 13 | file, err := os.ReadFile(modelFilePath) // #nosec G304 14 | require.Nil(t, err) 15 | require.NotEmpty(t, file) 16 | return string(file) 17 | } 18 | 19 | // NotExists expects file not to exist on path constructed from pathElems 20 | func NotExists(t *testing.T, pathElems ...string) { 21 | modelFilePath := filepath.Join(pathElems...) 22 | _, err := os.ReadFile(modelFilePath) // #nosec G304 23 | require.True(t, os.IsNotExist(err)) 24 | } 25 | -------------------------------------------------------------------------------- /mysql/cast_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCAST(t *testing.T) { 8 | assertSerialize(t, CAST(Float(11.22)).AS("bigint"), `CAST(? AS bigint)`) 9 | assertSerialize(t, CAST(Int(22)).AS_CHAR(), `CAST(? AS CHAR)`) 10 | assertSerialize(t, CAST(Int(22)).AS_CHAR(10), `CAST(? AS CHAR(10))`) 11 | assertSerialize(t, CAST(Int(22)).AS_DATE(), `CAST(? AS DATE)`) 12 | assertSerialize(t, CAST(Int(22)).AS_DECIMAL(), `CAST(? AS DECIMAL)`) 13 | assertSerialize(t, CAST(Int(22)).AS_TIME(), `CAST(? AS TIME)`) 14 | assertSerialize(t, CAST(Int(22)).AS_DATETIME(), `CAST(? AS DATETIME)`) 15 | assertSerialize(t, CAST(Int(22)).AS_SIGNED(), `CAST(? AS SIGNED)`) 16 | assertSerialize(t, CAST(Int(22)).AS_UNSIGNED(), `CAST(? AS UNSIGNED)`) 17 | assertSerialize(t, CAST(Int(22)).AS_BINARY(), `CAST(? AS BINARY)`) 18 | } 19 | -------------------------------------------------------------------------------- /sqlite/values.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | type values struct { 6 | jet.Values 7 | } 8 | 9 | // VALUES is a table value constructor that computes a set of one or more rows as a temporary constant table. 10 | // Each row is defined by the ROW constructor, which takes one or more expressions. 11 | // 12 | // Example usage: 13 | // 14 | // VALUES( 15 | // ROW(Int32(204), Float32(1.21)), 16 | // ROW(Int32(207), Float32(1.02)), 17 | // ) 18 | func VALUES(rows ...RowExpression) values { 19 | return values{Values: jet.Values(rows)} 20 | } 21 | 22 | // AS assigns an alias to the temporary VALUES table, allowing it to be referenced 23 | // within SQL FROM clauses, just like a regular table. 24 | func (v values) AS(alias string) SelectTable { 25 | return newSelectTable(v, alias, nil) 26 | } 27 | -------------------------------------------------------------------------------- /generator/metadata/table_meta_data.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | // Table metadata struct 4 | type Table struct { 5 | Name string `sql:"primary_key"` 6 | Comment string 7 | Columns []Column 8 | } 9 | 10 | // MutableColumns returns list of mutable columns for table 11 | func (t Table) MutableColumns() []Column { 12 | var ret []Column 13 | 14 | for _, column := range t.Columns { 15 | if column.IsPrimaryKey || column.IsGenerated { 16 | continue 17 | } 18 | 19 | ret = append(ret, column) 20 | } 21 | 22 | return ret 23 | } 24 | 25 | // DefaultColumns returns list of columns with default values set for table 26 | func (t Table) DefaultColumns() []Column { 27 | var ret []Column 28 | 29 | for _, column := range t.Columns { 30 | if column.HasDefault { 31 | ret = append(ret, column) 32 | } 33 | } 34 | 35 | return ret 36 | } 37 | -------------------------------------------------------------------------------- /internal/jet/column_list_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "testing" 6 | ) 7 | 8 | func TestColumnList_SET(t *testing.T) { 9 | columnList1 := ColumnList{IntegerColumn("id"), StringColumn("Name"), BoolColumn("active")} 10 | columnList2 := ColumnList{IntegerColumn("id"), StringColumn("Name"), BoolColumn("active")} 11 | 12 | columnList1.SET(columnList2) 13 | 14 | columnList3 := ColumnList{IntegerColumn("id"), StringColumn("Name")} 15 | 16 | require.PanicsWithValue(t, "jet: column list length mismatch: expected 2 columns, got 3", func() { 17 | columnList3.SET(columnList1) 18 | }) 19 | 20 | columnList4 := ColumnList{IntegerColumn("id"), StringColumn("FullName"), BoolColumn("active")} 21 | 22 | require.PanicsWithValue(t, "jet: column name mismatch at index 1: expected column 'Name', got 'FullName'", func() { 23 | columnList1.SET(columnList4) 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /qrm/db.go: -------------------------------------------------------------------------------- 1 | package qrm 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | ) 7 | 8 | // DB is common database interface used by query result mapping 9 | // Both *sql.DB and *sql.Tx implements DB interface 10 | type DB interface { 11 | Exec(query string, args ...interface{}) (sql.Result, error) 12 | ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) 13 | Query(query string, args ...interface{}) (*sql.Rows, error) 14 | QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) 15 | } 16 | 17 | // Queryable interface for sql QueryContext method 18 | type Queryable interface { 19 | QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) 20 | } 21 | 22 | // Executable interface for sql ExecContext method 23 | type Executable interface { 24 | ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) 25 | } 26 | -------------------------------------------------------------------------------- /internal/jet/utils_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestOptionalOrDefaultString(t *testing.T) { 10 | require.Equal(t, OptionalOrDefaultString("default"), "default") 11 | require.Equal(t, OptionalOrDefaultString("default", "optional"), "optional") 12 | } 13 | 14 | func TestOptionalOrDefaultExpression(t *testing.T) { 15 | defaultExpression := table2ColFloat 16 | optionalExpression := table1Col1 17 | 18 | require.Equal(t, OptionalOrDefaultExpression(defaultExpression), defaultExpression) 19 | require.Equal(t, OptionalOrDefaultExpression(defaultExpression, optionalExpression), optionalExpression) 20 | } 21 | 22 | func TestJoinAlias(t *testing.T) { 23 | require.Equal(t, joinAlias("", ""), "") 24 | require.Equal(t, joinAlias("foo", "bar"), "foo.bar") 25 | require.Equal(t, joinAlias("foo.*", "bar"), "foo.bar") 26 | require.Equal(t, joinAlias("", "bar"), "bar") 27 | } 28 | -------------------------------------------------------------------------------- /generator/metadata/column_meta_data.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | // Column struct 4 | type Column struct { 5 | Name string `sql:"primary_key"` 6 | IsPrimaryKey bool 7 | IsNullable bool 8 | IsGenerated bool 9 | HasDefault bool 10 | DataType DataType 11 | Comment string 12 | } 13 | 14 | // DataTypeKind is database type kind(base, enum, user-defined, array) 15 | type DataTypeKind string 16 | 17 | // DataTypeKind possible values 18 | const ( 19 | BaseType DataTypeKind = "base" 20 | EnumType DataTypeKind = "enum" 21 | UserDefinedType DataTypeKind = "user-defined" 22 | RangeType DataTypeKind = "range" 23 | ) 24 | 25 | // DataType contains information about column data type 26 | type DataType struct { 27 | Name string 28 | Kind DataTypeKind 29 | IsUnsigned bool 30 | Dimensions int // The number of array dimensions 31 | } 32 | 33 | func (d DataType) IsArray() bool { 34 | return d.Dimensions > 0 35 | } 36 | -------------------------------------------------------------------------------- /postgres/columns_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewIntervalColumn(t *testing.T) { 8 | subQuery := SELECT(Int(1)).AsTable("sub_query") 9 | 10 | subQueryIntervalColumn := IntervalColumn("col_interval").From(subQuery) 11 | assertSerialize(t, subQueryIntervalColumn, `sub_query.col_interval`) 12 | assertSerialize(t, subQueryIntervalColumn.EQ(INTERVAL(2, HOUR, 10, MINUTE)), 13 | `(sub_query.col_interval = INTERVAL '2 HOUR 10 MINUTE')`) 14 | assertProjectionSerialize(t, subQueryIntervalColumn, `sub_query.col_interval AS "col_interval"`) 15 | 16 | subQueryIntervalColumn2 := table1ColInterval.From(subQuery) 17 | assertSerialize(t, subQueryIntervalColumn2, `sub_query."table1.col_interval"`) 18 | assertSerialize(t, subQueryIntervalColumn2.EQ(INTERVAL(1, DAY)), `(sub_query."table1.col_interval" = INTERVAL '1 DAY')`) 19 | assertProjectionSerialize(t, subQueryIntervalColumn2, `sub_query."table1.col_interval" AS "table1.col_interval"`) 20 | } 21 | -------------------------------------------------------------------------------- /postgres/conflict_action.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | type conflictAction interface { 6 | jet.Serializer 7 | WHERE(condition BoolExpression) conflictAction 8 | } 9 | 10 | // SET creates conflict action for ON_CONFLICT clause 11 | func SET(assigments ...ColumnAssigment) conflictAction { 12 | conflictAction := updateConflictActionImpl{} 13 | conflictAction.doUpdate = jet.KeywordClause{Keyword: "DO UPDATE"} 14 | conflictAction.Serializer = jet.NewSerializerClauseImpl(&conflictAction.doUpdate, &conflictAction.set, &conflictAction.where) 15 | conflictAction.set = assigments 16 | return &conflictAction 17 | } 18 | 19 | type updateConflictActionImpl struct { 20 | jet.Serializer 21 | 22 | doUpdate jet.KeywordClause 23 | set jet.SetClauseNew 24 | where jet.ClauseWhere 25 | } 26 | 27 | func (u *updateConflictActionImpl) WHERE(condition BoolExpression) conflictAction { 28 | u.where.Condition = condition 29 | return u 30 | } 31 | -------------------------------------------------------------------------------- /tests/internal/utils/repo/repo.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "os/exec" 5 | "path/filepath" 6 | "strings" 7 | ) 8 | 9 | // GetRootDirPath will return this repo full dir path 10 | func GetRootDirPath() string { 11 | cmd := exec.Command("git", "rev-parse", "--show-toplevel") 12 | byteArr, err := cmd.Output() 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | return strings.TrimSpace(string(byteArr)) 18 | } 19 | 20 | // GetTestsDirPath will return tests folder full path 21 | func GetTestsDirPath() string { 22 | return filepath.Join(GetRootDirPath(), "tests") 23 | } 24 | 25 | // GetTestsFilePath will return full file path of the file in the tests folder 26 | func GetTestsFilePath(subPath string) string { 27 | return filepath.Join(GetTestsDirPath(), subPath) 28 | } 29 | 30 | // GetTestDataFilePath will return full file path of the file in the testdata folder 31 | func GetTestDataFilePath(subPath string) string { 32 | return filepath.Join(GetTestsDirPath(), "testdata", subPath) 33 | } 34 | -------------------------------------------------------------------------------- /tests/sqlite/cast_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/testutils" 5 | . "github.com/go-jet/jet/v2/sqlite" 6 | "github.com/stretchr/testify/require" 7 | "testing" 8 | ) 9 | 10 | func TestCast(t *testing.T) { 11 | query := SELECT( 12 | CAST(String("test")).AS("CHARACTER").AS("result.AS1"), 13 | CAST(Float(11.33)).AS_TEXT().AS("result.text"), 14 | CAST(String("33.44")).AS_REAL().AS("result.real"), 15 | CAST(String("33")).AS_INTEGER().AS("result.integer"), 16 | CAST(String("Blob blob")).AS_BLOB().AS("result.blob"), 17 | ) 18 | 19 | type Result struct { 20 | As1 string 21 | Text string 22 | Real float64 23 | Integer int64 24 | Blob []byte 25 | } 26 | 27 | var dest Result 28 | 29 | err := query.Query(db, &dest) 30 | require.NoError(t, err) 31 | 32 | testutils.AssertDeepEqual(t, dest, Result{ 33 | As1: "test", 34 | Text: "11.33", 35 | Real: 33.44, 36 | Integer: 33, 37 | Blob: []byte("Blob blob"), 38 | }) 39 | 40 | requireLogged(t, query) 41 | } 42 | -------------------------------------------------------------------------------- /tests/Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Integration tests 3 | 4 | This folder contains integration tests intended to test jet generator, statements and query result mapping with a running database. 5 | 6 | ## How to run tests? 7 | 8 | Before we can run tests, we need to set up and initialize test databases. 9 | To simplify the process there is a Makefile with a list of helper commands. 10 | ```shell 11 | # We first need to checkout testdata from separate repository into git submodule, 12 | # then download docker image for each of the databases listed in docker-compose.yaml file, and 13 | # finally run and initialize databases with downloaded test data. 14 | # Note that on the first run this command might take a couple of minutes. 15 | make setup 16 | 17 | # When databases are ready, we can generate sql builder and model types for each of the test databases 18 | make jet-gen-all 19 | ``` 20 | 21 | Then we can run the tests the usual way: 22 | ```shell 23 | go test -v ./... 24 | ``` 25 | 26 | To removes test containers, volumes, and images: 27 | ```shell 28 | make cleanup 29 | ``` -------------------------------------------------------------------------------- /mysql/delete_statement_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDeleteUnconditionally(t *testing.T) { 8 | assertStatementSqlErr(t, table1.DELETE(), `jet: WHERE clause not set`) 9 | assertStatementSqlErr(t, table1.DELETE().WHERE(nil), `jet: WHERE clause not set`) 10 | } 11 | 12 | func TestDeleteWithWhere(t *testing.T) { 13 | assertStatementSql(t, table1.DELETE().WHERE(table1Col1.EQ(Int(1))), ` 14 | DELETE FROM db.table1 15 | WHERE table1.col1 = ?; 16 | `, int64(1)) 17 | } 18 | 19 | func TestDeleteWithWhereOrderByLimit(t *testing.T) { 20 | assertStatementSql(t, table1.DELETE().WHERE(table1Col1.EQ(Int(1))).ORDER_BY(table1Col1).LIMIT(1), ` 21 | DELETE FROM db.table1 22 | WHERE table1.col1 = ? 23 | ORDER BY table1.col1 24 | LIMIT ?; 25 | `, int64(1), int64(1)) 26 | } 27 | 28 | func TestDeleteWithWhereAndReturning(t *testing.T) { 29 | assertStatementSql(t, table1.DELETE().WHERE(table1Col1.EQ(Int(1))).RETURNING(table1Col1), ` 30 | DELETE FROM db.table1 31 | WHERE table1.col1 = ? 32 | RETURNING table1.col1 AS "table1.col1"; 33 | `, int64(1)) 34 | } 35 | -------------------------------------------------------------------------------- /postgres/lock_statement_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestLockTable(t *testing.T) { 8 | assertStatementSql(t, table1.LOCK().IN(LOCK_ACCESS_SHARE), ` 9 | LOCK TABLE db.table1 IN ACCESS SHARE MODE; 10 | `) 11 | assertStatementSql(t, table1.LOCK().IN(LOCK_ROW_SHARE), ` 12 | LOCK TABLE db.table1 IN ROW SHARE MODE; 13 | `) 14 | assertStatementSql(t, table1.LOCK().IN(LOCK_ROW_EXCLUSIVE), ` 15 | LOCK TABLE db.table1 IN ROW EXCLUSIVE MODE; 16 | `) 17 | assertStatementSql(t, table1.LOCK().IN(LOCK_SHARE_UPDATE_EXCLUSIVE), ` 18 | LOCK TABLE db.table1 IN SHARE UPDATE EXCLUSIVE MODE; 19 | `) 20 | assertStatementSql(t, table1.LOCK().IN(LOCK_SHARE), ` 21 | LOCK TABLE db.table1 IN SHARE MODE; 22 | `) 23 | assertStatementSql(t, table1.LOCK().IN(LOCK_SHARE_ROW_EXCLUSIVE), ` 24 | LOCK TABLE db.table1 IN SHARE ROW EXCLUSIVE MODE; 25 | `) 26 | assertStatementSql(t, table1.LOCK().IN(LOCK_EXCLUSIVE), ` 27 | LOCK TABLE db.table1 IN EXCLUSIVE MODE; 28 | `) 29 | assertStatementSql(t, table1.LOCK().IN(LOCK_ACCESS_EXCLUSIVE).NOWAIT(), ` 30 | LOCK TABLE db.table1 IN ACCESS EXCLUSIVE MODE NOWAIT; 31 | `) 32 | } 33 | -------------------------------------------------------------------------------- /mysql/values.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | type values struct { 6 | jet.Values 7 | } 8 | 9 | // VALUES is a table value constructor that computes a set of one or more rows as a temporary constant table. 10 | // Each row is defined by the ROW constructor, which takes one or more expressions. 11 | // 12 | // Example usage: 13 | // 14 | // VALUES( 15 | // ROW(Int32(204), Float32(1.21)), 16 | // ROW(Int32(207), Float32(1.02)), 17 | // ) 18 | func VALUES(rows ...RowExpression) values { 19 | return values{Values: jet.Values(rows)} 20 | } 21 | 22 | // AS assigns an alias to the temporary VALUES table, allowing it to be referenced 23 | // within SQL FROM clauses, just like a regular table. 24 | // By default, VALUES columns are named `column1`, `column2`, etc... Default column aliasing can be 25 | // overwritten by passing new list of columns. 26 | // 27 | // Example usage: 28 | // 29 | // VALUES(...).AS("film_values", IntegerColumn("length"), TimestampColumn("update_date")) 30 | func (v values) AS(alias string, columns ...Column) SelectTable { 31 | return newSelectTable(v, alias, columns) 32 | } 33 | -------------------------------------------------------------------------------- /postgres/values.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | type values struct { 6 | jet.Values 7 | } 8 | 9 | // VALUES is a table value constructor that computes a set of one or more rows as a temporary constant table. 10 | // Each row is defined by the WRAP constructor, which takes one or more expressions. 11 | // 12 | // Example usage: 13 | // 14 | // VALUES( 15 | // WRAP(Int32(204), Real(1.21)), 16 | // WRAP(Int32(207), Real(1.02)), 17 | // ) 18 | func VALUES(rows ...RowExpression) values { 19 | return values{Values: jet.Values(rows)} 20 | } 21 | 22 | // AS assigns an alias to the temporary VALUES table, allowing it to be referenced 23 | // within SQL FROM clauses, just like a regular table. 24 | // By default, VALUES columns are named `column1`, `column2`, etc... Default column aliasing can be 25 | // overwritten by passing new list of columns. 26 | // 27 | // Example usage: 28 | // 29 | // VALUES(...).AS("film_values", IntegerColumn("length"), TimestampColumn("update_date")) 30 | func (v values) AS(alias string, columns ...Column) SelectTable { 31 | return newSelectTable(v, alias, columns) 32 | } 33 | -------------------------------------------------------------------------------- /generator/template/sql_builder_template_test.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/generator/metadata" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | func TestToGoEnumValueIdentifier(t *testing.T) { 10 | require.Equal(t, defaultEnumValueName("enum_name", "enum_value"), "EnumValue") 11 | require.Equal(t, defaultEnumValueName("NumEnum", "100"), "NumEnum100") 12 | } 13 | 14 | func TestColumnRenameReserved(t *testing.T) { 15 | tests := []struct { 16 | col string 17 | want string 18 | }{ 19 | {col: "TableName", want: "TableName_"}, 20 | {col: "Table", want: "Table_"}, 21 | {col: "SchemaName", want: "SchemaName_"}, 22 | {col: "Alias", want: "Alias_"}, 23 | {col: "AllColumns", want: "AllColumns_"}, 24 | {col: "MutableColumns", want: "MutableColumns_"}, 25 | {col: "DefaultColumns", want: "DefaultColumns_"}, 26 | {col: "OtherColumn", want: "OtherColumn"}, 27 | } 28 | 29 | for _, tt := range tests { 30 | t.Run(tt.col, func(t *testing.T) { 31 | builder := DefaultTableSQLBuilderColumn(metadata.Column{ 32 | Name: tt.col, 33 | }) 34 | require.Equal(t, builder.Name, tt.want) 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /generator/template/model_template_test.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/generator/metadata" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | func Test_TableModelField(t *testing.T) { 10 | require.Equal(t, DefaultTableModelField(metadata.Column{ 11 | Name: "col_name", 12 | IsPrimaryKey: true, 13 | IsNullable: true, 14 | DataType: metadata.DataType{ 15 | Name: "smallint", 16 | Kind: "base", 17 | IsUnsigned: true, 18 | }, 19 | }), TableModelField{ 20 | Name: "ColName", 21 | Type: Type{ 22 | ImportPath: "", 23 | Name: "*uint16", 24 | }, 25 | Tags: []string{"sql:\"primary_key\""}, 26 | }) 27 | 28 | require.Equal(t, DefaultTableModelField(metadata.Column{ 29 | Name: "time_column_1", 30 | IsPrimaryKey: false, 31 | IsNullable: true, 32 | DataType: metadata.DataType{ 33 | Name: "timestamp with time zone", 34 | Kind: "base", 35 | IsUnsigned: false, 36 | }, 37 | }), TableModelField{ 38 | Name: "TimeColumn1", 39 | Type: Type{ 40 | ImportPath: "time", 41 | Name: "*time.Time", 42 | }, 43 | Tags: nil, 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /internal/utils/must/must.go: -------------------------------------------------------------------------------- 1 | package must 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/utils/is" 5 | "reflect" 6 | ) 7 | 8 | // BeTrue panics when condition is false 9 | func BeTrue(condition bool, errorStr string) { 10 | if !condition { 11 | panic(errorStr) 12 | } 13 | } 14 | 15 | // BeTypeKind panics with errorStr error, if v interface is not of reflect kind 16 | func BeTypeKind(v interface{}, kind reflect.Kind, errorStr string) { 17 | if reflect.TypeOf(v).Kind() != kind { 18 | panic(errorStr) 19 | } 20 | } 21 | 22 | // ValueBeOfTypeKind panics with errorStr error, if v value is not of reflect kind 23 | func ValueBeOfTypeKind(v reflect.Value, kind reflect.Kind, errorStr string) { 24 | if v.Kind() != kind { 25 | panic(errorStr) 26 | } 27 | } 28 | 29 | // TypeBeOfKind panics with errorStr error, if v type is not of reflect kind 30 | func TypeBeOfKind(v reflect.Type, kind reflect.Kind, errorStr string) { 31 | if v.Kind() != kind { 32 | panic(errorStr) 33 | } 34 | } 35 | 36 | // BeInitializedPtr panics with errorStr if val interface is nil 37 | func BeInitializedPtr(val interface{}, errorStr string) { 38 | if is.Nil(val) { 39 | panic(errorStr) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/3rdparty/pq/format_timestamp.go: -------------------------------------------------------------------------------- 1 | package pq 2 | 3 | // Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany 4 | 5 | import ( 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | // FormatTimestamp formats t into Postgres' text format for timestamps. From: github.com/lib/pq 11 | func FormatTimestamp(t time.Time) []byte { 12 | // Need to send dates before 0001 A.D. with " BC" suffix, instead of the 13 | // minus sign preferred by Go. 14 | // Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on 15 | bc := false 16 | if t.Year() <= 0 { 17 | // flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11" 18 | t = t.AddDate((-t.Year())*2+1, 0, 0) 19 | bc = true 20 | } 21 | b := []byte(t.Format("2006-01-02 15:04:05.999999999Z07:00")) 22 | 23 | _, offset := t.Zone() 24 | offset = offset % 60 25 | if offset != 0 { 26 | // RFC3339Nano already printed the minus sign 27 | if offset < 0 { 28 | offset = -offset 29 | } 30 | 31 | b = append(b, ':') 32 | if offset < 10 { 33 | b = append(b, '0') 34 | } 35 | b = strconv.AppendInt(b, int64(offset), 10) 36 | } 37 | 38 | if bc { 39 | b = append(b, " BC"...) 40 | } 41 | return b 42 | } 43 | -------------------------------------------------------------------------------- /internal/jet/table_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "testing" 6 | ) 7 | 8 | func TestNewTable(t *testing.T) { 9 | newTable := NewTable("schema", "table", "", IntegerColumn("intCol")) 10 | 11 | require.Equal(t, newTable.SchemaName(), "schema") 12 | require.Equal(t, newTable.TableName(), "table") 13 | 14 | require.Equal(t, len(newTable.columns()), 1) 15 | require.Equal(t, newTable.columns()[0].Name(), "intCol") 16 | } 17 | 18 | func TestNewJoinTable(t *testing.T) { 19 | newTable1 := NewTable("schema", "table", "", IntegerColumn("intCol1")) 20 | newTable2 := NewTable("schema", "table2", "", IntegerColumn("intCol2")) 21 | 22 | joinTable := NewJoinTable(newTable1, newTable2, InnerJoin, IntegerColumn("intCol1").EQ(IntegerColumn("intCol2"))) 23 | 24 | assertClauseSerialize(t, joinTable, `schema.table 25 | INNER JOIN schema.table2 ON ("intCol1" = "intCol2")`) 26 | 27 | require.Equal(t, joinTable.SchemaName(), "schema") 28 | require.Equal(t, joinTable.TableName(), "") 29 | 30 | require.Equal(t, len(joinTable.columns()), 2) 31 | require.Equal(t, joinTable.columns()[0].Name(), "intCol1") 32 | require.Equal(t, joinTable.columns()[1].Name(), "intCol2") 33 | } 34 | -------------------------------------------------------------------------------- /sqlite/cast.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/jet" 5 | ) 6 | 7 | type cast struct { 8 | jet.Cast 9 | } 10 | 11 | // CAST function converts a expr (of any type) into latter specified datatype. 12 | func CAST(expr Expression) *cast { 13 | ret := &cast{} 14 | ret.Cast = jet.NewCastImpl(expr) 15 | 16 | return ret 17 | } 18 | 19 | // AS casts expressions to castType 20 | func (c *cast) AS(castType string) Expression { 21 | return c.Cast.AS(castType) 22 | } 23 | 24 | // AS_TEXT cast expression to TEXT type 25 | func (c *cast) AS_TEXT() StringExpression { 26 | return StringExp(c.AS("TEXT")) 27 | } 28 | 29 | // AS_NUMERIC cast expression to NUMERIC type 30 | func (c *cast) AS_NUMERIC() FloatExpression { 31 | return FloatExp(c.AS("NUMERIC")) 32 | } 33 | 34 | // AS_INTEGER cast expression to INTEGER type 35 | func (c *cast) AS_INTEGER() IntegerExpression { 36 | return IntExp(c.AS("INTEGER")) 37 | } 38 | 39 | // AS_REAL cast expression to REAL type 40 | func (c *cast) AS_REAL() FloatExpression { 41 | return FloatExp(c.AS("REAL")) 42 | } 43 | 44 | // AS_BLOB cast expression to BLOB type 45 | func (c *cast) AS_BLOB() BlobExpression { 46 | return BlobExp(c.AS("BLOB")) 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/code_scanner.yml: -------------------------------------------------------------------------------- 1 | name: Code Scanners 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | 10 | permissions: 11 | contents: read 12 | 13 | env: 14 | go_version: "1.22.8" 15 | 16 | 17 | jobs: 18 | security_scanning: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout Source 22 | uses: actions/checkout@v4 23 | - uses: actions/setup-go@v5 24 | with: 25 | go-version: ${{ env.go_version }} 26 | cache: true 27 | - name: Setup Tools 28 | run: | 29 | go install github.com/securego/gosec/v2/cmd/gosec@latest 30 | - name: Running Scan 31 | run: gosec --exclude=G402,G304 ./... 32 | lint_scanner: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout Source 36 | uses: actions/checkout@v4 37 | - uses: actions/setup-go@v5 38 | with: 39 | go-version: ${{ env.go_version }} 40 | cache: true 41 | - name: Setup Tools 42 | run: | 43 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest 44 | - name: Running Scan 45 | run: golangci-lint run --timeout=30m ./... 46 | -------------------------------------------------------------------------------- /internal/3rdparty/snaker/snaker_test.go: -------------------------------------------------------------------------------- 1 | package snaker 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "testing" 6 | ) 7 | 8 | func TestSnakeToCamel(t *testing.T) { 9 | require.Equal(t, SnakeToCamel(""), "") 10 | require.Equal(t, SnakeToCamel("_", false), "") 11 | require.Equal(t, SnakeToCamel("potato_"), "Potato") 12 | require.Equal(t, SnakeToCamel("potato_", false), "potato") 13 | require.Equal(t, SnakeToCamel("Potato_", false), "potato") 14 | require.Equal(t, SnakeToCamel("this_has_to_be_uppercased"), "ThisHasToBeUppercased") 15 | require.Equal(t, SnakeToCamel("this_is_an_id"), "ThisIsAnID") 16 | require.Equal(t, SnakeToCamel("this_is_an_identifier"), "ThisIsAnIdentifier") 17 | require.Equal(t, SnakeToCamel("id"), "ID") 18 | require.Equal(t, SnakeToCamel("oauth_client"), "OAuthClient") 19 | } 20 | 21 | func TestCamelToSnake(t *testing.T) { 22 | require.Equal(t, "", CamelToSnake("")) 23 | require.Equal(t, "_", CamelToSnake("_")) 24 | require.Equal(t, "snake_case", CamelToSnake("snake_case")) 25 | require.Equal(t, "camel_case", CamelToSnake("camelCase")) 26 | require.Equal(t, "jet_is_cool_as_hell", CamelToSnake("jetIsCoolAsHell")) 27 | require.Equal(t, "jet_is_cool_as_hell", CamelToSnake("jet_is_cool_as_hell")) 28 | require.Equal(t, "id", CamelToSnake("ID")) 29 | } 30 | -------------------------------------------------------------------------------- /internal/jet/window_func_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import "testing" 4 | 5 | func TestFrameExtent(t *testing.T) { 6 | assertClauseSerialize(t, PRECEDING(Int(2)), "$1 PRECEDING", int64(2)) 7 | assertClauseSerialize(t, FOLLOWING(Int(4)), "$1 FOLLOWING", int64(4)) 8 | } 9 | 10 | func TestWindowFunctions(t *testing.T) { 11 | assertClauseSerialize(t, PARTITION_BY(table1Col1), "(PARTITION BY table1.col1)") 12 | assertClauseSerialize(t, PARTITION_BY(table1Col3).ORDER_BY(table1Col1), "(PARTITION BY table1.col3 ORDER BY table1.col1)") 13 | assertClauseSerialize(t, ORDER_BY(table1Col1), "(ORDER BY table1.col1)") 14 | assertClauseSerialize(t, ORDER_BY(table1Col1).ROWS(PRECEDING(Int(1))), "(ORDER BY table1.col1 ROWS $1 PRECEDING)", int64(1)) 15 | assertClauseSerialize(t, ORDER_BY(table1Col1).ROWS(PRECEDING(Int(1)), FOLLOWING(Int(33))), 16 | "(ORDER BY table1.col1 ROWS BETWEEN $1 PRECEDING AND $2 FOLLOWING)", int64(1), int64(33)) 17 | assertClauseSerialize(t, ORDER_BY(table1Col1).RANGE(PRECEDING(UNBOUNDED), FOLLOWING(UNBOUNDED)), 18 | "(ORDER BY table1.col1 RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)") 19 | assertClauseSerialize(t, ORDER_BY(table1Col1).RANGE(PRECEDING(UNBOUNDED), CURRENT_ROW), 20 | "(ORDER BY table1.col1 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)") 21 | } 22 | -------------------------------------------------------------------------------- /internal/jet/raw_statement.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | type rawStatementImpl struct { 4 | statementInterfaceImpl 5 | 6 | RawQuery string 7 | NamedArguments map[string]interface{} 8 | } 9 | 10 | // RawStatement creates new sql statements from raw query and optional map of named arguments 11 | func RawStatement(dialect Dialect, rawQuery string, namedArgument ...map[string]interface{}) SerializerStatement { 12 | newRawStatement := rawStatementImpl{ 13 | statementInterfaceImpl: statementInterfaceImpl{ 14 | dialect: dialect, 15 | statementType: "", 16 | root: nil, 17 | }, 18 | RawQuery: rawQuery, 19 | } 20 | 21 | if len(namedArgument) > 0 { 22 | newRawStatement.NamedArguments = namedArgument[0] 23 | } 24 | 25 | newRawStatement.root = &newRawStatement 26 | 27 | return &newRawStatement 28 | } 29 | 30 | func (s *rawStatementImpl) projections() ProjectionList { 31 | return nil 32 | } 33 | 34 | func (s *rawStatementImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { 35 | if !contains(options, NoWrap) { 36 | out.WriteString("(") 37 | out.IncreaseIdent() 38 | } 39 | 40 | out.insertRawQuery(s.RawQuery, s.NamedArguments) 41 | 42 | if !contains(options, NoWrap) { 43 | out.DecreaseIdent() 44 | out.NewLine() 45 | out.WriteString(")") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /postgres/clause_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "testing" 4 | 5 | func TestOnConflict(t *testing.T) { 6 | 7 | assertClauseSerialize(t, &onConflictClause{}, "") 8 | 9 | onConflict := &onConflictClause{} 10 | onConflict.DO_NOTHING() 11 | assertClauseSerialize(t, onConflict, ` 12 | ON CONFLICT DO NOTHING`) 13 | 14 | onConflict = &onConflictClause{indexExpressions: ColumnList{table1ColBool}} 15 | onConflict.DO_NOTHING() 16 | assertClauseSerialize(t, onConflict, ` 17 | ON CONFLICT (col_bool) DO NOTHING`) 18 | 19 | onConflict = &onConflictClause{indexExpressions: ColumnList{table1ColBool}} 20 | onConflict.ON_CONSTRAINT("table_pkey").DO_NOTHING() 21 | assertClauseSerialize(t, onConflict, ` 22 | ON CONFLICT (col_bool) ON CONSTRAINT table_pkey DO NOTHING`) 23 | 24 | onConflict = &onConflictClause{indexExpressions: ColumnList{table1ColBool, table2ColFloat}} 25 | onConflict.WHERE(table2ColFloat.ADD(table1ColInt).GT(table1ColFloat)). 26 | DO_UPDATE( 27 | SET(table1ColBool.SET(Bool(true)), 28 | table1ColInt.SET(Int(11))). 29 | WHERE(table2ColFloat.GT(Float(11.1))), 30 | ) 31 | assertClauseSerialize(t, onConflict, ` 32 | ON CONFLICT (col_bool, col_float) WHERE (col_float + col_int) > col_float DO UPDATE 33 | SET col_bool = $1::boolean, 34 | col_int = $2 35 | WHERE table2.col_float > $3`) 36 | } 37 | -------------------------------------------------------------------------------- /internal/jet/cast.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | // Cast interface 4 | type Cast interface { 5 | AS(castType string) Expression 6 | } 7 | 8 | type castImpl struct { 9 | expression Expression 10 | } 11 | 12 | // NewCastImpl creates new generic cast 13 | func NewCastImpl(expression Expression) Cast { 14 | castImpl := castImpl{ 15 | expression: expression, 16 | } 17 | 18 | return &castImpl 19 | } 20 | 21 | func (b *castImpl) AS(castType string) Expression { 22 | castExp := &castExpression{ 23 | expression: b.expression, 24 | cast: string(castType), 25 | } 26 | 27 | castExp.ExpressionInterfaceImpl.Root = castExp 28 | 29 | return castExp 30 | } 31 | 32 | type castExpression struct { 33 | ExpressionInterfaceImpl 34 | 35 | expression Expression 36 | cast string 37 | } 38 | 39 | func (b *castExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { 40 | 41 | expression := b.expression 42 | castType := b.cast 43 | 44 | if castOverride := out.Dialect.OperatorSerializeOverride("CAST"); castOverride != nil { 45 | castOverride(expression, String(castType))(statement, out, FallTrough(options)...) 46 | return 47 | } 48 | 49 | out.WriteString("CAST(") 50 | expression.serialize(statement, out, FallTrough(options)...) 51 | out.WriteString("AS") 52 | out.WriteString(castType + ")") 53 | } 54 | -------------------------------------------------------------------------------- /generator/template/format_test.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import "testing" 4 | 5 | func Test_formatGolangComment(t *testing.T) { 6 | type args struct { 7 | comment string 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | want string 13 | }{ 14 | {name: "Empty string", args: args{comment: ""}, want: ""}, 15 | {name: "Non-empty string", args: args{comment: "This is a comment"}, want: "// This is a comment"}, 16 | {name: "String with control characters", args: args{comment: "This is a comment with control characters \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f and text after"}, want: "// This is a comment with control characters and text after"}, 17 | {name: "String with escape characters", args: args{comment: "This is a comment with escape characters \n\r\t and text after"}, want: "// This is a comment with escape characters and text after"}, 18 | {name: "String with unicode characters", args: args{comment: "This is a comment with unicode characters ₲鬼佬℧⇄↻ and text after"}, want: "// This is a comment with unicode characters ₲鬼佬℧⇄↻ and text after"}, 19 | } 20 | for _, tt := range tests { 21 | t.Run(tt.name, func(t *testing.T) { 22 | if got := formatGolangComment(tt.args.comment); got != tt.want { 23 | t.Errorf("formatGoLangComment() = %v, want %v", got, tt.want) 24 | } 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /generator/sqlite/sqlite_generator.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | 7 | "github.com/go-jet/jet/v2/generator/metadata" 8 | "github.com/go-jet/jet/v2/generator/template" 9 | "github.com/go-jet/jet/v2/sqlite" 10 | ) 11 | 12 | // GenerateDSN generates jet files using dsn connection string 13 | func GenerateDSN(dsn, destDir string, templates ...template.Template) error { 14 | db, err := sql.Open("sqlite3", dsn) 15 | if err != nil { 16 | return fmt.Errorf("failed to open sqlite connection: %w", err) 17 | } 18 | defer db.Close() 19 | 20 | fmt.Println("Retrieving schema information...") 21 | return GenerateDB(db, destDir, templates...) 22 | } 23 | 24 | // GenerateDB generates jet files using the provided *sql.DB 25 | func GenerateDB(db *sql.DB, destDir string, templates ...template.Template) error { 26 | generatorTemplate := template.Default(sqlite.Dialect) 27 | if len(templates) > 0 { 28 | generatorTemplate = templates[0] 29 | } 30 | 31 | schemaMetadata, err := metadata.GetSchema(db, &sqliteQuerySet{}, "") 32 | if err != nil { 33 | return fmt.Errorf("failed to query database metadata: %w", err) 34 | } 35 | 36 | err = template.ProcessSchema(destDir, schemaMetadata, generatorTemplate) 37 | if err != nil { 38 | return fmt.Errorf("failed to process database %s: %w", schemaMetadata.Name, err) 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /internal/jet/operators_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import "testing" 4 | 5 | func TestOperatorNOT(t *testing.T) { 6 | notExpression := NOT(Int(2).EQ(Int(1))) 7 | 8 | assertClauseSerialize(t, NOT(table1ColBool), "(NOT table1.col_bool)") 9 | assertClauseSerialize(t, notExpression, "(NOT ($1 = $2))", int64(2), int64(1)) 10 | assertProjectionSerialize(t, notExpression.AS("alias_not_expression"), `(NOT ($1 = $2)) AS "alias_not_expression"`, int64(2), int64(1)) 11 | assertClauseSerialize(t, notExpression.AND(Int(4).EQ(Int(5))), `((NOT ($1 = $2)) AND ($3 = $4))`, int64(2), int64(1), int64(4), int64(5)) 12 | } 13 | 14 | func TestCase1(t *testing.T) { 15 | query := CASE(). 16 | WHEN(table3Col1.EQ(Int(1))).THEN(table3Col1.ADD(Int(1))). 17 | WHEN(table3Col1.EQ(Int(2))).THEN(table3Col1.ADD(Int(2))) 18 | 19 | assertClauseSerialize(t, query, `(CASE WHEN table3.col1 = $1 THEN table3.col1 + $2 WHEN table3.col1 = $3 THEN table3.col1 + $4 END)`, 20 | int64(1), int64(1), int64(2), int64(2)) 21 | } 22 | 23 | func TestCase2(t *testing.T) { 24 | query := CASE(table3Col1). 25 | WHEN(Int(1)).THEN(table3Col1.ADD(Int(1))). 26 | WHEN(Int(2)).THEN(table3Col1.ADD(Int(2))). 27 | ELSE(Int(0)) 28 | 29 | assertClauseSerialize(t, query, `(CASE table3.col1 WHEN $1 THEN table3.col1 + $2 WHEN $3 THEN table3.col1 + $4 ELSE $5 END)`, 30 | int64(1), int64(1), int64(2), int64(2), int64(0)) 31 | } 32 | -------------------------------------------------------------------------------- /internal/utils/semantic/version.go: -------------------------------------------------------------------------------- 1 | package semantic 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Version struct holds semantic versioning information 10 | type Version struct { 11 | Major int 12 | Minor int 13 | Patch int 14 | } 15 | 16 | // VersionFromString creates new semantic Version by parsing version string 17 | func VersionFromString(version string) (Version, error) { 18 | parts := strings.Split(version, ".") 19 | 20 | var ret Version 21 | 22 | if len(parts) > 0 { 23 | major, err := strconv.Atoi(parts[0]) 24 | 25 | if err != nil { 26 | return ret, fmt.Errorf("major is not a number: %w", err) 27 | } 28 | 29 | ret.Major = major 30 | } 31 | 32 | if len(parts) > 1 { 33 | minor, err := strconv.Atoi(parts[1]) 34 | 35 | if err != nil { 36 | return ret, fmt.Errorf("minor is not a number: %w", err) 37 | } 38 | 39 | ret.Minor = minor 40 | } 41 | 42 | if len(parts) > 2 { 43 | patch, err := strconv.Atoi(parts[2]) 44 | 45 | if err != nil { 46 | return ret, fmt.Errorf("patch is not a number: %w", err) 47 | } 48 | 49 | ret.Patch = patch 50 | } 51 | 52 | return ret, nil 53 | } 54 | 55 | // Lt returns true if this version is less than version parameter 56 | func (v Version) Lt(version Version) bool { 57 | if v.Major < version.Major { 58 | return true 59 | } 60 | 61 | if v.Minor < version.Minor { 62 | return true 63 | } 64 | 65 | return v.Patch < version.Patch 66 | } 67 | -------------------------------------------------------------------------------- /mysql/types.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // Statement is common interface for all statements(SELECT, INSERT, UPDATE, DELETE, LOCK) 6 | type Statement = jet.Statement 7 | 8 | // Rows wraps sql.Rows type with a support for query result mapping 9 | type Rows = jet.Rows 10 | 11 | // Projection is interface for all projection types. Types that can be part of, for instance SELECT clause. 12 | type Projection = jet.Projection 13 | 14 | // ProjectionList can be used to create conditional constructed projection list. 15 | type ProjectionList = jet.ProjectionList 16 | 17 | // ColumnAssigment is interface wrapper around column assigment 18 | type ColumnAssigment = jet.ColumnAssigment 19 | 20 | // PrintableStatement is a statement which sql query can be logged 21 | type PrintableStatement = jet.PrintableStatement 22 | 23 | // OrderByClause is the combination of an expression and the wanted ordering to use as input for ORDER BY. 24 | type OrderByClause = jet.OrderByClause 25 | 26 | // GroupByClause interface to use as input for GROUP_BY 27 | type GroupByClause = jet.GroupByClause 28 | 29 | // SetLogger sets automatic statement logging 30 | // Deprecated: use SetQueryLogger instead. 31 | var SetLogger = jet.SetLoggerFunc 32 | 33 | // SetQueryLogger sets automatic query logging function. 34 | var SetQueryLogger = jet.SetQueryLogger 35 | 36 | // QueryInfo contains information about executed query 37 | type QueryInfo = jet.QueryInfo 38 | -------------------------------------------------------------------------------- /sqlite/types.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // Statement is common interface for all statements(SELECT, INSERT, UPDATE, DELETE, LOCK) 6 | type Statement = jet.Statement 7 | 8 | // Rows wraps sql.Rows type with a support for query result mapping 9 | type Rows = jet.Rows 10 | 11 | // Projection is interface for all projection types. Types that can be part of, for instance SELECT clause. 12 | type Projection = jet.Projection 13 | 14 | // ProjectionList can be used to create conditional constructed projection list. 15 | type ProjectionList = jet.ProjectionList 16 | 17 | // ColumnAssigment is interface wrapper around column assigment 18 | type ColumnAssigment = jet.ColumnAssigment 19 | 20 | // PrintableStatement is a statement which sql query can be logged 21 | type PrintableStatement = jet.PrintableStatement 22 | 23 | // OrderByClause is the combination of an expression and the wanted ordering to use as input for ORDER BY. 24 | type OrderByClause = jet.OrderByClause 25 | 26 | // GroupByClause interface to use as input for GROUP_BY 27 | type GroupByClause = jet.GroupByClause 28 | 29 | // SetLogger sets automatic statement logging. 30 | // Deprecated: use SetQueryLogger instead. 31 | var SetLogger = jet.SetLoggerFunc 32 | 33 | // SetQueryLogger sets automatic query logging function. 34 | var SetQueryLogger = jet.SetQueryLogger 35 | 36 | // QueryInfo contains information about executed query 37 | type QueryInfo = jet.QueryInfo 38 | -------------------------------------------------------------------------------- /postgres/types.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // Statement is common interface for all statements(SELECT, INSERT, UPDATE, DELETE, LOCK) 6 | type Statement = jet.Statement 7 | 8 | // Rows wraps sql.Rows type with a support for query result mapping 9 | type Rows = jet.Rows 10 | 11 | // Projection is interface for all projection types. Types that can be part of, for instance SELECT clause. 12 | type Projection = jet.Projection 13 | 14 | // ProjectionList can be used to create conditional constructed projection list. 15 | type ProjectionList = jet.ProjectionList 16 | 17 | // ColumnAssigment is interface wrapper around column assigment 18 | type ColumnAssigment = jet.ColumnAssigment 19 | 20 | // PrintableStatement is a statement which sql query can be logged 21 | type PrintableStatement = jet.PrintableStatement 22 | 23 | // OrderByClause is the combination of an expression and the wanted ordering to use as input for ORDER BY. 24 | type OrderByClause = jet.OrderByClause 25 | 26 | // GroupByClause interface to use as input for GROUP_BY 27 | type GroupByClause = jet.GroupByClause 28 | 29 | // SetLogger sets automatic statement logging function 30 | // Deprecated: use SetQueryLogger instead. 31 | var SetLogger = jet.SetLoggerFunc 32 | 33 | // SetQueryLogger sets automatic query logging function. 34 | var SetQueryLogger = jet.SetQueryLogger 35 | 36 | // QueryInfo contains information about executed query 37 | type QueryInfo = jet.QueryInfo 38 | -------------------------------------------------------------------------------- /tests/mysql/lock_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/testutils" 5 | . "github.com/go-jet/jet/v2/mysql" 6 | . "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table" 7 | "github.com/stretchr/testify/require" 8 | "testing" 9 | ) 10 | 11 | func TestLockRead(t *testing.T) { 12 | query := Customer.LOCK().READ() 13 | 14 | testutils.AssertStatementSql(t, query, ` 15 | LOCK TABLES dvds.customer READ; 16 | `) 17 | tx, err := db.DB.Begin() // can't prepare LOCK statement 18 | require.NoError(t, err) 19 | defer func() { 20 | err := tx.Rollback() 21 | require.NoError(t, err) 22 | }() 23 | 24 | testutils.AssertExec(t, query, tx) 25 | } 26 | 27 | func TestLockWrite(t *testing.T) { 28 | query := Customer.LOCK().WRITE() 29 | 30 | testutils.AssertStatementSql(t, query, ` 31 | LOCK TABLES dvds.customer WRITE; 32 | `) 33 | 34 | tx, err := db.DB.Begin() // can't prepare LOCK statement 35 | require.NoError(t, err) 36 | defer func() { 37 | err := tx.Rollback() 38 | require.NoError(t, err) 39 | }() 40 | 41 | testutils.AssertExec(t, query, tx) 42 | } 43 | 44 | func TestUnlockTables(t *testing.T) { 45 | query := UNLOCK_TABLES() 46 | 47 | testutils.AssertStatementSql(t, query, ` 48 | UNLOCK TABLES; 49 | `) 50 | 51 | tx, err := db.DB.Begin() // can't prepare LOCK statement 52 | require.NoError(t, err) 53 | defer func() { 54 | err := tx.Rollback() 55 | require.NoError(t, err) 56 | }() 57 | 58 | testutils.AssertExec(t, query, tx) 59 | } 60 | -------------------------------------------------------------------------------- /examples/quick-start/.gen/jetdb/dvds/model/mpaa_rating.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package model 9 | 10 | import "errors" 11 | 12 | type MpaaRating string 13 | 14 | const ( 15 | MpaaRating_G MpaaRating = "G" 16 | MpaaRating_Pg MpaaRating = "PG" 17 | MpaaRating_Pg13 MpaaRating = "PG-13" 18 | MpaaRating_R MpaaRating = "R" 19 | MpaaRating_Nc17 MpaaRating = "NC-17" 20 | ) 21 | 22 | var MpaaRatingAllValues = []MpaaRating{ 23 | MpaaRating_G, 24 | MpaaRating_Pg, 25 | MpaaRating_Pg13, 26 | MpaaRating_R, 27 | MpaaRating_Nc17, 28 | } 29 | 30 | func (e *MpaaRating) Scan(value interface{}) error { 31 | var enumValue string 32 | switch val := value.(type) { 33 | case string: 34 | enumValue = val 35 | case []byte: 36 | enumValue = string(val) 37 | default: 38 | return errors.New("jet: Invalid scan value for AllTypesEnum enum. Enum value has to be of type string or []byte") 39 | } 40 | 41 | switch enumValue { 42 | case "G": 43 | *e = MpaaRating_G 44 | case "PG": 45 | *e = MpaaRating_Pg 46 | case "PG-13": 47 | *e = MpaaRating_Pg13 48 | case "R": 49 | *e = MpaaRating_R 50 | case "NC-17": 51 | *e = MpaaRating_Nc17 52 | default: 53 | return errors.New("jet: Invalid scan value '" + enumValue + "' for MpaaRating enum") 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func (e MpaaRating) String() string { 60 | return string(e) 61 | } 62 | -------------------------------------------------------------------------------- /postgres/delete_statement.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // DeleteStatement is interface for PostgreSQL DELETE statement 6 | type DeleteStatement interface { 7 | jet.SerializerStatement 8 | 9 | USING(tables ...ReadableTable) DeleteStatement 10 | WHERE(expression BoolExpression) DeleteStatement 11 | RETURNING(projections ...jet.Projection) DeleteStatement 12 | } 13 | 14 | type deleteStatementImpl struct { 15 | jet.SerializerStatement 16 | 17 | Delete jet.ClauseDelete 18 | Using jet.ClauseFrom 19 | Where jet.ClauseWhere 20 | Returning jet.ClauseReturning 21 | } 22 | 23 | func newDeleteStatement(table WritableTable) DeleteStatement { 24 | newDelete := &deleteStatementImpl{} 25 | newDelete.SerializerStatement = jet.NewStatementImpl(Dialect, jet.DeleteStatementType, newDelete, 26 | &newDelete.Delete, 27 | &newDelete.Using, 28 | &newDelete.Where, 29 | &newDelete.Returning) 30 | 31 | newDelete.Delete.Table = table 32 | newDelete.Using.Name = "USING" 33 | newDelete.Where.Mandatory = true 34 | 35 | return newDelete 36 | } 37 | 38 | func (d *deleteStatementImpl) USING(tables ...ReadableTable) DeleteStatement { 39 | d.Using.Tables = readableTablesToSerializerList(tables) 40 | return d 41 | } 42 | 43 | func (d *deleteStatementImpl) WHERE(expression BoolExpression) DeleteStatement { 44 | d.Where.Condition = expression 45 | return d 46 | } 47 | 48 | func (d *deleteStatementImpl) RETURNING(projections ...jet.Projection) DeleteStatement { 49 | d.Returning.ProjectionList = projections 50 | return d 51 | } 52 | -------------------------------------------------------------------------------- /tests/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | image: postgres:14.1 4 | restart: always 5 | environment: 6 | - POSTGRES_USER=jet 7 | - POSTGRES_PASSWORD=jet 8 | - POSTGRES_DB=jetdb 9 | ports: 10 | - '50901:5432' 11 | volumes: 12 | - ./testdata/init/postgres:/docker-entrypoint-initdb.d 13 | 14 | mysql: 15 | image: mysql/mysql-server:8.0.27 16 | command: ['--default-authentication-plugin=mysql_native_password', '--log_bin_trust_function_creators=1'] 17 | restart: always 18 | environment: 19 | MYSQL_ROOT_PASSWORD: jet 20 | MYSQL_USER: jet 21 | MYSQL_PASSWORD: jet 22 | ports: 23 | - '50902:3306' 24 | volumes: 25 | - ./testdata/init/mysql:/docker-entrypoint-initdb.d 26 | 27 | mariadb: 28 | image: mariadb:11.4 29 | command: ['--default-authentication-plugin=mysql_native_password', '--log_bin_trust_function_creators=1'] 30 | restart: always 31 | environment: 32 | MYSQL_ROOT_PASSWORD: jet 33 | MYSQL_USER: jet 34 | MYSQL_PASSWORD: jet 35 | ports: 36 | - '50903:3306' 37 | volumes: 38 | - ./testdata/init/mysql:/docker-entrypoint-initdb.d 39 | 40 | cockroach: 41 | image: cockroachdb/cockroach-unstable:v23.1.0-rc.2 42 | environment: 43 | - COCKROACH_USER=jet 44 | - COCKROACH_PASSWORD=jet 45 | - COCKROACH_DATABASE=jetdb 46 | ports: 47 | - "26257:26257" 48 | command: start-single-node --accept-sql-without-tls 49 | # volumes: 50 | # - ./testdata/init/cockroach:/docker-entrypoint-initdb.d 51 | 52 | -------------------------------------------------------------------------------- /internal/jet/select_lock.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | // RowLock is interface for SELECT statement row lock types 4 | type RowLock interface { 5 | Serializer 6 | 7 | OF(...Table) RowLock 8 | NOWAIT() RowLock 9 | SKIP_LOCKED() RowLock 10 | } 11 | 12 | type selectLockImpl struct { 13 | lockStrength string 14 | of []Table 15 | noWait, skipLocked bool 16 | } 17 | 18 | // NewRowLock creates new RowLock 19 | func NewRowLock(name string) func() RowLock { 20 | return func() RowLock { 21 | return newSelectLock(name) 22 | } 23 | } 24 | 25 | func newSelectLock(lockStrength string) *selectLockImpl { 26 | return &selectLockImpl{lockStrength: lockStrength} 27 | } 28 | 29 | func (s *selectLockImpl) OF(tables ...Table) RowLock { 30 | s.of = tables 31 | return s 32 | } 33 | 34 | func (s *selectLockImpl) NOWAIT() RowLock { 35 | s.noWait = true 36 | return s 37 | } 38 | 39 | func (s *selectLockImpl) SKIP_LOCKED() RowLock { 40 | s.skipLocked = true 41 | return s 42 | } 43 | 44 | func (s *selectLockImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { 45 | out.WriteString(s.lockStrength) 46 | 47 | if len(s.of) > 0 { 48 | out.WriteString("OF") 49 | 50 | for i, of := range s.of { 51 | if i > 0 { 52 | out.WriteString(", ") 53 | } 54 | 55 | table := of.Alias() 56 | if table == "" { 57 | table = of.TableName() 58 | } 59 | 60 | out.WriteIdentifier(table) 61 | } 62 | } 63 | 64 | if s.noWait { 65 | out.WriteString("NOWAIT") 66 | } 67 | 68 | if s.skipLocked { 69 | out.WriteString("SKIP LOCKED") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/testutils/time_utils.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/utils/throw" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | // Date creates time from t string 10 | func Date(t string) *time.Time { 11 | newTime, err := time.Parse("2006-01-02", t) 12 | 13 | throw.OnError(err) 14 | 15 | return &newTime 16 | } 17 | 18 | // TimestampWithoutTimeZone creates time from t 19 | func TimestampWithoutTimeZone(t string, precision int) *time.Time { 20 | 21 | precisionStr := "" 22 | 23 | if precision > 0 { 24 | precisionStr = "." + strings.Repeat("9", precision) 25 | } 26 | 27 | newTime, err := time.Parse("2006-01-02 15:04:05"+precisionStr+" +0000", t+" +0000") 28 | 29 | throw.OnError(err) 30 | 31 | return &newTime 32 | } 33 | 34 | // TimeWithoutTimeZone creates time from t 35 | func TimeWithoutTimeZone(t string) *time.Time { 36 | newTime, err := time.Parse("15:04:05", t) 37 | 38 | throw.OnError(err) 39 | 40 | return &newTime 41 | } 42 | 43 | // TimeWithTimeZone creates time from t 44 | func TimeWithTimeZone(t string) *time.Time { 45 | newTimez, err := time.Parse("15:04:05 -0700", t) 46 | 47 | throw.OnError(err) 48 | 49 | return &newTimez 50 | } 51 | 52 | // TimestampWithTimeZone creates time from t 53 | func TimestampWithTimeZone(t string, precision int) *time.Time { 54 | 55 | precisionStr := "" 56 | 57 | if precision > 0 { 58 | precisionStr = "." + strings.Repeat("9", precision) 59 | } 60 | 61 | newTime, err := time.Parse("2006-01-02 15:04:05"+precisionStr+" -0700 MST", t) 62 | 63 | throw.OnError(err) 64 | 65 | return &newTime 66 | } 67 | -------------------------------------------------------------------------------- /postgres/array_columns.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // Interfaces for different postgres array column types 6 | type ( 7 | ColumnBoolArray jet.ColumnArray[BoolExpression] 8 | ColumnStringArray jet.ColumnArray[StringExpression] 9 | ColumnIntegerArray jet.ColumnArray[IntegerExpression] 10 | ColumnFloatArray jet.ColumnArray[FloatExpression] 11 | ColumnByteaArray jet.ColumnArray[ByteaExpression] 12 | ColumnDateArray jet.ColumnArray[DateExpression] 13 | ColumnTimestampArray jet.ColumnArray[TimestampExpression] 14 | ColumnTimestampzArray jet.ColumnArray[TimestampzExpression] 15 | ColumnTimeArray jet.ColumnArray[TimeExpression] 16 | ColumnTimezArray jet.ColumnArray[TimezExpression] 17 | ColumnIntervalArray jet.ColumnArray[IntervalExpression] 18 | ) 19 | 20 | // Column constructors for different postgres array column types 21 | var ( 22 | BoolArrayColumn = jet.ArrayColumn[BoolExpression] 23 | StringArrayColumn = jet.ArrayColumn[StringExpression] 24 | IntegerArrayColumn = jet.ArrayColumn[IntegerExpression] 25 | FloatArrayColumn = jet.ArrayColumn[FloatExpression] 26 | ByteaArrayColumn = jet.ArrayColumn[ByteaExpression] 27 | DateArrayColumn = jet.ArrayColumn[DateExpression] 28 | TimestampArrayColumn = jet.ArrayColumn[TimestampExpression] 29 | TimestampzArrayColumn = jet.ArrayColumn[TimestampzExpression] 30 | TimeArrayColumn = jet.ArrayColumn[TimeExpression] 31 | TimezArrayColumn = jet.ArrayColumn[TimezExpression] 32 | IntervalArrayColumn = jet.ArrayColumn[IntervalExpression] 33 | ) 34 | -------------------------------------------------------------------------------- /postgres/update_statement_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestUpdateWithOneValue(t *testing.T) { 9 | expectedSQL := ` 10 | UPDATE db.table1 11 | SET col_int = $1 12 | WHERE table1.col_int >= $2; 13 | ` 14 | stmt := table1.UPDATE(table1ColInt). 15 | SET(1). 16 | WHERE(table1ColInt.GT_EQ(Int(33))) 17 | 18 | fmt.Println(stmt.Sql()) 19 | 20 | assertStatementSql(t, stmt, expectedSQL, 1, int64(33)) 21 | } 22 | 23 | func TestUpdateWithValues(t *testing.T) { 24 | expectedSQL := ` 25 | UPDATE db.table1 26 | SET (col_int, col_float) = ($1, $2) 27 | WHERE table1.col_int >= $3; 28 | ` 29 | stmt := table1.UPDATE(table1ColInt, table1ColFloat). 30 | SET(1, 22.2). 31 | WHERE(table1ColInt.GT_EQ(Int(33))) 32 | 33 | fmt.Println(stmt.Sql()) 34 | 35 | assertStatementSql(t, stmt, expectedSQL, 1, 22.2, int64(33)) 36 | } 37 | 38 | func TestUpdateOneColumnWithSelect(t *testing.T) { 39 | expectedSQL := ` 40 | UPDATE db.table1 41 | SET col_float = ( 42 | SELECT table1.col_float AS "table1.col_float" 43 | FROM db.table1 44 | ) 45 | WHERE table1.col1 = $1 46 | RETURNING table1.col1 AS "table1.col1"; 47 | ` 48 | stmt := table1. 49 | UPDATE(table1ColFloat). 50 | SET( 51 | table1.SELECT(table1ColFloat), 52 | ). 53 | WHERE(table1Col1.EQ(Int(2))). 54 | RETURNING(table1Col1) 55 | 56 | assertStatementSql(t, stmt, expectedSQL, int64(2)) 57 | } 58 | 59 | func TestInvalidInputs(t *testing.T) { 60 | assertStatementSqlErr(t, table1.UPDATE(table1ColInt).SET(1), "jet: WHERE clause not set") 61 | assertStatementSqlErr(t, table1.UPDATE(nil).SET(1), "jet: nil column in columns list") 62 | } 63 | -------------------------------------------------------------------------------- /internal/jet/expression_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestExpressionIS_NULL(t *testing.T) { 8 | assertClauseSerialize(t, table2Col3.IS_NULL(), "table2.col3 IS NULL") 9 | assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NULL(), "(table2.col3 + table2.col3) IS NULL") 10 | } 11 | 12 | func TestExpressionIS_NOT_NULL(t *testing.T) { 13 | assertClauseSerialize(t, table2Col3.IS_NOT_NULL(), "table2.col3 IS NOT NULL") 14 | assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NOT_NULL(), "(table2.col3 + table2.col3) IS NOT NULL") 15 | } 16 | 17 | func TestExpressionIS_DISTINCT_FROM(t *testing.T) { 18 | assertClauseSerialize(t, table2Col3.IS_DISTINCT_FROM(table2Col4), "(table2.col3 IS DISTINCT FROM table2.col4)") 19 | assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_DISTINCT_FROM(Int(23)), "((table2.col3 + table2.col3) IS DISTINCT FROM $1)", int64(23)) 20 | } 21 | 22 | func TestExpressionIS_NOT_DISTINCT_FROM(t *testing.T) { 23 | assertClauseSerialize(t, table2Col3.IS_NOT_DISTINCT_FROM(table2Col4), "(table2.col3 IS NOT DISTINCT FROM table2.col4)") 24 | assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NOT_DISTINCT_FROM(Int(23)), "((table2.col3 + table2.col3) IS NOT DISTINCT FROM $1)", int64(23)) 25 | } 26 | 27 | func TestIN(t *testing.T) { 28 | assertClauseSerialize(t, table2ColInt.IN(Int(1), Int(2), Int(3)), 29 | `(table2.col_int IN ($1, $2, $3))`, int64(1), int64(2), int64(3)) 30 | 31 | } 32 | 33 | func TestNOT_IN(t *testing.T) { 34 | 35 | assertClauseSerialize(t, table2ColInt.NOT_IN(Int(1), Int(2), Int(3)), 36 | `(table2.col_int NOT IN ($1, $2, $3))`, int64(1), int64(2), int64(3)) 37 | 38 | } 39 | -------------------------------------------------------------------------------- /mysql/lock_statement.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // LockStatement is interface for MySQL LOCK tables 6 | type LockStatement interface { 7 | Statement 8 | READ() Statement 9 | WRITE() Statement 10 | } 11 | 12 | // LOCK creates LockStatement from list of tables 13 | func LOCK(tables ...jet.SerializerTable) LockStatement { 14 | newLock := &lockStatementImpl{ 15 | Lock: jet.ClauseStatementBegin{Name: "LOCK TABLES", Tables: tables}, 16 | Read: jet.ClauseOptional{Name: "READ"}, 17 | Write: jet.ClauseOptional{Name: "WRITE"}, 18 | } 19 | 20 | newLock.SerializerStatement = jet.NewStatementImpl(Dialect, jet.LockStatementType, newLock, &newLock.Lock, &newLock.Read, &newLock.Write) 21 | 22 | return newLock 23 | } 24 | 25 | type lockStatementImpl struct { 26 | jet.SerializerStatement 27 | 28 | Lock jet.ClauseStatementBegin 29 | Read jet.ClauseOptional 30 | Write jet.ClauseOptional 31 | } 32 | 33 | func (l *lockStatementImpl) READ() Statement { 34 | l.Read.Show = true 35 | return l 36 | } 37 | 38 | func (l *lockStatementImpl) WRITE() Statement { 39 | l.Write.Show = true 40 | return l 41 | } 42 | 43 | // UNLOCK_TABLES explicitly releases any table locks held by the current session 44 | func UNLOCK_TABLES() Statement { 45 | newUnlock := &unlockStatementImpl{ 46 | Unlock: jet.ClauseStatementBegin{Name: "UNLOCK TABLES"}, 47 | } 48 | 49 | newUnlock.SerializerStatement = jet.NewStatementImpl(Dialect, jet.UnLockStatementType, newUnlock, &newUnlock.Unlock) 50 | 51 | return newUnlock 52 | } 53 | 54 | type unlockStatementImpl struct { 55 | jet.SerializerStatement 56 | Unlock jet.ClauseStatementBegin 57 | } 58 | -------------------------------------------------------------------------------- /internal/utils/filesys/filesys.go: -------------------------------------------------------------------------------- 1 | package filesys 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "go/format" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | // FormatAndSaveGoFile saves go file at folder dir, with name fileName and contents text. 13 | func FormatAndSaveGoFile(dirPath, fileName string, text []byte) error { 14 | newGoFilePath := filepath.Join(dirPath, fileName) 15 | 16 | if !strings.HasSuffix(newGoFilePath, ".go") { 17 | newGoFilePath += ".go" 18 | } 19 | 20 | file, err := os.Create(newGoFilePath) // #nosec 304 21 | 22 | if err != nil { 23 | return err 24 | } 25 | 26 | defer file.Close() 27 | 28 | p, err := format.Source(text) 29 | 30 | // if there is a format error we will write unformulated text for debug purposes 31 | if err != nil { 32 | _, writeErr := file.Write(text) 33 | if writeErr != nil { 34 | return errors.Join(writeErr, fmt.Errorf("failed to format '%s', check '%s' for syntax errors: %w", fileName, newGoFilePath, err)) 35 | } 36 | return fmt.Errorf("failed to format '%s', check '%s' for syntax errors: %w", fileName, newGoFilePath, err) 37 | } 38 | 39 | _, err = file.Write(p) 40 | if err != nil { 41 | return fmt.Errorf("failed to save '%s' file: %w", newGoFilePath, err) 42 | } 43 | 44 | return nil 45 | } 46 | 47 | // EnsureDirPathExist ensures dir path exists. If path does not exist, creates new path. 48 | func EnsureDirPathExist(dirPath string) error { 49 | if _, err := os.Stat(dirPath); os.IsNotExist(err) { 50 | err := os.MkdirAll(dirPath, 0o750) 51 | 52 | if err != nil { 53 | return fmt.Errorf("can't create directory - %s: %w", dirPath, err) 54 | } 55 | } 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /tests/postgres/lock_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "context" 5 | "github.com/go-jet/jet/v2/internal/testutils" 6 | "github.com/stretchr/testify/require" 7 | "testing" 8 | "time" 9 | 10 | . "github.com/go-jet/jet/v2/postgres" 11 | . "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/table" 12 | ) 13 | 14 | func TestLockTable(t *testing.T) { 15 | skipForCockroachDB(t) // doesn't support 16 | 17 | expectedSQL := ` 18 | LOCK TABLE dvds.address IN` 19 | 20 | var testData = []TableLockMode{ 21 | LOCK_ACCESS_SHARE, 22 | LOCK_ROW_SHARE, 23 | LOCK_ROW_EXCLUSIVE, 24 | LOCK_SHARE_UPDATE_EXCLUSIVE, 25 | LOCK_SHARE, 26 | LOCK_SHARE_ROW_EXCLUSIVE, 27 | LOCK_EXCLUSIVE, 28 | LOCK_ACCESS_EXCLUSIVE, 29 | } 30 | 31 | for _, lockMode := range testData { 32 | query := Address.LOCK().IN(lockMode) 33 | 34 | testutils.AssertDebugStatementSql(t, query, expectedSQL+" "+string(lockMode)+" MODE;\n") 35 | testutils.AssertExecAndRollback(t, query, db) 36 | } 37 | 38 | for _, lockMode := range testData { 39 | query := Address.LOCK().IN(lockMode).NOWAIT() 40 | 41 | testutils.AssertDebugStatementSql(t, query, expectedSQL+" "+string(lockMode)+" MODE NOWAIT;\n") 42 | testutils.AssertExecAndRollback(t, query, db) 43 | } 44 | } 45 | 46 | func TestLockExecContext(t *testing.T) { 47 | skipForCockroachDB(t) 48 | 49 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Microsecond) 50 | defer cancel() 51 | 52 | time.Sleep(10 * time.Millisecond) 53 | 54 | tx, _ := db.Begin() 55 | defer tx.Rollback() 56 | 57 | _, err := Address.LOCK().IN(LOCK_ACCESS_SHARE).ExecContext(ctx, tx) 58 | 59 | require.Error(t, err, "context deadline exceeded") 60 | } 61 | -------------------------------------------------------------------------------- /generator/metadata/dialect_query_set.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | ) 7 | 8 | // TableType is type of database table(view or base) 9 | type TableType string 10 | 11 | // SQL table types 12 | const ( 13 | BaseTable TableType = "BASE TABLE" 14 | ViewTable TableType = "VIEW" 15 | ) 16 | 17 | // DialectQuerySet is set of methods necessary to retrieve dialect metadata information 18 | type DialectQuerySet interface { 19 | GetTablesMetaData(db *sql.DB, schemaName string, tableType TableType) ([]Table, error) 20 | GetEnumsMetaData(db *sql.DB, schemaName string) ([]Enum, error) 21 | } 22 | 23 | // GetSchema retrieves Schema information from database 24 | func GetSchema(db *sql.DB, querySet DialectQuerySet, schemaName string) (Schema, error) { 25 | tablesMetaData, err := querySet.GetTablesMetaData(db, schemaName, BaseTable) 26 | if err != nil { 27 | return Schema{}, fmt.Errorf("failed to get %s tables metadata: %w", schemaName, err) 28 | } 29 | 30 | viewMetaData, err := querySet.GetTablesMetaData(db, schemaName, ViewTable) 31 | if err != nil { 32 | return Schema{}, fmt.Errorf("failed to get %s view metadata: %w", schemaName, err) 33 | } 34 | 35 | enumsMetaData, err := querySet.GetEnumsMetaData(db, schemaName) 36 | if err != nil { 37 | return Schema{}, fmt.Errorf("failed to get %s enum metadata: %w", schemaName, err) 38 | } 39 | 40 | ret := Schema{ 41 | Name: schemaName, 42 | TablesMetaData: tablesMetaData, 43 | ViewsMetaData: viewMetaData, 44 | EnumsMetaData: enumsMetaData, 45 | } 46 | 47 | fmt.Println(" FOUND", len(ret.TablesMetaData), "table(s),", len(ret.ViewsMetaData), "view(s),", 48 | len(ret.EnumsMetaData), "enum(s)") 49 | 50 | return ret, nil 51 | } 52 | -------------------------------------------------------------------------------- /internal/utils/datetime/duration.go: -------------------------------------------------------------------------------- 1 | package datetime 2 | 3 | import ( 4 | //"github.com/go-jet/jet/v2/internal/utils/min" 5 | "time" 6 | ) 7 | 8 | // ExtractTimeComponents extracts number of days, hours, minutes, seconds, microseconds from duration 9 | func ExtractTimeComponents(duration time.Duration) (days, hours, minutes, seconds, microseconds int64) { 10 | days = int64(duration / (24 * time.Hour)) 11 | reminder := duration % (24 * time.Hour) 12 | 13 | hours = int64(reminder / time.Hour) 14 | reminder = reminder % time.Hour 15 | 16 | minutes = int64(reminder / time.Minute) 17 | reminder = reminder % time.Minute 18 | 19 | seconds = int64(reminder / time.Second) 20 | reminder = reminder % time.Second 21 | 22 | microseconds = int64(reminder / time.Microsecond) 23 | 24 | return 25 | } 26 | 27 | // TryParseAsTime attempts to parse the provided value as a time using one of the given formats. 28 | // 29 | // The function iterates over the provided formats and tries to parse the value into a time.Time object. 30 | // It returns the parsed time and a boolean indicating whether the parsing was successful. 31 | func TryParseAsTime(value interface{}, formats []string) (time.Time, bool) { 32 | 33 | var timeStr string 34 | 35 | switch v := value.(type) { 36 | case string: 37 | timeStr = v 38 | case []byte: 39 | timeStr = string(v) 40 | case int64: 41 | return time.Unix(v, 0), true // sqlite 42 | default: 43 | return time.Time{}, false 44 | } 45 | 46 | for _, format := range formats { 47 | formatLen := min(len(format), len(timeStr)) 48 | t, err := time.Parse(format[:formatLen], timeStr) 49 | 50 | if err != nil { 51 | continue 52 | } 53 | 54 | return t, true 55 | } 56 | 57 | return time.Time{}, false 58 | } 59 | -------------------------------------------------------------------------------- /sqlite/delete_statement.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // DeleteStatement is interface for MySQL DELETE statement 6 | type DeleteStatement interface { 7 | Statement 8 | 9 | WHERE(expression BoolExpression) DeleteStatement 10 | ORDER_BY(orderByClauses ...OrderByClause) DeleteStatement 11 | LIMIT(limit int64) DeleteStatement 12 | RETURNING(projections ...Projection) DeleteStatement 13 | } 14 | 15 | type deleteStatementImpl struct { 16 | jet.SerializerStatement 17 | 18 | Delete jet.ClauseDelete 19 | Where jet.ClauseWhere 20 | OrderBy jet.ClauseOrderBy 21 | Limit jet.ClauseLimit 22 | Returning jet.ClauseReturning 23 | } 24 | 25 | func newDeleteStatement(table Table) DeleteStatement { 26 | newDelete := &deleteStatementImpl{} 27 | newDelete.SerializerStatement = jet.NewStatementImpl(Dialect, jet.DeleteStatementType, newDelete, 28 | &newDelete.Delete, 29 | &newDelete.Where, 30 | &newDelete.OrderBy, 31 | &newDelete.Limit, 32 | &newDelete.Returning, 33 | ) 34 | 35 | newDelete.Delete.Table = table 36 | newDelete.Where.Mandatory = true 37 | newDelete.Limit.Count = -1 38 | 39 | return newDelete 40 | } 41 | 42 | func (d *deleteStatementImpl) WHERE(expression BoolExpression) DeleteStatement { 43 | d.Where.Condition = expression 44 | return d 45 | } 46 | 47 | func (d *deleteStatementImpl) ORDER_BY(orderByClauses ...OrderByClause) DeleteStatement { 48 | d.OrderBy.List = orderByClauses 49 | return d 50 | } 51 | 52 | func (d *deleteStatementImpl) LIMIT(limit int64) DeleteStatement { 53 | d.Limit.Count = limit 54 | return d 55 | } 56 | 57 | func (d *deleteStatementImpl) RETURNING(projections ...jet.Projection) DeleteStatement { 58 | d.Returning.ProjectionList = projections 59 | return d 60 | } 61 | -------------------------------------------------------------------------------- /postgres/set_statement_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSelectSets(t *testing.T) { 8 | select1 := SELECT(table1ColBool).FROM(table1) 9 | select2 := SELECT(table2ColBool).FROM(table2) 10 | 11 | assertStatementSql(t, select1.UNION(select2), ` 12 | ( 13 | SELECT table1.col_bool AS "table1.col_bool" 14 | FROM db.table1 15 | ) 16 | UNION 17 | ( 18 | SELECT table2.col_bool AS "table2.col_bool" 19 | FROM db.table2 20 | ); 21 | `) 22 | assertStatementSql(t, select1.UNION_ALL(select2), ` 23 | ( 24 | SELECT table1.col_bool AS "table1.col_bool" 25 | FROM db.table1 26 | ) 27 | UNION ALL 28 | ( 29 | SELECT table2.col_bool AS "table2.col_bool" 30 | FROM db.table2 31 | ); 32 | `) 33 | 34 | assertStatementSql(t, select1.INTERSECT(select2), ` 35 | ( 36 | SELECT table1.col_bool AS "table1.col_bool" 37 | FROM db.table1 38 | ) 39 | INTERSECT 40 | ( 41 | SELECT table2.col_bool AS "table2.col_bool" 42 | FROM db.table2 43 | ); 44 | `) 45 | 46 | assertStatementSql(t, select1.INTERSECT_ALL(select2), ` 47 | ( 48 | SELECT table1.col_bool AS "table1.col_bool" 49 | FROM db.table1 50 | ) 51 | INTERSECT ALL 52 | ( 53 | SELECT table2.col_bool AS "table2.col_bool" 54 | FROM db.table2 55 | ); 56 | `) 57 | assertStatementSql(t, select1.EXCEPT(select2), ` 58 | ( 59 | SELECT table1.col_bool AS "table1.col_bool" 60 | FROM db.table1 61 | ) 62 | EXCEPT 63 | ( 64 | SELECT table2.col_bool AS "table2.col_bool" 65 | FROM db.table2 66 | ); 67 | `) 68 | 69 | assertStatementSql(t, select1.EXCEPT_ALL(select2), ` 70 | ( 71 | SELECT table1.col_bool AS "table1.col_bool" 72 | FROM db.table1 73 | ) 74 | EXCEPT ALL 75 | ( 76 | SELECT table2.col_bool AS "table2.col_bool" 77 | FROM db.table2 78 | ); 79 | `) 80 | 81 | } 82 | -------------------------------------------------------------------------------- /postgres/lock_statement.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // TableLockMode is a type of possible SQL table lock 6 | type TableLockMode string 7 | 8 | // Lock types for LockStatement. 9 | const ( 10 | LOCK_ACCESS_SHARE TableLockMode = "ACCESS SHARE" 11 | LOCK_ROW_SHARE TableLockMode = "ROW SHARE" 12 | LOCK_ROW_EXCLUSIVE TableLockMode = "ROW EXCLUSIVE" 13 | LOCK_SHARE_UPDATE_EXCLUSIVE TableLockMode = "SHARE UPDATE EXCLUSIVE" 14 | LOCK_SHARE TableLockMode = "SHARE" 15 | LOCK_SHARE_ROW_EXCLUSIVE TableLockMode = "SHARE ROW EXCLUSIVE" 16 | LOCK_EXCLUSIVE TableLockMode = "EXCLUSIVE" 17 | LOCK_ACCESS_EXCLUSIVE TableLockMode = "ACCESS EXCLUSIVE" 18 | ) 19 | 20 | // LockStatement is interface for MySQL LOCK tables 21 | type LockStatement interface { 22 | Statement 23 | 24 | IN(lockMode TableLockMode) LockStatement 25 | NOWAIT() LockStatement 26 | } 27 | 28 | // LOCK creates LockStatement from list of tables 29 | func LOCK(tables ...jet.SerializerTable) LockStatement { 30 | newLock := &lockStatementImpl{} 31 | newLock.SerializerStatement = jet.NewStatementImpl(Dialect, jet.LockStatementType, newLock, 32 | &newLock.StatementBegin, &newLock.In, &newLock.NoWait) 33 | 34 | newLock.StatementBegin.Name = "LOCK TABLE" 35 | newLock.StatementBegin.Tables = tables 36 | newLock.NoWait.Name = "NOWAIT" 37 | return newLock 38 | } 39 | 40 | type lockStatementImpl struct { 41 | jet.SerializerStatement 42 | 43 | StatementBegin jet.ClauseStatementBegin 44 | In jet.ClauseIn 45 | NoWait jet.ClauseOptional 46 | } 47 | 48 | func (l *lockStatementImpl) IN(lockMode TableLockMode) LockStatement { 49 | l.In.LockMode = string(lockMode) 50 | return l 51 | } 52 | 53 | func (l *lockStatementImpl) NOWAIT() LockStatement { 54 | l.NoWait.Show = true 55 | return l 56 | } 57 | -------------------------------------------------------------------------------- /generator/template/generator_template.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/generator/metadata" 5 | "github.com/go-jet/jet/v2/internal/jet" 6 | ) 7 | 8 | // Template is generator template used for file generation 9 | type Template struct { 10 | Dialect jet.Dialect 11 | Schema func(schemaMetaData metadata.Schema) Schema 12 | } 13 | 14 | // Default is default generator template implementation 15 | func Default(dialect jet.Dialect) Template { 16 | return Template{ 17 | Dialect: dialect, 18 | Schema: DefaultSchema, 19 | } 20 | } 21 | 22 | // UseSchema replaces current schema generate function with a new implementation and returns new generator template 23 | func (t Template) UseSchema(schemaFunc func(schemaMetaData metadata.Schema) Schema) Template { 24 | t.Schema = schemaFunc 25 | return t 26 | } 27 | 28 | // Schema is schema generator template used to generate schema(model and sql builder) files 29 | type Schema struct { 30 | Path string 31 | Model Model 32 | SQLBuilder SQLBuilder 33 | } 34 | 35 | // UsePath replaces path and returns new schema template 36 | func (s Schema) UsePath(path string) Schema { 37 | s.Path = path 38 | return s 39 | } 40 | 41 | // UseModel returns new schema template with replaced template for model files generation 42 | func (s Schema) UseModel(model Model) Schema { 43 | s.Model = model 44 | return s 45 | } 46 | 47 | // UseSQLBuilder returns new schema with replaced template for sql builder files generation 48 | func (s Schema) UseSQLBuilder(sqlBuilder SQLBuilder) Schema { 49 | s.SQLBuilder = sqlBuilder 50 | return s 51 | } 52 | 53 | // DefaultSchema returns default schema template implementation 54 | func DefaultSchema(schemaMetaData metadata.Schema) Schema { 55 | return Schema{ 56 | Path: schemaMetaData.Name, 57 | Model: DefaultModel(), 58 | SQLBuilder: DefaultSQLBuilder(), 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /postgres/clause.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/jet" 5 | "github.com/go-jet/jet/v2/internal/utils/is" 6 | ) 7 | 8 | type onConflict interface { 9 | ON_CONSTRAINT(name string) conflictTarget 10 | WHERE(indexPredicate BoolExpression) conflictTarget 11 | conflictTarget 12 | } 13 | 14 | type conflictTarget interface { 15 | DO_NOTHING() InsertStatement 16 | DO_UPDATE(action conflictAction) InsertStatement 17 | } 18 | 19 | type onConflictClause struct { 20 | insertStatement InsertStatement 21 | constraint string 22 | indexExpressions []jet.ColumnExpression 23 | whereClause jet.ClauseWhere 24 | do jet.Serializer 25 | } 26 | 27 | func (o *onConflictClause) ON_CONSTRAINT(name string) conflictTarget { 28 | o.constraint = name 29 | return o 30 | } 31 | 32 | func (o *onConflictClause) WHERE(indexPredicate BoolExpression) conflictTarget { 33 | o.whereClause.Condition = indexPredicate 34 | return o 35 | } 36 | 37 | func (o *onConflictClause) DO_NOTHING() InsertStatement { 38 | o.do = jet.Keyword("DO NOTHING") 39 | return o.insertStatement 40 | } 41 | 42 | func (o *onConflictClause) DO_UPDATE(action conflictAction) InsertStatement { 43 | o.do = action 44 | return o.insertStatement 45 | } 46 | 47 | func (o *onConflictClause) Serialize(statementType jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) { 48 | if is.Nil(o.do) { 49 | return 50 | } 51 | 52 | out.NewLine() 53 | out.WriteString("ON CONFLICT") 54 | if len(o.indexExpressions) > 0 { 55 | out.WriteString("(") 56 | jet.SerializeColumnExpressions(o.indexExpressions, statementType, out, jet.ShortName) 57 | out.WriteString(")") 58 | } 59 | 60 | if o.constraint != "" { 61 | out.WriteString("ON CONSTRAINT") 62 | out.WriteString(o.constraint) 63 | } 64 | 65 | o.whereClause.Serialize(statementType, out, jet.SkipNewLine, jet.ShortName) 66 | 67 | out.IncreaseIdent(7) 68 | jet.Serialize(o.do, statementType, out) 69 | out.DecreaseIdent(7) 70 | } 71 | -------------------------------------------------------------------------------- /mysql/cast.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/jet" 5 | "strconv" 6 | ) 7 | 8 | type cast struct { 9 | jet.Cast 10 | } 11 | 12 | // CAST function converts a expr (of any type) into latter specified datatype. 13 | func CAST(expr Expression) *cast { 14 | ret := &cast{} 15 | ret.Cast = jet.NewCastImpl(expr) 16 | 17 | return ret 18 | } 19 | 20 | // AS casts expressions to castType 21 | func (c *cast) AS(castType string) Expression { 22 | return c.Cast.AS(castType) 23 | } 24 | 25 | // AS_DATETIME cast expression to DATETIME type 26 | func (c *cast) AS_DATETIME() DateTimeExpression { 27 | return DateTimeExp(c.AS("DATETIME")) 28 | } 29 | 30 | // AS_SIGNED casts expression to SIGNED type 31 | func (c *cast) AS_SIGNED() IntegerExpression { 32 | return IntExp(c.AS("SIGNED")) 33 | } 34 | 35 | // AS_UNSIGNED casts expression to UNSIGNED type 36 | func (c *cast) AS_UNSIGNED() IntegerExpression { 37 | return IntExp(c.AS("UNSIGNED")) 38 | } 39 | 40 | // AS_CHAR casts expression to CHAR type with optional length 41 | func (c *cast) AS_CHAR(length ...int) StringExpression { 42 | if len(length) > 0 { 43 | return StringExp(c.AS("CHAR(" + strconv.Itoa(length[0]) + ")")) 44 | } 45 | 46 | return StringExp(c.AS("CHAR")) 47 | } 48 | 49 | // AS_DATE casts expression AS DATE type 50 | func (c *cast) AS_DATE() DateExpression { 51 | return DateExp(c.AS("DATE")) 52 | } 53 | 54 | func (c *cast) AS_FLOAT() FloatExpression { 55 | return FloatExp(c.AS("FLOAT")) 56 | } 57 | 58 | func (c *cast) AS_DOUBLE() FloatExpression { 59 | return FloatExp(c.AS("DOUBLE")) 60 | } 61 | 62 | // AS_DECIMAL casts expression AS DECIMAL type 63 | func (c *cast) AS_DECIMAL() FloatExpression { 64 | return FloatExp(c.AS("DECIMAL")) 65 | } 66 | 67 | // AS_TIME casts expression AS TIME type 68 | func (c *cast) AS_TIME() TimeExpression { 69 | return TimeExp(c.AS("TIME")) 70 | } 71 | 72 | // AS_BINARY casts expression as BINARY type 73 | func (c *cast) AS_BINARY() BlobExpression { 74 | return BlobExp(c.AS("BINARY")) 75 | } 76 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-jet/jet/v2 2 | 3 | go 1.23.0 4 | 5 | // used by jet generator 6 | require ( 7 | github.com/go-sql-driver/mysql v1.9.3 8 | github.com/google/uuid v1.6.0 9 | github.com/jackc/pgconn v1.14.3 10 | github.com/jackc/pgtype v1.14.4 11 | github.com/jackc/pgx/v4 v4.18.3 12 | github.com/lib/pq v1.10.9 13 | github.com/mattn/go-sqlite3 v1.14.28 14 | ) 15 | 16 | // used in tests 17 | require ( 18 | github.com/bytedance/sonic v1.13.3 19 | github.com/google/go-cmp v0.7.0 20 | github.com/pkg/profile v1.7.0 21 | github.com/shopspring/decimal v1.4.0 22 | github.com/stretchr/testify v1.10.0 23 | github.com/volatiletech/null/v8 v8.1.2 24 | gopkg.in/guregu/null.v4 v4.0.0 25 | ) 26 | 27 | require ( 28 | filippo.io/edwards25519 v1.1.0 // indirect 29 | github.com/bytedance/sonic/loader v0.2.4 // indirect 30 | github.com/cloudwego/base64x v0.1.5 // indirect 31 | github.com/davecgh/go-spew v1.1.1 // indirect 32 | github.com/felixge/fgprof v0.9.3 // indirect 33 | github.com/friendsofgo/errors v0.9.2 // indirect 34 | github.com/gofrs/uuid v4.0.0+incompatible // indirect 35 | github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect 36 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 37 | github.com/jackc/pgio v1.0.0 // indirect 38 | github.com/jackc/pgpassfile v1.0.0 // indirect 39 | github.com/jackc/pgproto3/v2 v2.3.3 // indirect 40 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 41 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 42 | github.com/pmezard/go-difflib v1.0.0 // indirect 43 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 44 | github.com/volatiletech/inflect v0.0.1 // indirect 45 | github.com/volatiletech/randomize v0.0.1 // indirect 46 | github.com/volatiletech/strmangle v0.0.1 // indirect 47 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect 48 | golang.org/x/crypto v0.35.0 // indirect 49 | golang.org/x/text v0.22.0 // indirect 50 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 51 | gopkg.in/yaml.v3 v3.0.1 // indirect 52 | ) 53 | -------------------------------------------------------------------------------- /sqlite/update_statement_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestUpdateWithOneValue(t *testing.T) { 10 | expectedSQL := ` 11 | UPDATE db.table1 12 | SET col_int = ? 13 | WHERE table1.col_int >= ?; 14 | ` 15 | stmt := table1.UPDATE(table1ColInt). 16 | SET(1). 17 | WHERE(table1ColInt.GT_EQ(Int(33))) 18 | 19 | fmt.Println(stmt.Sql()) 20 | 21 | assertStatementSql(t, stmt, expectedSQL, 1, int64(33)) 22 | } 23 | 24 | func TestUpdateWithValues(t *testing.T) { 25 | expectedSQL := ` 26 | UPDATE db.table1 27 | SET col_int = ?, 28 | col_float = ? 29 | WHERE table1.col_int >= ?; 30 | ` 31 | stmt := table1.UPDATE(table1ColInt, table1ColFloat). 32 | SET(1, 22.2). 33 | WHERE(table1ColInt.GT_EQ(Int(33))) 34 | 35 | fmt.Println(stmt.Sql()) 36 | 37 | assertStatementSql(t, stmt, expectedSQL, 1, 22.2, int64(33)) 38 | } 39 | 40 | func TestUpdateOneColumnWithSelect(t *testing.T) { 41 | expectedSQL := ` 42 | UPDATE db.table1 43 | SET col_float = ( 44 | SELECT table1.col_float AS "table1.col_float" 45 | FROM db.table1 46 | ) 47 | WHERE table1.col1 = ?; 48 | ` 49 | stmt := table1. 50 | UPDATE(table1ColFloat). 51 | SET( 52 | table1.SELECT(table1ColFloat), 53 | ). 54 | WHERE(table1Col1.EQ(Int(2))) 55 | 56 | assertStatementSql(t, stmt, expectedSQL, int64(2)) 57 | } 58 | 59 | func TestUpdateReservedWorldColumn(t *testing.T) { 60 | type table struct { 61 | Load string 62 | } 63 | 64 | loadColumn := StringColumn("Load") 65 | assertStatementSql(t, 66 | table1.UPDATE(loadColumn). 67 | MODEL( 68 | table{ 69 | Load: "foo", 70 | }, 71 | ). 72 | WHERE(loadColumn.EQ(String("bar"))), strings.Replace(` 73 | UPDATE db.table1 74 | SET ''Load'' = ? 75 | WHERE ''Load'' = ?; 76 | `, "''", "`", -1), "foo", "bar") 77 | } 78 | 79 | func TestInvalidInputs(t *testing.T) { 80 | assertStatementSqlErr(t, table1.UPDATE(table1ColInt).SET(1), "jet: WHERE clause not set") 81 | assertStatementSqlErr(t, table1.UPDATE(nil).SET(1), "jet: nil column in columns list for SET clause") 82 | } 83 | -------------------------------------------------------------------------------- /internal/3rdparty/pq/format_timestamp_test.go: -------------------------------------------------------------------------------- 1 | package pq 2 | 3 | // Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany 4 | 5 | import ( 6 | "testing" 7 | "time" 8 | ) 9 | 10 | var formatTimeTests = []struct { 11 | time time.Time 12 | expected string 13 | }{ 14 | {time.Time{}, "0001-01-01 00:00:00Z"}, 15 | {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03 04:05:06.123456789Z"}, 16 | {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03 04:05:06.123456789+02:00"}, 17 | {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03 04:05:06.123456789-06:00"}, 18 | {time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03 04:05:06-07:30:09"}, 19 | 20 | {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z"}, 21 | {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00"}, 22 | {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00"}, 23 | 24 | {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z BC"}, 25 | {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00 BC"}, 26 | {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00 BC"}, 27 | 28 | {time.Date(1, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09"}, 29 | {time.Date(0, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09 BC"}, 30 | } 31 | 32 | func TestFormatTs(t *testing.T) { 33 | for i, tt := range formatTimeTests { 34 | val := string(FormatTimestamp(tt.time)) 35 | if val != tt.expected { 36 | t.Errorf("%d: incorrect time format %q, want %q", i, val, tt.expected) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sqlite/literal_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestBool(t *testing.T) { 10 | assertSerialize(t, Bool(false), `?`, false) 11 | } 12 | 13 | func TestInt(t *testing.T) { 14 | assertSerialize(t, Int(11), `?`, int64(11)) 15 | } 16 | 17 | func TestInt8(t *testing.T) { 18 | val := int8(math.MinInt8) 19 | assertSerialize(t, Int8(val), `?`, val) 20 | } 21 | 22 | func TestInt16(t *testing.T) { 23 | val := int16(math.MinInt16) 24 | assertSerialize(t, Int16(val), `?`, val) 25 | } 26 | 27 | func TestInt32(t *testing.T) { 28 | val := int32(math.MinInt32) 29 | assertSerialize(t, Int32(val), `?`, val) 30 | } 31 | 32 | func TestInt64(t *testing.T) { 33 | val := int64(math.MinInt64) 34 | assertSerialize(t, Int64(val), `?`, val) 35 | } 36 | 37 | func TestUint8(t *testing.T) { 38 | val := uint8(math.MaxUint8) 39 | assertSerialize(t, Uint8(val), `?`, val) 40 | } 41 | 42 | func TestUint16(t *testing.T) { 43 | val := uint16(math.MaxUint16) 44 | assertSerialize(t, Uint16(val), `?`, val) 45 | } 46 | 47 | func TestUint32(t *testing.T) { 48 | val := uint32(math.MaxUint32) 49 | assertSerialize(t, Uint32(val), `?`, val) 50 | } 51 | 52 | func TestUint64(t *testing.T) { 53 | val := uint64(math.MaxUint64) 54 | assertSerialize(t, Uint64(val), `?`, val) 55 | } 56 | 57 | func TestFloat(t *testing.T) { 58 | assertSerialize(t, Float(12.34), `?`, float64(12.34)) 59 | } 60 | 61 | func TestString(t *testing.T) { 62 | assertSerialize(t, String("Some text"), `?`, "Some text") 63 | } 64 | 65 | var testTime = time.Now() 66 | 67 | func TestDate(t *testing.T) { 68 | assertSerialize(t, Date(2014, time.January, 2), "DATE(?)", "2014-01-02") 69 | assertSerialize(t, DATE(testTime), "DATE(?)", testTime) 70 | } 71 | 72 | func TestTime(t *testing.T) { 73 | assertSerialize(t, Time(10, 15, 30), `TIME(?)`, "10:15:30") 74 | assertSerialize(t, TIME(testTime), "TIME(?)", testTime) 75 | } 76 | 77 | func TestDateTime(t *testing.T) { 78 | assertSerialize(t, DateTime(2010, time.March, 30, 10, 15, 30), `DATETIME(?)`, "2010-03-30 10:15:30") 79 | assertSerialize(t, DATETIME(testTime), `DATETIME(?)`, testTime) 80 | } 81 | -------------------------------------------------------------------------------- /tests/dbconfig/dbconfig.go: -------------------------------------------------------------------------------- 1 | package dbconfig 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-jet/jet/v2/tests/internal/utils/repo" 6 | ) 7 | 8 | // Postgres test database connection parameters 9 | const ( 10 | PgHost = "localhost" 11 | PgPort = 50901 12 | PgUser = "jet" 13 | PgPassword = "jet" 14 | PgDBName = "jetdb" 15 | ) 16 | 17 | // PostgresConnectString is PostgreSQL test database connection string 18 | var PostgresConnectString = pgConnectionString(PgHost, PgPort, PgUser, PgPassword, PgDBName) 19 | 20 | // Postgres test database connection parameters 21 | const ( 22 | CockroachHost = "localhost" 23 | CockroachPort = 26257 24 | CockroachUser = "jet" 25 | CockroachPassword = "jet" 26 | CockroachDBName = "jetdb" 27 | ) 28 | 29 | // CockroachConnectString is Cockroach test database connection string 30 | var CockroachConnectString = pgConnectionString(CockroachHost, CockroachPort, CockroachUser, CockroachPassword, CockroachDBName) 31 | 32 | func pgConnectionString(host string, port int, user, password, dbName string) string { 33 | return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", 34 | host, port, user, password, dbName) 35 | } 36 | 37 | // MySQL test database connection parameters 38 | const ( 39 | MySqLHost = "127.0.0.1" 40 | MySQLPort = 50902 41 | MySQLUser = "jet" 42 | MySQLPassword = "jet" 43 | 44 | MariaDBHost = "127.0.0.1" 45 | MariaDBPort = 50903 46 | MariaDBUser = "jet" 47 | MariaDBPassword = "jet" 48 | ) 49 | 50 | // MySQLConnectionString is MySQL connection string for test database 51 | func MySQLConnectionString(isMariaDB bool, dbName string) string { 52 | if isMariaDB { 53 | return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", MariaDBUser, MariaDBPassword, MariaDBHost, MariaDBPort, dbName) 54 | } 55 | 56 | return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", MySQLUser, MySQLPassword, MySqLHost, MySQLPort, dbName) 57 | } 58 | 59 | // sqllite 60 | var ( 61 | SakilaDBPath = repo.GetTestDataFilePath("/init/sqlite/sakila.db") 62 | ChinookDBPath = repo.GetTestDataFilePath("/init/sqlite/chinook.db") 63 | TestSampleDBPath = repo.GetTestDataFilePath("/init/sqlite/test_sample.db") 64 | ) 65 | -------------------------------------------------------------------------------- /sqlite/columns.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // Column is common column interface for all types of columns. 6 | type Column = jet.ColumnExpression 7 | 8 | // ColumnList function returns list of columns that be used as projection or column list for UPDATE and INSERT statement. 9 | type ColumnList = jet.ColumnList 10 | 11 | // ColumnBool is interface for SQL boolean columns. 12 | type ColumnBool = jet.ColumnBool 13 | 14 | // BoolColumn creates named bool column. 15 | var BoolColumn = jet.BoolColumn 16 | 17 | // ColumnString is interface for SQL text, character, character varying 18 | // bytea, uuid columns and enums types. 19 | type ColumnString = jet.ColumnString 20 | 21 | // StringColumn creates named string column. 22 | var StringColumn = jet.StringColumn 23 | 24 | // ColumnBlob is interface for 25 | type ColumnBlob = jet.ColumnBlob 26 | 27 | // BlobColumn creates new named blob column 28 | var BlobColumn = jet.BlobColumn 29 | 30 | // ColumnInteger is interface for SQL smallint, integer, bigint columns. 31 | type ColumnInteger = jet.ColumnInteger 32 | 33 | // IntegerColumn creates named integer column. 34 | var IntegerColumn = jet.IntegerColumn 35 | 36 | // ColumnFloat is interface for SQL real, numeric, decimal or double precision column. 37 | type ColumnFloat = jet.ColumnFloat 38 | 39 | // FloatColumn creates named float column. 40 | var FloatColumn = jet.FloatColumn 41 | 42 | // ColumnTime is interface for SQL time column. 43 | type ColumnTime = jet.ColumnTime 44 | 45 | // TimeColumn creates named time column 46 | var TimeColumn = jet.TimeColumn 47 | 48 | // ColumnDate is interface of SQL date columns. 49 | type ColumnDate = jet.ColumnDate 50 | 51 | // DateColumn creates named date column. 52 | var DateColumn = jet.DateColumn 53 | 54 | // ColumnDateTime is interface of SQL timestamp columns. 55 | type ColumnDateTime = jet.ColumnTimestamp 56 | 57 | // DateTimeColumn creates named timestamp column 58 | var DateTimeColumn = jet.TimestampColumn 59 | 60 | // ColumnTimestamp is interface of SQL timestamp columns. 61 | type ColumnTimestamp = jet.ColumnTimestamp 62 | 63 | // TimestampColumn creates named timestamp column 64 | var TimestampColumn = jet.TimestampColumn 65 | -------------------------------------------------------------------------------- /mysql/columns.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // Column is common column interface for all types of columns. 6 | type Column = jet.ColumnExpression 7 | 8 | // ColumnList function returns list of columns that be used as projection or column list for UPDATE and INSERT statement. 9 | type ColumnList = jet.ColumnList 10 | 11 | // ColumnBool is interface for SQL boolean columns. 12 | type ColumnBool = jet.ColumnBool 13 | 14 | // BoolColumn creates named bool column. 15 | var BoolColumn = jet.BoolColumn 16 | 17 | // ColumnString is interface for SQL text, character, character varying 18 | // bytea, uuid columns and enums types. 19 | type ColumnString = jet.ColumnString 20 | 21 | // StringColumn creates named string column. 22 | var StringColumn = jet.StringColumn 23 | 24 | // ColumnBlob is interface for blob columns. 25 | type ColumnBlob = jet.ColumnBlob 26 | 27 | // BlobColumn creates named blob column. 28 | var BlobColumn = jet.BlobColumn 29 | 30 | // ColumnInteger is interface for SQL smallint, integer, bigint columns. 31 | type ColumnInteger = jet.ColumnInteger 32 | 33 | // IntegerColumn creates named integer column. 34 | var IntegerColumn = jet.IntegerColumn 35 | 36 | // ColumnFloat is interface for SQL real, numeric, decimal or double precision column. 37 | type ColumnFloat = jet.ColumnFloat 38 | 39 | // FloatColumn creates named float column. 40 | var FloatColumn = jet.FloatColumn 41 | 42 | // ColumnTime is interface for SQL time column. 43 | type ColumnTime = jet.ColumnTime 44 | 45 | // TimeColumn creates named time column 46 | var TimeColumn = jet.TimeColumn 47 | 48 | // ColumnDate is interface of SQL date columns. 49 | type ColumnDate = jet.ColumnDate 50 | 51 | // DateColumn creates named date column. 52 | var DateColumn = jet.DateColumn 53 | 54 | // ColumnDateTime is interface of SQL timestamp columns. 55 | type ColumnDateTime = jet.ColumnTimestamp 56 | 57 | // DateTimeColumn creates named timestamp column 58 | var DateTimeColumn = jet.TimestampColumn 59 | 60 | // ColumnTimestamp is interface of SQL timestamp columns. 61 | type ColumnTimestamp = jet.ColumnTimestamp 62 | 63 | // TimestampColumn creates named timestamp column 64 | var TimestampColumn = jet.TimestampColumn 65 | -------------------------------------------------------------------------------- /internal/jet/select_table.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | // SelectTable is interface for SELECT sub-queries 4 | type SelectTable interface { 5 | SerializerHasProjections 6 | Alias() string 7 | AllColumns() ProjectionList 8 | } 9 | 10 | type selectTableImpl struct { 11 | Statement SerializerHasProjections 12 | alias string 13 | columnAliases []ColumnExpression 14 | } 15 | 16 | // NewSelectTable func 17 | func NewSelectTable(selectStmt SerializerHasProjections, alias string, columnAliases []ColumnExpression) selectTableImpl { 18 | selectTable := selectTableImpl{ 19 | Statement: selectStmt, 20 | alias: alias, 21 | columnAliases: columnAliases, 22 | } 23 | 24 | for _, column := range selectTable.columnAliases { 25 | column.setSubQuery(selectTable) 26 | } 27 | 28 | return selectTable 29 | } 30 | 31 | func (s selectTableImpl) projections() ProjectionList { 32 | return s.Statement.projections() 33 | } 34 | 35 | func (s selectTableImpl) Alias() string { 36 | return s.alias 37 | } 38 | 39 | func (s selectTableImpl) AllColumns() ProjectionList { 40 | if len(s.columnAliases) > 0 { 41 | return ColumnListToProjectionList(s.columnAliases) 42 | } 43 | 44 | projectionList := s.projections().fromImpl(s) 45 | return projectionList.(ProjectionList) 46 | } 47 | 48 | func (s selectTableImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { 49 | s.Statement.serialize(statement, out) 50 | 51 | out.WriteString("AS") 52 | out.WriteIdentifier(s.alias) 53 | 54 | if len(s.columnAliases) > 0 { 55 | out.WriteByte('(') 56 | SerializeColumnExpressionNames(s.columnAliases, out) 57 | out.WriteByte(')') 58 | } 59 | } 60 | 61 | // -------------------------------------- 62 | 63 | type lateralImpl struct { 64 | selectTableImpl 65 | } 66 | 67 | // NewLateral creates new lateral expression from select statement with alias 68 | func NewLateral(selectStmt SerializerStatement, alias string) SelectTable { 69 | return lateralImpl{selectTableImpl: NewSelectTable(selectStmt, alias, nil)} 70 | } 71 | 72 | func (s lateralImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { 73 | out.WriteString("LATERAL") 74 | s.Statement.serialize(statement, out) 75 | 76 | out.WriteString("AS") 77 | out.WriteIdentifier(s.alias) 78 | } 79 | -------------------------------------------------------------------------------- /internal/jet/projection_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import "testing" 4 | 5 | func TestProjectionAs(t *testing.T) { 6 | projectionList := ProjectionList{ 7 | table1Col3, 8 | SUM(table1ColInt).AS("sum"), 9 | SUM(table1ColInt).AS("table.sum"), 10 | ProjectionList{ 11 | table1ColBool, 12 | AVG(table1ColInt).AS("avg"), 13 | AVG(table1ColInt).AS("t.avg"), 14 | }, 15 | ColumnList{table2Col3, table2Col4}, 16 | } 17 | 18 | aliasedProjectionList := projectionList.As("new_alias.*") 19 | 20 | assertProjectionSerialize(t, aliasedProjectionList, 21 | `table1.col3 AS "new_alias.col3", 22 | SUM(table1.col_int) AS "new_alias.sum", 23 | SUM(table1.col_int) AS "new_alias.sum", 24 | table1.col_bool AS "new_alias.col_bool", 25 | AVG(table1.col_int) AS "new_alias.avg", 26 | AVG(table1.col_int) AS "new_alias.avg", 27 | table2.col3 AS "new_alias.col3", 28 | table2.col4 AS "new_alias.col4"`) 29 | 30 | aliasedProjectionList = projectionList.As("") 31 | 32 | assertProjectionSerialize(t, aliasedProjectionList, 33 | `table1.col3 AS "col3", 34 | SUM(table1.col_int) AS "sum", 35 | SUM(table1.col_int) AS "sum", 36 | table1.col_bool AS "col_bool", 37 | AVG(table1.col_int) AS "avg", 38 | AVG(table1.col_int) AS "avg", 39 | table2.col3 AS "col3", 40 | table2.col4 AS "col4"`) 41 | 42 | subQueryProjections := projectionList.fromImpl(NewSelectTable(nil, "subQuery", nil)) 43 | 44 | assertProjectionSerialize(t, subQueryProjections, 45 | `"subQuery"."table1.col3" AS "table1.col3", 46 | "subQuery".sum AS "sum", 47 | "subQuery"."table.sum" AS "table.sum", 48 | "subQuery"."table1.col_bool" AS "table1.col_bool", 49 | "subQuery".avg AS "avg", 50 | "subQuery"."t.avg" AS "t.avg", 51 | "subQuery"."table2.col3" AS "table2.col3", 52 | "subQuery"."table2.col4" AS "table2.col4"`) 53 | 54 | aliasedSubQueryProjectionList := subQueryProjections.(ProjectionList).As("subAlias") 55 | 56 | assertProjectionSerialize(t, aliasedSubQueryProjectionList, 57 | `"subQuery"."table1.col3" AS "subAlias.col3", 58 | "subQuery".sum AS "subAlias.sum", 59 | "subQuery"."table.sum" AS "subAlias.sum", 60 | "subQuery"."table1.col_bool" AS "subAlias.col_bool", 61 | "subQuery".avg AS "subAlias.avg", 62 | "subQuery"."t.avg" AS "subAlias.avg", 63 | "subQuery"."table2.col3" AS "subAlias.col3", 64 | "subQuery"."table2.col4" AS "subAlias.col4"`) 65 | } 66 | -------------------------------------------------------------------------------- /mysql/delete_statement.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // DeleteStatement is interface for MySQL DELETE statement 6 | type DeleteStatement interface { 7 | Statement 8 | 9 | OPTIMIZER_HINTS(hints ...OptimizerHint) DeleteStatement 10 | 11 | USING(tables ...ReadableTable) DeleteStatement 12 | WHERE(expression BoolExpression) DeleteStatement 13 | ORDER_BY(orderByClauses ...OrderByClause) DeleteStatement 14 | LIMIT(limit int64) DeleteStatement 15 | RETURNING(projections ...jet.Projection) DeleteStatement 16 | } 17 | 18 | type deleteStatementImpl struct { 19 | jet.SerializerStatement 20 | 21 | Delete jet.ClauseDelete 22 | Using jet.ClauseFrom 23 | Where jet.ClauseWhere 24 | OrderBy jet.ClauseOrderBy 25 | Limit jet.ClauseLimit 26 | Returning jet.ClauseReturning 27 | } 28 | 29 | func newDeleteStatement(table Table) DeleteStatement { 30 | newDelete := &deleteStatementImpl{} 31 | newDelete.SerializerStatement = jet.NewStatementImpl(Dialect, jet.DeleteStatementType, newDelete, 32 | &newDelete.Delete, 33 | &newDelete.Using, 34 | &newDelete.Where, 35 | &newDelete.OrderBy, 36 | &newDelete.Limit, 37 | &newDelete.Returning, 38 | ) 39 | 40 | newDelete.Delete.Table = table 41 | newDelete.Using.Name = "USING" 42 | newDelete.Where.Mandatory = true 43 | newDelete.Limit.Count = -1 44 | 45 | return newDelete 46 | } 47 | 48 | func (d *deleteStatementImpl) OPTIMIZER_HINTS(hints ...OptimizerHint) DeleteStatement { 49 | d.Delete.OptimizerHints = hints 50 | return d 51 | } 52 | 53 | func (d *deleteStatementImpl) USING(tables ...ReadableTable) DeleteStatement { 54 | d.Using.Tables = readableTablesToSerializerList(tables) 55 | return d 56 | } 57 | 58 | func (d *deleteStatementImpl) WHERE(expression BoolExpression) DeleteStatement { 59 | d.Where.Condition = expression 60 | return d 61 | } 62 | 63 | func (d *deleteStatementImpl) ORDER_BY(orderByClauses ...OrderByClause) DeleteStatement { 64 | d.OrderBy.List = orderByClauses 65 | return d 66 | } 67 | 68 | func (d *deleteStatementImpl) LIMIT(limit int64) DeleteStatement { 69 | d.Limit.Count = limit 70 | return d 71 | } 72 | 73 | func (d *deleteStatementImpl) RETURNING(projections ...jet.Projection) DeleteStatement { 74 | d.Returning.ProjectionList = projections 75 | return d 76 | } 77 | -------------------------------------------------------------------------------- /internal/jet/group_by_clause.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | // GroupByClause interface 4 | type GroupByClause interface { 5 | serializeForGroupBy(statement StatementType, out *SQLBuilder) 6 | } 7 | 8 | // GROUPING_SETS operator allows grouping of the rows in a table by multiple sets of columns in a single query. 9 | // This can be useful when we want to analyze data by different combinations of columns, without having to write separate 10 | // queries for each combination. 11 | func GROUPING_SETS(expressions ...Expression) GroupByClause { 12 | return Func("GROUPING SETS", expressions...) 13 | } 14 | 15 | // ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list. 16 | // It creates extra rows in the result set that represent the subtotal values for each combination of columns. 17 | func ROLLUP(expressions ...Expression) GroupByClause { 18 | return Func("ROLLUP", expressions...) 19 | } 20 | 21 | // CUBE operator is used with the GROUP BY clause to generate subtotals for all possible combinations of a group of columns. 22 | // It creates extra rows in the result set that represent the subtotal values for each combination of columns. 23 | func CUBE(expressions ...Expression) GroupByClause { 24 | return Func("CUBE", expressions...) 25 | } 26 | 27 | // GROUPING function is used to identify which columns are included in a grouping set or a subtotal row. It takes as input 28 | // the name of a column and returns 1 if the column is not included in the current grouping set, and 0 otherwise. 29 | // It can be also used with multiple parameters to check if a set of columns is included in the current grouping set. The result 30 | // of the GROUPING function would then be an integer bit mask having 1’s for the arguments which have GROUPING(argument) as 1. 31 | func GROUPING(expressions ...Expression) IntegerExpression { 32 | return IntExp(Func("GROUPING", expressions...)) 33 | } 34 | 35 | // WITH_ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list. 36 | // It creates extra rows in the result set that represent the subtotal values for each combination of columns. 37 | func WITH_ROLLUP(expressions ...Expression) GroupByClause { 38 | return CustomExpression( 39 | parametersSerializer(expressions), Token("WITH ROLLUP"), 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /mysql/with_statement.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // CommonTableExpression defines set of interface methods for postgres CTEs 6 | type CommonTableExpression interface { 7 | SelectTable 8 | 9 | AS(statement jet.SerializerHasProjections) CommonTableExpression 10 | // ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query. 11 | ALIAS(alias string) SelectTable 12 | 13 | internalCTE() *jet.CommonTableExpression 14 | } 15 | 16 | type commonTableExpression struct { 17 | readableTableInterfaceImpl 18 | jet.CommonTableExpression 19 | } 20 | 21 | // WITH function creates new WITH statement from list of common table expressions 22 | func WITH(cte ...CommonTableExpression) func(statement jet.Statement) Statement { 23 | return jet.WITH(Dialect, false, toInternalCTE(cte)...) 24 | } 25 | 26 | // WITH_RECURSIVE function creates new WITH RECURSIVE statement from list of common table expressions 27 | func WITH_RECURSIVE(cte ...CommonTableExpression) func(statement jet.Statement) Statement { 28 | return jet.WITH(Dialect, true, toInternalCTE(cte)...) 29 | } 30 | 31 | // CTE creates new named commonTableExpression 32 | func CTE(name string, columns ...jet.ColumnExpression) CommonTableExpression { 33 | cte := &commonTableExpression{ 34 | readableTableInterfaceImpl: readableTableInterfaceImpl{}, 35 | CommonTableExpression: jet.CTE(name, columns...), 36 | } 37 | 38 | cte.root = cte 39 | 40 | return cte 41 | } 42 | 43 | // AS is used to define a CTE query 44 | func (c *commonTableExpression) AS(statement jet.SerializerHasProjections) CommonTableExpression { 45 | c.CommonTableExpression.Statement = statement 46 | return c 47 | } 48 | 49 | func (c *commonTableExpression) internalCTE() *jet.CommonTableExpression { 50 | return &c.CommonTableExpression 51 | } 52 | 53 | // ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query. 54 | func (c *commonTableExpression) ALIAS(name string) SelectTable { 55 | return newSelectTable(c, name, nil) 56 | } 57 | 58 | func toInternalCTE(ctes []CommonTableExpression) []*jet.CommonTableExpression { 59 | var ret []*jet.CommonTableExpression 60 | 61 | for _, cte := range ctes { 62 | ret = append(ret, cte.internalCTE()) 63 | } 64 | 65 | return ret 66 | } 67 | -------------------------------------------------------------------------------- /mysql/literal_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestBool(t *testing.T) { 10 | assertSerialize(t, Bool(false), `?`, false) 11 | } 12 | 13 | func TestInt(t *testing.T) { 14 | assertSerialize(t, Int(11), `?`, int64(11)) 15 | } 16 | 17 | func TestInt8(t *testing.T) { 18 | val := int8(math.MinInt8) 19 | assertSerialize(t, Int8(val), `?`, val) 20 | } 21 | 22 | func TestInt16(t *testing.T) { 23 | val := int16(math.MinInt16) 24 | assertSerialize(t, Int16(val), `?`, val) 25 | } 26 | 27 | func TestInt32(t *testing.T) { 28 | val := int32(math.MinInt32) 29 | assertSerialize(t, Int32(val), `?`, val) 30 | } 31 | 32 | func TestInt64(t *testing.T) { 33 | val := int64(math.MinInt64) 34 | assertSerialize(t, Int64(val), `?`, val) 35 | } 36 | 37 | func TestUint8(t *testing.T) { 38 | val := uint8(math.MaxUint8) 39 | assertSerialize(t, Uint8(val), `?`, val) 40 | } 41 | 42 | func TestUint16(t *testing.T) { 43 | val := uint16(math.MaxUint16) 44 | assertSerialize(t, Uint16(val), `?`, val) 45 | } 46 | 47 | func TestUint32(t *testing.T) { 48 | val := uint32(math.MaxUint32) 49 | assertSerialize(t, Uint32(val), `?`, val) 50 | } 51 | 52 | func TestUint64(t *testing.T) { 53 | val := uint64(math.MaxUint64) 54 | assertSerialize(t, Uint64(val), `?`, val) 55 | } 56 | 57 | func TestFloat(t *testing.T) { 58 | assertSerialize(t, Float(12.34), `?`, float64(12.34)) 59 | } 60 | 61 | func TestString(t *testing.T) { 62 | assertSerialize(t, String("Some text"), `?`, "Some text") 63 | } 64 | 65 | func TestDate(t *testing.T) { 66 | assertSerialize(t, Date(2014, time.January, 2), `CAST(? AS DATE)`, "2014-01-02") 67 | assertSerialize(t, DateT(time.Now()), `CAST(? AS DATE)`) 68 | } 69 | 70 | func TestTime(t *testing.T) { 71 | assertSerialize(t, Time(10, 15, 30), `CAST(? AS TIME)`, "10:15:30") 72 | assertSerialize(t, TimeT(time.Now()), `CAST(? AS TIME)`) 73 | } 74 | 75 | func TestDateTime(t *testing.T) { 76 | assertSerialize(t, DateTime(2010, time.March, 30, 10, 15, 30), `CAST(? AS DATETIME)`, "2010-03-30 10:15:30") 77 | assertSerialize(t, DateTimeT(time.Now()), `CAST(? AS DATETIME)`) 78 | } 79 | 80 | func TestTimestamp(t *testing.T) { 81 | assertSerialize(t, Timestamp(2010, time.March, 30, 10, 15, 30), `TIMESTAMP(?)`, "2010-03-30 10:15:30") 82 | assertSerialize(t, TimestampT(time.Now()), `TIMESTAMP(?)`) 83 | } 84 | -------------------------------------------------------------------------------- /sqlite/expressions_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "testing" 6 | ) 7 | 8 | func TestRaw(t *testing.T) { 9 | assertSerialize(t, Raw("current_database()"), "(current_database())") 10 | assertDebugSerialize(t, Raw("current_database()"), "(current_database())") 11 | 12 | assertSerialize(t, Raw(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22}), 13 | "(? + table.colInt + ?)", 11, 22) 14 | assertDebugSerialize(t, Raw(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22}), 15 | "(11 + table.colInt + 22)") 16 | 17 | assertSerialize(t, 18 | Int(700).ADD(RawInt("#1 + table.colInt + #2", RawArgs{"#1": 11, "#2": 22})), 19 | "(? + (? + table.colInt + ?))", 20 | int64(700), 11, 22) 21 | assertDebugSerialize(t, 22 | Int(700).ADD(RawInt("#1 + table.colInt + #2", RawArgs{"#1": 11, "#2": 22})), 23 | "(700 + (11 + table.colInt + 22))") 24 | } 25 | 26 | func TestRawDuplicateArguments(t *testing.T) { 27 | assertSerialize(t, Raw(":arg + table.colInt + :arg", RawArgs{":arg": 11}), 28 | "(? + table.colInt + ?)", 11, 11) 29 | 30 | assertSerialize(t, Raw("#age + table.colInt + #year + #age + #year + 11", RawArgs{"#age": 11, "#year": 2000}), 31 | "(? + table.colInt + ? + ? + ? + 11)", 11, 2000, 11, 2000) 32 | 33 | assertSerialize(t, Raw("#1 + all_types.integer + #2 + #1 + #2 + #3 + #4", 34 | RawArgs{"#1": 11, "#2": 22, "#3": 33, "#4": 44}), 35 | `(? + all_types.integer + ? + ? + ? + ? + ?)`, 11, 22, 11, 22, 33, 44) 36 | } 37 | 38 | func TestRawInvalidArguments(t *testing.T) { 39 | defer func() { 40 | r := recover() 41 | require.Equal(t, "jet: named argument 'first_arg' does not appear in raw query", r) 42 | }() 43 | 44 | assertSerialize(t, Raw("table.colInt + :second_arg", RawArgs{"first_arg": 11}), "(table.colInt + ?)", 22) 45 | } 46 | 47 | func TestRawType(t *testing.T) { 48 | assertSerialize(t, RawBool("table.colInt < :float", RawArgs{":float": 11.22}).IS_FALSE(), 49 | "(table.colInt < ?) IS FALSE", 11.22) 50 | 51 | assertSerialize(t, RawFloat("table.colInt + &float", RawArgs{"&float": 11.22}).EQ(Float(3.14)), 52 | "((table.colInt + ?) = ?)", 11.22, 3.14) 53 | assertSerialize(t, RawString("table.colStr || str", RawArgs{"str": "doe"}).EQ(String("john doe")), 54 | "((table.colStr || ?) = ?)", "doe", "john doe") 55 | } 56 | -------------------------------------------------------------------------------- /sqlite/update_statement.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // UpdateStatement is interface of SQL UPDATE statement 6 | type UpdateStatement interface { 7 | jet.Statement 8 | 9 | SET(value interface{}, values ...interface{}) UpdateStatement 10 | MODEL(data interface{}) UpdateStatement 11 | 12 | FROM(tables ...ReadableTable) UpdateStatement 13 | WHERE(expression BoolExpression) UpdateStatement 14 | RETURNING(projections ...Projection) UpdateStatement 15 | } 16 | 17 | type updateStatementImpl struct { 18 | jet.SerializerStatement 19 | 20 | Update jet.ClauseUpdate 21 | From jet.ClauseFrom 22 | Set jet.SetClause 23 | SetNew jet.SetClauseNew 24 | Where jet.ClauseWhere 25 | Returning jet.ClauseReturning 26 | } 27 | 28 | func newUpdateStatement(table Table, columns []jet.Column) UpdateStatement { 29 | update := &updateStatementImpl{} 30 | update.SerializerStatement = jet.NewStatementImpl(Dialect, jet.UpdateStatementType, update, 31 | &update.Update, 32 | &update.Set, 33 | &update.SetNew, 34 | &update.From, 35 | &update.Where, 36 | &update.Returning) 37 | 38 | update.Update.Table = table 39 | update.Set.Columns = columns 40 | update.Where.Mandatory = true 41 | 42 | return update 43 | } 44 | 45 | func (u *updateStatementImpl) SET(value interface{}, values ...interface{}) UpdateStatement { 46 | columnAssigment, isColumnAssigment := value.(ColumnAssigment) 47 | 48 | if isColumnAssigment { 49 | u.SetNew = []ColumnAssigment{columnAssigment} 50 | for _, value := range values { 51 | u.SetNew = append(u.SetNew, value.(ColumnAssigment)) 52 | } 53 | } else { 54 | u.Set.Values = jet.UnwindRowFromValues(value, values) 55 | } 56 | 57 | return u 58 | } 59 | 60 | func (u *updateStatementImpl) MODEL(data interface{}) UpdateStatement { 61 | u.Set.Values = jet.UnwindRowFromModel(u.Set.Columns, data) 62 | return u 63 | } 64 | 65 | func (u *updateStatementImpl) FROM(tables ...ReadableTable) UpdateStatement { 66 | u.From.Tables = readableTablesToSerializerList(tables) 67 | return u 68 | } 69 | 70 | func (u *updateStatementImpl) WHERE(expression BoolExpression) UpdateStatement { 71 | u.Where.Condition = expression 72 | return u 73 | } 74 | 75 | func (u *updateStatementImpl) RETURNING(projections ...Projection) UpdateStatement { 76 | u.Returning.ProjectionList = projections 77 | return u 78 | } 79 | -------------------------------------------------------------------------------- /internal/jet/logger.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import ( 4 | "context" 5 | "runtime" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | // PrintableStatement is a statement which sql query can be logged 11 | type PrintableStatement interface { 12 | Sql() (query string, args []interface{}) 13 | DebugSql() (query string) 14 | } 15 | 16 | // LoggerFunc is a function user can implement to support automatic statement logging. 17 | type LoggerFunc func(ctx context.Context, statement PrintableStatement) 18 | 19 | var logger LoggerFunc 20 | 21 | // SetLoggerFunc sets automatic statement logging 22 | func SetLoggerFunc(loggerFunc LoggerFunc) { 23 | logger = loggerFunc 24 | } 25 | 26 | func callLogger(ctx context.Context, statement Statement) { 27 | if logger != nil { 28 | logger(ctx, statement) 29 | } 30 | } 31 | 32 | // QueryInfo contains information about executed query 33 | type QueryInfo struct { 34 | Statement PrintableStatement 35 | // Depending on how the statement is executed, RowsProcessed is: 36 | // - Number of rows returned for Query() and QueryContext() methods 37 | // - RowsAffected() for Exec() and ExecContext() methods 38 | // - Always 0 for Rows() method. 39 | RowsProcessed int64 40 | Duration time.Duration 41 | Err error 42 | } 43 | 44 | // QueryLoggerFunc is a function user can implement to retrieve more information about statement executed. 45 | type QueryLoggerFunc func(ctx context.Context, info QueryInfo) 46 | 47 | var queryLoggerFunc QueryLoggerFunc 48 | 49 | // SetQueryLogger sets automatic query logging function. 50 | func SetQueryLogger(loggerFunc QueryLoggerFunc) { 51 | queryLoggerFunc = loggerFunc 52 | } 53 | 54 | func callQueryLoggerFunc(ctx context.Context, info QueryInfo) { 55 | if queryLoggerFunc != nil { 56 | queryLoggerFunc(ctx, info) 57 | } 58 | } 59 | 60 | // Caller returns information about statement caller 61 | func (q QueryInfo) Caller() (file string, line int, function string) { 62 | skip := 4 63 | // depending on execution type (Query, QueryContext, Exec, ...) looped once or twice 64 | for { 65 | var pc uintptr 66 | var ok bool 67 | 68 | pc, file, line, ok = runtime.Caller(skip) 69 | if !ok { 70 | return 71 | } 72 | 73 | funcDetails := runtime.FuncForPC(pc) 74 | if !strings.Contains(funcDetails.Name(), "github.com/go-jet/jet/v2/internal") { 75 | function = funcDetails.Name() 76 | return 77 | } 78 | 79 | skip++ 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/mysql/cast_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/testutils" 5 | . "github.com/go-jet/jet/v2/mysql" 6 | . "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/table" 7 | "github.com/stretchr/testify/require" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestCast(t *testing.T) { 13 | 14 | query := SELECT( 15 | CAST(String("test")).AS("CHAR CHARACTER SET utf8").AS("result.AS1"), 16 | CAST(String("2011-02-02")).AS_DATE().AS("result.date1"), 17 | CAST(String("14:06:10")).AS_TIME().AS("result.time"), 18 | CAST(String("2011-02-02 14:06:10")).AS_DATETIME().AS("result.datetime"), 19 | 20 | CAST(Int(150)).AS_CHAR().AS("result.char1"), 21 | CAST(Int(150)).AS_CHAR(30).AS("result.char2"), 22 | 23 | CAST(Int(5).SUB(Int(10))).AS_SIGNED().AS("result.signed"), 24 | CAST(Int(5).ADD(Int(10))).AS_UNSIGNED().AS("result.unsigned"), 25 | CAST(String("Some text")).AS_BINARY().AS("result.binary"), 26 | ).FROM(AllTypes) 27 | 28 | testutils.AssertStatementSql(t, query, ` 29 | SELECT CAST(? AS CHAR CHARACTER SET utf8) AS "result.AS1", 30 | CAST(? AS DATE) AS "result.date1", 31 | CAST(? AS TIME) AS "result.time", 32 | CAST(? AS DATETIME) AS "result.datetime", 33 | CAST(? AS CHAR) AS "result.char1", 34 | CAST(? AS CHAR(30)) AS "result.char2", 35 | CAST((? - ?) AS SIGNED) AS "result.signed", 36 | CAST((? + ?) AS UNSIGNED) AS "result.unsigned", 37 | CAST(? AS BINARY) AS "result.binary" 38 | FROM test_sample.all_types; 39 | `, "test", "2011-02-02", "14:06:10", "2011-02-02 14:06:10", int64(150), int64(150), int64(5), 40 | int64(10), int64(5), int64(10), "Some text") 41 | 42 | type Result struct { 43 | As1 string 44 | Date1 time.Time 45 | Time time.Time 46 | DateTime time.Time 47 | Char1 string 48 | Char2 string 49 | Signed int 50 | Unsigned int 51 | Binary string 52 | } 53 | 54 | var dest Result 55 | 56 | err := query.Query(db, &dest) 57 | 58 | require.NoError(t, err) 59 | 60 | testutils.AssertDeepEqual(t, dest, Result{ 61 | As1: "test", 62 | Date1: *testutils.Date("2011-02-02"), 63 | Time: *testutils.TimeWithoutTimeZone("14:06:10"), 64 | DateTime: *testutils.TimestampWithoutTimeZone("2011-02-02 14:06:10", 0), 65 | Char1: "150", 66 | Char2: "150", 67 | Signed: -5, 68 | Unsigned: 15, 69 | Binary: "Some text", 70 | }) 71 | 72 | requireLogged(t, query) 73 | } 74 | -------------------------------------------------------------------------------- /sqlite/dialect_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBoolExpressionIS_DISTINCT_FROM(t *testing.T) { 8 | assertSerialize(t, table1ColBool.IS_DISTINCT_FROM(table2ColBool), "(table1.col_bool IS NOT table2.col_bool)") 9 | assertSerialize(t, table1ColBool.IS_DISTINCT_FROM(Bool(false)), "(table1.col_bool IS NOT ?)", false) 10 | } 11 | 12 | func TestBoolExpressionIS_NOT_DISTINCT_FROM(t *testing.T) { 13 | assertSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(table2ColBool), "(table1.col_bool IS table2.col_bool)") 14 | assertSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(Bool(false)), "(table1.col_bool IS ?)", false) 15 | } 16 | 17 | func TestBoolLiteral(t *testing.T) { 18 | assertSerialize(t, Bool(true), "?", true) 19 | assertSerialize(t, Bool(false), "?", false) 20 | } 21 | 22 | func TestIntegerExpressionDIV(t *testing.T) { 23 | assertSerialize(t, table1ColInt.DIV(table2ColInt), "(table1.col_int / table2.col_int)") 24 | assertSerialize(t, table1ColInt.DIV(Int(11)), "(table1.col_int / ?)", int64(11)) 25 | } 26 | 27 | func TestIntExpressionPOW(t *testing.T) { 28 | assertSerialize(t, table1ColInt.POW(table2ColInt), "POW(table1.col_int, table2.col_int)") 29 | assertSerialize(t, table1ColInt.POW(Int(11)), "POW(table1.col_int, ?)", int64(11)) 30 | } 31 | 32 | func TestIntExpressionBIT_XOR(t *testing.T) { 33 | assertSerialize(t, table1ColInt.BIT_XOR(table2ColInt), "((~(table1.col_int & table2.col_int))&(table1.col_int | table2.col_int))") 34 | assertSerialize(t, table1ColInt.BIT_XOR(Int(11)), "((~(table1.col_int & ?))&(table1.col_int | ?))", int64(11), int64(11)) 35 | } 36 | 37 | func TestExists(t *testing.T) { 38 | assertSerialize(t, EXISTS( 39 | table2. 40 | SELECT(Int(1)). 41 | WHERE(table1Col1.EQ(table2Col3)), 42 | ), 43 | `(EXISTS ( 44 | SELECT ? 45 | FROM db.table2 46 | WHERE table1.col1 = table2.col3 47 | ))`, int64(1)) 48 | } 49 | 50 | func TestString_REGEXP_LIKE_operator(t *testing.T) { 51 | assertSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "(table3.col2 REGEXP table2.col_str)") 52 | assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN")), "(table3.col2 REGEXP ?)", "JOHN") 53 | 54 | } 55 | 56 | func TestString_NOT_REGEXP_LIKE_operator(t *testing.T) { 57 | assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(table2ColStr), "(table3.col2 NOT REGEXP table2.col_str)") 58 | assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN")), "(table3.col2 NOT REGEXP ?)", "JOHN") 59 | } 60 | -------------------------------------------------------------------------------- /mysql/utils_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/jet" 5 | "github.com/go-jet/jet/v2/internal/testutils" 6 | "testing" 7 | ) 8 | 9 | var table1Col1 = IntegerColumn("col1") 10 | var table1ColBool = BoolColumn("col_bool") 11 | var table1ColInt = IntegerColumn("col_int") 12 | var table1ColFloat = FloatColumn("col_float") 13 | var table1ColString = StringColumn("col_string") 14 | var table1Col3 = IntegerColumn("col3") 15 | var table1ColTimestamp = TimestampColumn("col_timestamp") 16 | var table1ColDate = DateColumn("col_date") 17 | var table1ColTime = TimeColumn("col_time") 18 | 19 | var table1 = NewTable("db", "table1", "", table1Col1, table1ColInt, table1ColFloat, table1ColString, table1Col3, table1ColBool, table1ColDate, table1ColTimestamp, table1ColTime) 20 | 21 | var table2Col3 = IntegerColumn("col3") 22 | var table2Col4 = IntegerColumn("col4") 23 | var table2ColInt = IntegerColumn("col_int") 24 | var table2ColFloat = FloatColumn("col_float") 25 | var table2ColStr = StringColumn("col_str") 26 | var table2ColBool = BoolColumn("col_bool") 27 | var table2ColTimestamp = TimestampColumn("col_timestamp") 28 | var table2ColDate = DateColumn("col_date") 29 | 30 | var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColDate, table2ColTimestamp) 31 | 32 | var table3Col1 = IntegerColumn("col1") 33 | var table3ColInt = IntegerColumn("col_int") 34 | var table3StrCol = StringColumn("col2") 35 | var table3 = NewTable("db", "table3", "", table3Col1, table3ColInt, table3StrCol) 36 | 37 | func assertSerialize(t *testing.T, clause jet.Serializer, query string, args ...interface{}) { 38 | testutils.AssertSerialize(t, Dialect, clause, query, args...) 39 | } 40 | 41 | func assertDebugSerialize(t *testing.T, clause jet.Serializer, query string, args ...interface{}) { 42 | testutils.AssertDebugSerialize(t, Dialect, clause, query, args...) 43 | } 44 | 45 | func assertSerializeErr(t *testing.T, clause jet.Serializer, errString string) { 46 | testutils.AssertSerializeErr(t, Dialect, clause, errString) 47 | } 48 | 49 | func assertProjectionSerialize(t *testing.T, projection jet.Projection, query string, args ...interface{}) { 50 | testutils.AssertProjectionSerialize(t, Dialect, projection, query, args...) 51 | } 52 | 53 | var assertPanicErr = testutils.AssertPanicErr 54 | var assertStatementSql = testutils.AssertStatementSql 55 | var assertStatementSqlErr = testutils.AssertStatementSqlErr 56 | -------------------------------------------------------------------------------- /postgres/functions_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "github.com/lib/pq" 5 | "testing" 6 | ) 7 | 8 | func TestROW(t *testing.T) { 9 | assertSerialize(t, ROW(SELECT(Int(1))), `ROW(( 10 | SELECT $1 11 | ))`) 12 | assertSerialize(t, ROW(Int(1), SELECT(Int(2)), Float(11.11)), `ROW($1, ( 13 | SELECT $2 14 | ), $3)`) 15 | } 16 | 17 | func TestDATE_TRUNC(t *testing.T) { 18 | assertSerialize(t, DATE_TRUNC(YEAR, NOW()), "DATE_TRUNC('YEAR', NOW())") 19 | assertSerialize( 20 | t, 21 | DATE_TRUNC(DAY, NOW().ADD(INTERVAL(1, HOUR)), "Australia/Sydney"), 22 | "DATE_TRUNC('DAY', NOW() + INTERVAL '1 HOUR', 'Australia/Sydney')", 23 | ) 24 | } 25 | 26 | func TestGENERATE_SERIES(t *testing.T) { 27 | assertSerialize( 28 | t, 29 | GENERATE_SERIES(NOW(), NOW().ADD(INTERVAL(10, DAY))), 30 | "GENERATE_SERIES(NOW(), NOW() + INTERVAL '10 DAY')", 31 | ) 32 | assertSerialize( 33 | t, 34 | GENERATE_SERIES(NOW(), NOW().ADD(INTERVAL(10, DAY)), INTERVAL(2, DAY)), 35 | "GENERATE_SERIES(NOW(), NOW() + INTERVAL '10 DAY', INTERVAL '2 DAY')", 36 | ) 37 | } 38 | 39 | func TestArrayFunctions(t *testing.T) { 40 | 41 | intArray := Int32Array(1, 2, 3) 42 | stringArrayColumn := StringArrayColumn("str_arr_col") 43 | 44 | assertSerialize(t, ARRAY_LOWER(intArray), "ARRAY_LOWER($1::integer[])", pq.Int32Array{1, 2, 3}) 45 | assertSerialize(t, ARRAY_DIMS(stringArrayColumn).EQ(String("[1,1]")), 46 | "(ARRAY_DIMS(str_arr_col) = $1::text)", 47 | "[1,1]", 48 | ) 49 | assertSerialize(t, ARRAY_NDIMS(stringArrayColumn).EQ(Int(1)), "(ARRAY_NDIMS(str_arr_col) = $1)", int64(1)) 50 | assertSerialize(t, ARRAY_REVERSE(stringArrayColumn), "ARRAY_REVERSE(str_arr_col)") 51 | assertSerialize(t, ARRAY_SAMPLE(stringArrayColumn, Int(2)).AT(Int(1)).EQ(String("john")), 52 | "(ARRAY_SAMPLE(str_arr_col, $1)[$2] = $3::text)", 53 | int64(2), int64(1), "john", 54 | ) 55 | 56 | assertSerialize(t, ARRAY_SHUFFLE(intArray).AT(Int(2)).EQ(Int(33)), 57 | "(ARRAY_SHUFFLE($1::integer[])[$2] = $3)", 58 | pq.Int32Array{1, 2, 3}, int64(2), int64(33), 59 | ) 60 | 61 | assertSerialize(t, ARRAY_SORT(stringArrayColumn, Bool(true)), 62 | "ARRAY_SORT(str_arr_col, $1::boolean)", 63 | true, 64 | ) 65 | 66 | assertSerialize(t, ARRAY_SORT(stringArrayColumn, Bool(true), Bool(false)), 67 | "ARRAY_SORT(str_arr_col, $1::boolean, $2::boolean)", 68 | true, false, 69 | ) 70 | 71 | assertSerialize(t, TRIM_ARRAY(stringArrayColumn, Int(6)), 72 | "TRIM_ARRAY(str_arr_col, $1)", 73 | int64(6), 74 | ) 75 | } 76 | -------------------------------------------------------------------------------- /sqlite/utils_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/jet" 5 | "github.com/go-jet/jet/v2/internal/testutils" 6 | "testing" 7 | ) 8 | 9 | var table1Col1 = IntegerColumn("col1") 10 | var table1ColBool = BoolColumn("col_bool") 11 | var table1ColInt = IntegerColumn("col_int") 12 | var table1ColFloat = FloatColumn("col_float") 13 | var table1ColString = StringColumn("col_string") 14 | var table1Col3 = IntegerColumn("col3") 15 | var table1ColTimestamp = TimestampColumn("col_timestamp") 16 | var table1ColDate = DateColumn("col_date") 17 | var table1ColTime = TimeColumn("col_time") 18 | 19 | var table1 = NewTable("db", "table1", "", table1Col1, table1ColInt, table1ColFloat, table1ColString, table1Col3, table1ColBool, table1ColDate, table1ColTimestamp, table1ColTime) 20 | 21 | var table2Col3 = IntegerColumn("col3") 22 | var table2Col4 = IntegerColumn("col4") 23 | var table2ColInt = IntegerColumn("col_int") 24 | var table2ColFloat = FloatColumn("col_float") 25 | var table2ColStr = StringColumn("col_str") 26 | var table2ColBool = BoolColumn("col_bool") 27 | var table2ColTimestamp = TimestampColumn("col_timestamp") 28 | var table2ColDate = DateColumn("col_date") 29 | 30 | var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColDate, table2ColTimestamp) 31 | 32 | var table3Col1 = IntegerColumn("col1") 33 | var table3ColInt = IntegerColumn("col_int") 34 | var table3StrCol = StringColumn("col2") 35 | var table3 = NewTable("db", "table3", "", table3Col1, table3ColInt, table3StrCol) 36 | 37 | func assertSerialize(t *testing.T, clause jet.Serializer, query string, args ...interface{}) { 38 | testutils.AssertSerialize(t, Dialect, clause, query, args...) 39 | } 40 | 41 | func assertDebugSerialize(t *testing.T, clause jet.Serializer, query string, args ...interface{}) { 42 | testutils.AssertDebugSerialize(t, Dialect, clause, query, args...) 43 | } 44 | 45 | func assertSerializeErr(t *testing.T, clause jet.Serializer, errString string) { 46 | testutils.AssertSerializeErr(t, Dialect, clause, errString) 47 | } 48 | 49 | func assertProjectionSerialize(t *testing.T, projection jet.Projection, query string, args ...interface{}) { 50 | testutils.AssertProjectionSerialize(t, Dialect, projection, query, args...) 51 | } 52 | 53 | var assertPanicErr = testutils.AssertPanicErr 54 | var assertStatementSql = testutils.AssertStatementSql 55 | var assertStatementSqlErr = testutils.AssertStatementSqlErr 56 | -------------------------------------------------------------------------------- /mysql/update_statement.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // UpdateStatement is interface of SQL UPDATE statement 6 | type UpdateStatement interface { 7 | jet.Statement 8 | 9 | OPTIMIZER_HINTS(hints ...OptimizerHint) UpdateStatement 10 | 11 | SET(value interface{}, values ...interface{}) UpdateStatement 12 | MODEL(data interface{}) UpdateStatement 13 | 14 | WHERE(expression BoolExpression) UpdateStatement 15 | LIMIT(limit int64) UpdateStatement 16 | } 17 | 18 | type updateStatementImpl struct { 19 | jet.SerializerStatement 20 | 21 | Update jet.ClauseUpdate 22 | Set jet.SetClause 23 | SetNew jet.SetClauseNew 24 | Where jet.ClauseWhere 25 | Limit jet.ClauseLimit 26 | } 27 | 28 | func newUpdateStatement(table Table, columns []jet.Column) UpdateStatement { 29 | update := &updateStatementImpl{} 30 | update.SerializerStatement = jet.NewStatementImpl(Dialect, jet.UpdateStatementType, update, 31 | &update.Update, 32 | &update.Set, 33 | &update.SetNew, 34 | &update.Where, 35 | &update.Limit) 36 | 37 | update.Update.Table = table 38 | update.Set.Columns = columns 39 | update.Where.Mandatory = true 40 | update.Limit.Count = -1 // Initialize to -1 to indicate no LIMIT 41 | 42 | return update 43 | } 44 | 45 | func (u *updateStatementImpl) OPTIMIZER_HINTS(hints ...OptimizerHint) UpdateStatement { 46 | u.Update.OptimizerHints = hints 47 | return u 48 | } 49 | 50 | func (u *updateStatementImpl) SET(value interface{}, values ...interface{}) UpdateStatement { 51 | columnAssigment, isColumnAssigment := value.(ColumnAssigment) 52 | 53 | if isColumnAssigment { 54 | u.SetNew = []ColumnAssigment{columnAssigment} 55 | for _, value := range values { 56 | u.SetNew = append(u.SetNew, value.(ColumnAssigment)) 57 | } 58 | } else { 59 | u.Set.Values = jet.UnwindRowFromValues(value, values) 60 | } 61 | 62 | return u 63 | } 64 | 65 | func (u *updateStatementImpl) MODEL(data interface{}) UpdateStatement { 66 | u.Set.Values = jet.UnwindRowFromModel(u.Set.Columns, data) 67 | return u 68 | } 69 | 70 | func (u *updateStatementImpl) WHERE(expression BoolExpression) UpdateStatement { 71 | u.Where.Condition = expression 72 | return u 73 | } 74 | 75 | func (u *updateStatementImpl) LIMIT(limit int64) UpdateStatement { 76 | if _, isJoinTable := u.Update.Table.(*joinTable); isJoinTable { 77 | panic("jet: MySQL does not support LIMIT with multi-table UPDATE statements") 78 | } 79 | u.Limit.Count = limit 80 | return u 81 | } 82 | -------------------------------------------------------------------------------- /internal/jet/order_set_aggregate_functions.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | // MODE computes the most frequent value of the aggregated argument 4 | func MODE() *OrderSetAggregateFunc { 5 | return newOrderSetAggregateFunction("MODE", nil) 6 | } 7 | 8 | // PERCENTILE_CONT computes a value corresponding to the specified fraction within the ordered set of 9 | // aggregated argument values. This will interpolate between adjacent input items if needed. 10 | func PERCENTILE_CONT(fraction FloatExpression) *OrderSetAggregateFunc { 11 | return newOrderSetAggregateFunction("PERCENTILE_CONT", fraction) 12 | } 13 | 14 | // PERCENTILE_DISC computes the first value within the ordered set of aggregated argument values whose position 15 | // in the ordering equals or exceeds the specified fraction. The aggregated argument must be of a sortable type. 16 | func PERCENTILE_DISC(fraction FloatExpression) *OrderSetAggregateFunc { 17 | return newOrderSetAggregateFunction("PERCENTILE_DISC", fraction) 18 | } 19 | 20 | // OrderSetAggregateFunc implementation of order set aggregate function 21 | type OrderSetAggregateFunc struct { 22 | name string 23 | fraction FloatExpression 24 | orderBy Window 25 | } 26 | 27 | func newOrderSetAggregateFunction(name string, fraction FloatExpression) *OrderSetAggregateFunc { 28 | return &OrderSetAggregateFunc{ 29 | name: name, 30 | fraction: fraction, 31 | } 32 | } 33 | 34 | // WITHIN_GROUP_ORDER_BY specifies ordered set of aggregated argument values 35 | func (p *OrderSetAggregateFunc) WITHIN_GROUP_ORDER_BY(orderBy OrderByClause) Expression { 36 | p.orderBy = ORDER_BY(orderBy) 37 | return newOrderSetAggregateFuncExpression(*p) 38 | } 39 | 40 | func newOrderSetAggregateFuncExpression(aggFunc OrderSetAggregateFunc) *orderSetAggregateFuncExpression { 41 | ret := &orderSetAggregateFuncExpression{ 42 | OrderSetAggregateFunc: aggFunc, 43 | } 44 | 45 | ret.ExpressionInterfaceImpl.Root = ret 46 | 47 | return ret 48 | } 49 | 50 | type orderSetAggregateFuncExpression struct { 51 | ExpressionInterfaceImpl 52 | OrderSetAggregateFunc 53 | } 54 | 55 | func (p *orderSetAggregateFuncExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { 56 | out.WriteString(p.name) 57 | 58 | if p.fraction != nil { 59 | wrap(p.fraction).serialize(statement, out, FallTrough(options)...) 60 | } else { 61 | wrap().serialize(statement, out, FallTrough(options)...) 62 | } 63 | out.WriteString("WITHIN GROUP") 64 | p.orderBy.serialize(statement, out) 65 | } 66 | -------------------------------------------------------------------------------- /sqlite/literal.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/jet" 5 | "time" 6 | ) 7 | 8 | // Keywords 9 | var ( 10 | STAR = jet.STAR 11 | NULL = jet.NULL 12 | ) 13 | 14 | // Bool creates new bool literal expression 15 | var Bool = jet.Bool 16 | 17 | // Int is constructor for 64 bit signed integer expressions literals. 18 | var Int = jet.Int 19 | 20 | // Int8 is constructor for 8 bit signed integer expressions literals. 21 | var Int8 = jet.Int8 22 | 23 | // Int16 is constructor for 16 bit signed integer expressions literals. 24 | var Int16 = jet.Int16 25 | 26 | // Int32 is constructor for 32 bit signed integer expressions literals. 27 | var Int32 = jet.Int32 28 | 29 | // Int64 is constructor for 64 bit signed integer expressions literals. 30 | var Int64 = jet.Int 31 | 32 | // Uint8 is constructor for 8 bit unsigned integer expressions literals. 33 | var Uint8 = jet.Uint8 34 | 35 | // Uint16 is constructor for 16 bit unsigned integer expressions literals. 36 | var Uint16 = jet.Uint16 37 | 38 | // Uint32 is constructor for 32 bit unsigned integer expressions literals. 39 | var Uint32 = jet.Uint32 40 | 41 | // Uint64 is constructor for 64 bit unsigned integer expressions literals. 42 | var Uint64 = jet.Uint64 43 | 44 | // Float creates new float literal expression from float64 value 45 | var Float = jet.Float 46 | 47 | // Decimal creates new float literal expression from string value 48 | var Decimal = jet.Decimal 49 | 50 | // String creates new string literal expression 51 | var String = jet.String 52 | 53 | // Blob creates new blob literal expression 54 | func Blob(data []byte) BlobExpression { 55 | return BlobExp(jet.Literal(data)) 56 | } 57 | 58 | // UUID is a helper function to create string literal expression from uuid object 59 | // value can be any uuid type with a String method 60 | var UUID = jet.UUID 61 | 62 | // Date creates new date literal expression 63 | func Date(year int, month time.Month, day int) DateExpression { 64 | return DATE(jet.Date(year, month, day)) 65 | } 66 | 67 | // Time creates new time literal expression 68 | func Time(hour, minute, second int, nanoseconds ...time.Duration) TimeExpression { 69 | return TIME(jet.Time(hour, minute, second, nanoseconds...)) 70 | } 71 | 72 | // DateTime creates new datetime(timestamp) literal expression 73 | func DateTime(year int, month time.Month, day, hour, minute, second int, nanoseconds ...time.Duration) DateTimeExpression { 74 | return DATETIME(jet.Timestamp(year, month, day, hour, minute, second, nanoseconds...)) 75 | } 76 | -------------------------------------------------------------------------------- /sqlite/on_conflict_clause.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/jet" 5 | "github.com/go-jet/jet/v2/internal/utils/is" 6 | ) 7 | 8 | type onConflict interface { 9 | WHERE(indexPredicate BoolExpression) conflictTarget 10 | conflictTarget 11 | } 12 | 13 | type conflictTarget interface { 14 | DO_NOTHING() InsertStatement 15 | DO_UPDATE(action conflictAction) InsertStatement 16 | } 17 | 18 | type onConflictClause struct { 19 | insertStatement InsertStatement 20 | indexExpressions []jet.ColumnExpression 21 | whereClause jet.ClauseWhere 22 | do jet.Serializer 23 | } 24 | 25 | func (o *onConflictClause) WHERE(indexPredicate BoolExpression) conflictTarget { 26 | o.whereClause.Condition = indexPredicate 27 | return o 28 | } 29 | 30 | func (o *onConflictClause) DO_NOTHING() InsertStatement { 31 | o.do = jet.Keyword("DO NOTHING") 32 | return o.insertStatement 33 | } 34 | 35 | func (o *onConflictClause) DO_UPDATE(action conflictAction) InsertStatement { 36 | o.do = action 37 | return o.insertStatement 38 | } 39 | 40 | func (o *onConflictClause) Serialize(statementType jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) { 41 | if is.Nil(o.do) { 42 | return 43 | } 44 | 45 | out.NewLine() 46 | out.WriteString("ON CONFLICT") 47 | if len(o.indexExpressions) > 0 { 48 | out.WriteString("(") 49 | jet.SerializeColumnExpressions(o.indexExpressions, statementType, out, jet.ShortName) 50 | out.WriteString(")") 51 | } 52 | 53 | o.whereClause.Serialize(statementType, out, jet.SkipNewLine, jet.ShortName) 54 | 55 | out.IncreaseIdent(7) 56 | jet.Serialize(o.do, statementType, out) 57 | out.DecreaseIdent(7) 58 | } 59 | 60 | type conflictAction interface { 61 | jet.Serializer 62 | WHERE(condition BoolExpression) conflictAction 63 | } 64 | 65 | // SET creates conflict action for ON_CONFLICT clause 66 | func SET(assigments ...ColumnAssigment) conflictAction { 67 | conflictAction := updateConflictActionImpl{} 68 | conflictAction.doUpdate = jet.KeywordClause{Keyword: "DO UPDATE"} 69 | conflictAction.Serializer = jet.NewSerializerClauseImpl(&conflictAction.doUpdate, &conflictAction.set, &conflictAction.where) 70 | conflictAction.set = assigments 71 | return &conflictAction 72 | } 73 | 74 | type updateConflictActionImpl struct { 75 | jet.Serializer 76 | 77 | doUpdate jet.KeywordClause 78 | set jet.SetClauseNew 79 | where jet.ClauseWhere 80 | } 81 | 82 | func (u *updateConflictActionImpl) WHERE(condition BoolExpression) conflictAction { 83 | u.where.Condition = condition 84 | return u 85 | } 86 | -------------------------------------------------------------------------------- /internal/jet/order_by_clause.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | // OrderByClause interface 4 | type OrderByClause interface { 5 | // NULLS_FIRST specifies sort where null values appear before all non-null values. 6 | // For some dialects(mysql,mariadb), which do not support NULL_FIRST, NULL_FIRST is simulated 7 | // with additional IS_NOT_NULL expression. 8 | // For instance, 9 | // Rental.ReturnDate.DESC().NULLS_FIRST() 10 | // would translate to, 11 | // rental.return_date IS NOT NULL, rental.return_date DESC 12 | NULLS_FIRST() OrderByClause 13 | 14 | // NULLS_LAST specifies sort where null values appear after all non-null values. 15 | // For some dialects(mysql,mariadb), which do not support NULLS_LAST, NULLS_LAST is simulated 16 | // with additional IS_NULL expression. 17 | // For instance, 18 | // Rental.ReturnDate.ASC().NULLS_LAST() 19 | // would translate to, 20 | // rental.return_date IS NULL, rental.return_date ASC 21 | NULLS_LAST() OrderByClause 22 | 23 | serializeForOrderBy(statement StatementType, out *SQLBuilder) 24 | } 25 | 26 | type orderByClauseImpl struct { 27 | expression Expression 28 | ascending *bool 29 | nullsFirst *bool 30 | } 31 | 32 | func (ord *orderByClauseImpl) NULLS_FIRST() OrderByClause { 33 | nullsFirst := true 34 | ord.nullsFirst = &nullsFirst 35 | return ord 36 | } 37 | func (ord *orderByClauseImpl) NULLS_LAST() OrderByClause { 38 | nullsFirst := false 39 | ord.nullsFirst = &nullsFirst 40 | return ord 41 | } 42 | 43 | func (ord *orderByClauseImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) { 44 | customSerializer := out.Dialect.SerializeOrderBy() 45 | if customSerializer != nil { 46 | customSerializer(ord.expression, ord.ascending, ord.nullsFirst)(statement, out) 47 | return 48 | } 49 | 50 | if ord.expression == nil { 51 | panic("jet: nil expression in ORDER BY clause") 52 | } 53 | 54 | ord.expression.serializeForOrderBy(statement, out) 55 | 56 | if ord.ascending != nil { 57 | if *ord.ascending { 58 | out.WriteString("ASC") 59 | } else { 60 | out.WriteString("DESC") 61 | } 62 | } 63 | 64 | if ord.nullsFirst != nil { 65 | if *ord.nullsFirst { 66 | out.WriteString("NULLS FIRST") 67 | } else { 68 | out.WriteString("NULLS LAST") 69 | } 70 | } 71 | } 72 | 73 | func newOrderByAscending(expression Expression, ascending bool) OrderByClause { 74 | return &orderByClauseImpl{expression: expression, ascending: &ascending} 75 | } 76 | 77 | func newOrderByNullsFirst(expression Expression, nullsFirst bool) OrderByClause { 78 | return &orderByClauseImpl{expression: expression, nullsFirst: &nullsFirst} 79 | } 80 | -------------------------------------------------------------------------------- /internal/jet/time_expression_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | var timeVar = Time(10, 20, 0, 0) 9 | 10 | func TestTimeExpressionEQ(t *testing.T) { 11 | assertClauseSerialize(t, table1ColTime.EQ(table2ColTime), "(table1.col_time = table2.col_time)") 12 | assertClauseSerialize(t, table1ColTime.EQ(timeVar), "(table1.col_time = $1)", "10:20:00") 13 | } 14 | 15 | func TestTimeExpressionNOT_EQ(t *testing.T) { 16 | assertClauseSerialize(t, table1ColTime.NOT_EQ(table2ColTime), "(table1.col_time != table2.col_time)") 17 | assertClauseSerialize(t, table1ColTime.NOT_EQ(timeVar), "(table1.col_time != $1)", "10:20:00") 18 | } 19 | 20 | func TestTimeExpressionIS_DISTINCT_FROM(t *testing.T) { 21 | assertClauseSerialize(t, table1ColTime.IS_DISTINCT_FROM(table2ColTime), "(table1.col_time IS DISTINCT FROM table2.col_time)") 22 | assertClauseSerialize(t, table1ColTime.IS_DISTINCT_FROM(timeVar), "(table1.col_time IS DISTINCT FROM $1)", "10:20:00") 23 | } 24 | 25 | func TestTimeExpressionIS_NOT_DISTINCT_FROM(t *testing.T) { 26 | assertClauseSerialize(t, table1ColTime.IS_NOT_DISTINCT_FROM(table2ColTime), "(table1.col_time IS NOT DISTINCT FROM table2.col_time)") 27 | assertClauseSerialize(t, table1ColTime.IS_NOT_DISTINCT_FROM(timeVar), "(table1.col_time IS NOT DISTINCT FROM $1)", "10:20:00") 28 | } 29 | 30 | func TestTimeExpressionLT(t *testing.T) { 31 | assertClauseSerialize(t, table1ColTime.LT(table2ColTime), "(table1.col_time < table2.col_time)") 32 | assertClauseSerialize(t, table1ColTime.LT(timeVar), "(table1.col_time < $1)", "10:20:00") 33 | } 34 | 35 | func TestTimeExpressionLT_EQ(t *testing.T) { 36 | assertClauseSerialize(t, table1ColTime.LT_EQ(table2ColTime), "(table1.col_time <= table2.col_time)") 37 | assertClauseSerialize(t, table1ColTime.LT_EQ(timeVar), "(table1.col_time <= $1)", "10:20:00") 38 | } 39 | 40 | func TestTimeExpressionGT(t *testing.T) { 41 | assertClauseSerialize(t, table1ColTime.GT(table2ColTime), "(table1.col_time > table2.col_time)") 42 | assertClauseSerialize(t, table1ColTime.GT(timeVar), "(table1.col_time > $1)", "10:20:00") 43 | } 44 | 45 | func TestTimeExpressionGT_EQ(t *testing.T) { 46 | assertClauseSerialize(t, table1ColTime.GT_EQ(table2ColTime), "(table1.col_time >= table2.col_time)") 47 | assertClauseSerialize(t, table1ColTime.GT_EQ(timeVar), "(table1.col_time >= $1)", "10:20:00") 48 | } 49 | 50 | func TestTimeExp(t *testing.T) { 51 | assertClauseSerialize(t, TimeExp(table1ColFloat), "table1.col_float") 52 | assertClauseSerialize(t, TimeExp(table1ColFloat).LT(Time(1, 1, 1, 1*time.Millisecond)), 53 | "(table1.col_float < $1)", string("01:01:01.001")) 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '36 17 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v2 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | -------------------------------------------------------------------------------- /mysql/select_json.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/jet" 5 | ) 6 | 7 | // SelectJsonStatement is an interface for MySQL statements that generate JSON on the server. 8 | type SelectJsonStatement interface { 9 | Statement 10 | jet.Serializer 11 | 12 | AS(alias string) Projection 13 | 14 | FROM(table ReadableTable) SelectJsonStatement 15 | WHERE(condition BoolExpression) SelectJsonStatement 16 | ORDER_BY(orderByClauses ...OrderByClause) SelectJsonStatement 17 | LIMIT(limit int64) SelectJsonStatement 18 | OFFSET(offset int64) SelectJsonStatement 19 | } 20 | 21 | // SELECT_JSON_ARR creates a new SelectJsonStatement with a list of projections. 22 | func SELECT_JSON_ARR(projections ...Projection) SelectJsonStatement { 23 | return newSelectStatementJson(projections, jet.SelectJsonArrStatementType) 24 | } 25 | 26 | // SELECT_JSON_OBJ creates a new SelectJsonStatement with a list of projections. 27 | func SELECT_JSON_OBJ(projections ...Projection) SelectJsonStatement { 28 | return newSelectStatementJson(projections, jet.SelectJsonObjStatementType) 29 | } 30 | 31 | type selectJsonStatement struct { 32 | *selectStatementImpl 33 | } 34 | 35 | func newSelectStatementJson(projections []Projection, statementType jet.StatementType) SelectJsonStatement { 36 | newSelect := &selectJsonStatement{ 37 | selectStatementImpl: newSelectStatement(statementType, nil, nil), 38 | } 39 | 40 | newSelect.Select.ProjectionList = ProjectionList{constructJsonFunc(projections, statementType).AS("json")} 41 | 42 | return newSelect 43 | } 44 | 45 | func constructJsonFunc(projections []Projection, statementType jet.StatementType) Expression { 46 | jsonObj := Func("JSON_OBJECT", CustomExpression(jet.JsonObjProjectionList(projections))) 47 | 48 | if statementType == jet.SelectJsonArrStatementType { 49 | return Func("JSON_ARRAYAGG", jsonObj) 50 | } 51 | 52 | return jsonObj 53 | } 54 | 55 | func (s *selectJsonStatement) FROM(table ReadableTable) SelectJsonStatement { 56 | s.From.Tables = []jet.Serializer{table} 57 | 58 | return s 59 | } 60 | 61 | func (s *selectJsonStatement) WHERE(condition BoolExpression) SelectJsonStatement { 62 | s.Where.Condition = condition 63 | return s 64 | } 65 | 66 | func (s *selectJsonStatement) ORDER_BY(orderByClauses ...OrderByClause) SelectJsonStatement { 67 | s.OrderBy.List = orderByClauses 68 | return s 69 | } 70 | 71 | func (s *selectJsonStatement) LIMIT(limit int64) SelectJsonStatement { 72 | s.Limit.Count = limit 73 | return s 74 | } 75 | 76 | func (s *selectJsonStatement) OFFSET(offset int64) SelectJsonStatement { 77 | s.Offset.Count = Int(offset) 78 | return s 79 | } 80 | -------------------------------------------------------------------------------- /tests/sqlite/delete_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "context" 5 | "github.com/go-jet/jet/v2/qrm" 6 | "testing" 7 | "time" 8 | 9 | "github.com/go-jet/jet/v2/internal/testutils" 10 | . "github.com/go-jet/jet/v2/sqlite" 11 | "github.com/go-jet/jet/v2/tests/.gentestdata/sqlite/test_sample/model" 12 | . "github.com/go-jet/jet/v2/tests/.gentestdata/sqlite/test_sample/table" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func TestDelete_WHERE_RETURNING(t *testing.T) { 17 | tx := beginSampleDBTx(t) 18 | defer tx.Rollback() 19 | 20 | var expectedSQL = ` 21 | DELETE FROM link 22 | WHERE link.name IN ('Bing', 'Yahoo') 23 | RETURNING link.id AS "link.id", 24 | link.url AS "link.url", 25 | link.name AS "link.name", 26 | link.description AS "link.description"; 27 | ` 28 | deleteStmt := Link.DELETE(). 29 | WHERE(Link.Name.IN(String("Bing"), String("Yahoo"))). 30 | RETURNING(Link.AllColumns) 31 | 32 | testutils.AssertDebugStatementSql(t, deleteStmt, expectedSQL, "Bing", "Yahoo") 33 | var dest []model.Link 34 | err := deleteStmt.Query(tx, &dest) 35 | require.NoError(t, err) 36 | require.Len(t, dest, 2) 37 | requireLogged(t, deleteStmt) 38 | } 39 | 40 | func TestDeleteWithWhereOrderByLimit(t *testing.T) { 41 | t.SkipNow() // Until https://github.com/mattn/go-sqlite3/pull/802 is fixed 42 | tx := beginSampleDBTx(t) 43 | defer tx.Rollback() 44 | 45 | sampleDB.Stats() 46 | 47 | var expectedSQL = ` 48 | DELETE FROM link 49 | WHERE link.name IN ('Bing', 'Yahoo') 50 | ORDER BY link.name 51 | LIMIT 1; 52 | ` 53 | deleteStmt := Link.DELETE(). 54 | WHERE(Link.Name.IN(String("Bing"), String("Yahoo"))). 55 | ORDER_BY(Link.Name). 56 | LIMIT(1) 57 | 58 | testutils.AssertDebugStatementSql(t, deleteStmt, expectedSQL, "Bing", "Yahoo", int64(1)) 59 | testutils.AssertExec(t, deleteStmt, tx, 1) 60 | requireLogged(t, deleteStmt) 61 | } 62 | 63 | func TestDeleteContextDeadlineExceeded(t *testing.T) { 64 | 65 | deleteStmt := Link. 66 | DELETE(). 67 | WHERE(Link.Name.IN(String("Bing"), String("Yahoo"))) 68 | 69 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Microsecond) 70 | defer cancel() 71 | 72 | time.Sleep(20 * time.Millisecond) 73 | 74 | testutils.ExecuteInTxAndRollback(t, sampleDB, func(tx qrm.DB) { 75 | var dest []model.Link 76 | err := deleteStmt.QueryContext(ctx, tx, &dest) 77 | require.Error(t, err, "context deadline exceeded") 78 | }) 79 | 80 | testutils.ExecuteInTxAndRollback(t, sampleDB, func(tx qrm.DB) { 81 | _, err := deleteStmt.ExecContext(ctx, tx) 82 | require.Error(t, err, "context deadline exceeded") 83 | }) 84 | 85 | requireLogged(t, deleteStmt) 86 | } 87 | -------------------------------------------------------------------------------- /internal/jet/sql_builder_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/google/uuid" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestArgToString(t *testing.T) { 12 | s := &SQLBuilder{Dialect: defaultDialect, Debug: true} 13 | 14 | require.Equal(t, s.argToString(true), "TRUE") 15 | require.Equal(t, s.argToString(false), "FALSE") 16 | 17 | require.Equal(t, s.argToString(int(-32)), "-32") 18 | require.Equal(t, s.argToString(uint(32)), "32") 19 | require.Equal(t, s.argToString(int8(-43)), "-43") 20 | require.Equal(t, s.argToString(uint8(43)), "43") 21 | require.Equal(t, s.argToString(int16(-54)), "-54") 22 | require.Equal(t, s.argToString(uint16(54)), "54") 23 | require.Equal(t, s.argToString(int32(-65)), "-65") 24 | require.Equal(t, s.argToString(uint32(65)), "65") 25 | require.Equal(t, s.argToString(int64(-64)), "-64") 26 | require.Equal(t, s.argToString(uint64(64)), "64") 27 | require.Equal(t, s.argToString(float32(2.0)), "2") 28 | require.Equal(t, s.argToString(float64(1.11)), "1.11") 29 | 30 | require.Equal(t, s.argToString("john"), "'john'") 31 | require.Equal(t, s.argToString("It's text"), "'It''s text'") 32 | require.Equal(t, s.argToString([]byte("john")), "'john'") 33 | require.Equal(t, s.argToString(uuid.MustParse("b68dbff4-a87d-11e9-a7f2-98ded00c39c6")), "'b68dbff4-a87d-11e9-a7f2-98ded00c39c6'") 34 | 35 | time, err := time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", "Mon Jan 2 15:04:05 -0700 MST 2006") 36 | require.NoError(t, err) 37 | require.Equal(t, s.argToString(time), "'2006-01-02 15:04:05-07:00'") 38 | 39 | func() { 40 | defer func() { 41 | require.Equal(t, recover().(string), "jet: map[string]bool type can not be used as SQL query parameter") 42 | }() 43 | 44 | s.argToString(map[string]bool{}) 45 | }() 46 | } 47 | 48 | func TestFallTrough(t *testing.T) { 49 | require.Equal(t, FallTrough([]SerializeOption{ShortName}), []SerializeOption{ShortName}) 50 | require.Equal(t, FallTrough([]SerializeOption{SkipNewLine}), []SerializeOption(nil)) 51 | require.Equal(t, FallTrough([]SerializeOption{ShortName, SkipNewLine}), []SerializeOption{ShortName}) 52 | } 53 | 54 | func TestShouldQuote(t *testing.T) { 55 | require.Equal(t, shouldQuoteIdentifier("123"), true) 56 | require.Equal(t, shouldQuoteIdentifier("123.235"), true) 57 | require.Equal(t, shouldQuoteIdentifier("abc123"), false) 58 | require.Equal(t, shouldQuoteIdentifier("abc.123"), true) 59 | require.Equal(t, shouldQuoteIdentifier("abc_123"), false) 60 | require.Equal(t, shouldQuoteIdentifier("Abc_123"), true) 61 | require.Equal(t, shouldQuoteIdentifier("DŽƜĐǶ"), true) 62 | require.Equal(t, shouldQuoteIdentifier("1test"), true) 63 | } 64 | -------------------------------------------------------------------------------- /internal/utils/dbidentifier/dbidentifier.go: -------------------------------------------------------------------------------- 1 | package dbidentifier 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/3rdparty/snaker" 5 | "strings" 6 | "unicode" 7 | ) 8 | 9 | // ToGoIdentifier converts database identifier to Go identifier. 10 | func ToGoIdentifier(databaseIdentifier string) string { 11 | return snaker.SnakeToCamel(replaceInvalidChars(databaseIdentifier)) 12 | } 13 | 14 | // ToGoFileName converts database identifier to Go file name. 15 | func ToGoFileName(databaseIdentifier string) string { 16 | return strings.ToLower(replaceInvalidChars(databaseIdentifier)) 17 | } 18 | 19 | func replaceInvalidChars(identifier string) string { 20 | increase, needs := needsCharReplacement(identifier) 21 | 22 | if !needs { 23 | return identifier 24 | } 25 | 26 | var b strings.Builder 27 | 28 | b.Grow(len(identifier) + increase) 29 | 30 | for _, c := range identifier { 31 | switch { 32 | case unicode.IsSpace(c): 33 | b.WriteByte('_') 34 | case unicode.IsControl(c): 35 | continue 36 | default: 37 | replacement, ok := asciiCharacterReplacement[c] 38 | 39 | if ok { 40 | b.WriteByte('_') 41 | b.WriteString(replacement) 42 | b.WriteByte('_') 43 | } else { 44 | b.WriteRune(c) 45 | } 46 | } 47 | 48 | } 49 | 50 | return b.String() 51 | } 52 | 53 | func needsCharReplacement(identifier string) (increase int, needs bool) { 54 | for _, c := range identifier { 55 | switch { 56 | case unicode.IsSpace(c): 57 | needs = true 58 | case unicode.IsControl(c): 59 | increase += -1 60 | needs = true 61 | continue 62 | default: 63 | replacement, ok := asciiCharacterReplacement[c] 64 | 65 | if ok { 66 | increase += len(replacement) + 1 67 | needs = true 68 | } 69 | } 70 | } 71 | 72 | return increase, needs 73 | } 74 | 75 | var asciiCharacterReplacement = map[rune]string{ 76 | '!': "exclamation", 77 | '"': "quotation", 78 | '#': "number", 79 | '$': "dollar", 80 | '%': "percent", 81 | '&': "ampersand", 82 | '\'': "apostrophe", 83 | '(': "opening_parentheses", 84 | ')': "closing_parentheses", 85 | '*': "asterisk", 86 | '+': "plus", 87 | ',': "comma", 88 | '-': "_", 89 | '.': "_", 90 | '/': "slash", 91 | ':': "colon", 92 | ';': "semicolon", 93 | '<': "less", 94 | '=': "equal", 95 | '>': "greater", 96 | '?': "question", 97 | '@': "at", 98 | '[': "opening_bracket", 99 | '\\': "backslash", 100 | ']': "closing_bracket", 101 | '^': "caret", 102 | '`': "accent", 103 | '{': "opening_braces", 104 | '|': "vertical_bar", 105 | '}': "closing_braces", 106 | '~': "tilde", 107 | } 108 | -------------------------------------------------------------------------------- /postgres/with_statement.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // CommonTableExpression defines set of interface methods for postgres CTEs 6 | type CommonTableExpression interface { 7 | SelectTable 8 | 9 | AS(statement jet.SerializerHasProjections) CommonTableExpression 10 | AS_NOT_MATERIALIZED(statement jet.SerializerStatement) CommonTableExpression 11 | // ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query. 12 | ALIAS(alias string) SelectTable 13 | 14 | internalCTE() *jet.CommonTableExpression 15 | } 16 | 17 | type commonTableExpression struct { 18 | readableTableInterfaceImpl 19 | jet.CommonTableExpression 20 | } 21 | 22 | // WITH function creates new WITH statement from list of common table expressions 23 | func WITH(cte ...CommonTableExpression) func(statement jet.Statement) Statement { 24 | return jet.WITH(Dialect, false, toInternalCTE(cte)...) 25 | } 26 | 27 | // WITH_RECURSIVE function creates new WITH RECURSIVE statement from list of common table expressions 28 | func WITH_RECURSIVE(cte ...CommonTableExpression) func(statement jet.Statement) Statement { 29 | return jet.WITH(Dialect, true, toInternalCTE(cte)...) 30 | } 31 | 32 | // CTE creates new named commonTableExpression 33 | func CTE(name string, columns ...jet.ColumnExpression) CommonTableExpression { 34 | cte := &commonTableExpression{ 35 | readableTableInterfaceImpl: readableTableInterfaceImpl{}, 36 | CommonTableExpression: jet.CTE(name, columns...), 37 | } 38 | 39 | cte.root = cte 40 | 41 | return cte 42 | } 43 | 44 | // AS is used to define a CTE query 45 | func (c *commonTableExpression) AS(statement jet.SerializerHasProjections) CommonTableExpression { 46 | c.CommonTableExpression.Statement = statement 47 | return c 48 | } 49 | 50 | // AS_NOT_MATERIALIZED is used to define not materialized CTE query 51 | func (c *commonTableExpression) AS_NOT_MATERIALIZED(statement jet.SerializerStatement) CommonTableExpression { 52 | c.CommonTableExpression.NotMaterialized = true 53 | c.CommonTableExpression.Statement = statement 54 | return c 55 | } 56 | 57 | func (c *commonTableExpression) internalCTE() *jet.CommonTableExpression { 58 | return &c.CommonTableExpression 59 | } 60 | 61 | // ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query. 62 | func (c *commonTableExpression) ALIAS(name string) SelectTable { 63 | return newSelectTable(c, name, nil) 64 | } 65 | 66 | func toInternalCTE(ctes []CommonTableExpression) []*jet.CommonTableExpression { 67 | var ret []*jet.CommonTableExpression 68 | 69 | for _, cte := range ctes { 70 | ret = append(ret, cte.internalCTE()) 71 | } 72 | 73 | return ret 74 | } 75 | -------------------------------------------------------------------------------- /internal/jet/timez_expression_test.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var timezVar = Timez(10, 20, 0, 0, "+4:00") 8 | 9 | func TestTimezExpressionEQ(t *testing.T) { 10 | assertClauseSerialize(t, table1ColTimez.EQ(table2ColTimez), "(table1.col_timez = table2.col_timez)") 11 | assertClauseSerialize(t, table1ColTimez.EQ(timezVar), "(table1.col_timez = $1)", "10:20:00 +4:00") 12 | } 13 | 14 | func TestTimezExpressionNOT_EQ(t *testing.T) { 15 | assertClauseSerialize(t, table1ColTimez.NOT_EQ(table2ColTimez), "(table1.col_timez != table2.col_timez)") 16 | assertClauseSerialize(t, table1ColTimez.NOT_EQ(timezVar), "(table1.col_timez != $1)", "10:20:00 +4:00") 17 | } 18 | 19 | func TestTimezExpressionIS_DISTINCT_FROM(t *testing.T) { 20 | assertClauseSerialize(t, table1ColTimez.IS_DISTINCT_FROM(table2ColTimez), "(table1.col_timez IS DISTINCT FROM table2.col_timez)") 21 | assertClauseSerialize(t, table1ColTimez.IS_DISTINCT_FROM(timezVar), "(table1.col_timez IS DISTINCT FROM $1)", "10:20:00 +4:00") 22 | } 23 | 24 | func TestTimezExpressionIS_NOT_DISTINCT_FROM(t *testing.T) { 25 | assertClauseSerialize(t, table1ColTimez.IS_NOT_DISTINCT_FROM(table2ColTimez), "(table1.col_timez IS NOT DISTINCT FROM table2.col_timez)") 26 | assertClauseSerialize(t, table1ColTimez.IS_NOT_DISTINCT_FROM(timezVar), "(table1.col_timez IS NOT DISTINCT FROM $1)", "10:20:00 +4:00") 27 | } 28 | 29 | func TestTimezExpressionLT(t *testing.T) { 30 | assertClauseSerialize(t, table1ColTimez.LT(table2ColTimez), "(table1.col_timez < table2.col_timez)") 31 | assertClauseSerialize(t, table1ColTimez.LT(timezVar), "(table1.col_timez < $1)", "10:20:00 +4:00") 32 | } 33 | 34 | func TestTimezExpressionLT_EQ(t *testing.T) { 35 | assertClauseSerialize(t, table1ColTimez.LT_EQ(table2ColTimez), "(table1.col_timez <= table2.col_timez)") 36 | assertClauseSerialize(t, table1ColTimez.LT_EQ(timezVar), "(table1.col_timez <= $1)", "10:20:00 +4:00") 37 | } 38 | 39 | func TestTimezExpressionGT(t *testing.T) { 40 | assertClauseSerialize(t, table1ColTimez.GT(table2ColTimez), "(table1.col_timez > table2.col_timez)") 41 | assertClauseSerialize(t, table1ColTimez.GT(timezVar), "(table1.col_timez > $1)", "10:20:00 +4:00") 42 | } 43 | 44 | func TestTimezExpressionGT_EQ(t *testing.T) { 45 | assertClauseSerialize(t, table1ColTimez.GT_EQ(table2ColTimez), "(table1.col_timez >= table2.col_timez)") 46 | assertClauseSerialize(t, table1ColTimez.GT_EQ(timezVar), "(table1.col_timez >= $1)", "10:20:00 +4:00") 47 | } 48 | 49 | func TestTimezExp(t *testing.T) { 50 | assertClauseSerialize(t, TimezExp(table1ColFloat), "table1.col_float") 51 | assertClauseSerialize(t, TimezExp(table1ColFloat).LT(Timez(1, 1, 1, 1, "+4:00")), 52 | "(table1.col_float < $1)", string("01:01:01.000000001 +4:00")) 53 | } 54 | -------------------------------------------------------------------------------- /sqlite/with_statement.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // CommonTableExpression defines set of interface methods for postgres CTEs 6 | type CommonTableExpression interface { 7 | SelectTable 8 | 9 | AS(statement jet.SerializerHasProjections) CommonTableExpression 10 | AS_NOT_MATERIALIZED(statement jet.SerializerHasProjections) CommonTableExpression 11 | // ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query. 12 | ALIAS(alias string) SelectTable 13 | 14 | internalCTE() *jet.CommonTableExpression 15 | } 16 | 17 | type commonTableExpression struct { 18 | readableTableInterfaceImpl 19 | jet.CommonTableExpression 20 | } 21 | 22 | // WITH function creates new WITH statement from list of common table expressions 23 | func WITH(cte ...CommonTableExpression) func(statement jet.Statement) Statement { 24 | return jet.WITH(Dialect, false, toInternalCTE(cte)...) 25 | } 26 | 27 | // WITH_RECURSIVE function creates new WITH RECURSIVE statement from list of common table expressions 28 | func WITH_RECURSIVE(cte ...CommonTableExpression) func(statement jet.Statement) Statement { 29 | return jet.WITH(Dialect, true, toInternalCTE(cte)...) 30 | } 31 | 32 | // CTE creates new named commonTableExpression 33 | func CTE(name string, columns ...jet.ColumnExpression) CommonTableExpression { 34 | cte := &commonTableExpression{ 35 | readableTableInterfaceImpl: readableTableInterfaceImpl{}, 36 | CommonTableExpression: jet.CTE(name, columns...), 37 | } 38 | 39 | cte.root = cte 40 | 41 | return cte 42 | } 43 | 44 | // AS is used to define a CTE query 45 | func (c *commonTableExpression) AS(statement jet.SerializerHasProjections) CommonTableExpression { 46 | c.CommonTableExpression.Statement = statement 47 | return c 48 | } 49 | 50 | // AS_NOT_MATERIALIZED is used to define not materialized CTE query 51 | func (c *commonTableExpression) AS_NOT_MATERIALIZED(statement jet.SerializerHasProjections) CommonTableExpression { 52 | c.CommonTableExpression.NotMaterialized = true 53 | c.CommonTableExpression.Statement = statement 54 | return c 55 | } 56 | 57 | func (c *commonTableExpression) internalCTE() *jet.CommonTableExpression { 58 | return &c.CommonTableExpression 59 | } 60 | 61 | // ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query. 62 | func (c *commonTableExpression) ALIAS(name string) SelectTable { 63 | return newSelectTable(c, name, nil) 64 | } 65 | 66 | func toInternalCTE(ctes []CommonTableExpression) []*jet.CommonTableExpression { 67 | var ret []*jet.CommonTableExpression 68 | 69 | for _, cte := range ctes { 70 | ret = append(ret, cte.internalCTE()) 71 | } 72 | 73 | return ret 74 | } 75 | -------------------------------------------------------------------------------- /postgres/insert_statement.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import "github.com/go-jet/jet/v2/internal/jet" 4 | 5 | // InsertStatement is interface for SQL INSERT statements 6 | type InsertStatement interface { 7 | jet.SerializerStatement 8 | 9 | // Insert row of values 10 | VALUES(value interface{}, values ...interface{}) InsertStatement 11 | // Insert row of values, where value for each column is extracted from filed of structure data. 12 | // If data is not struct or there is no field for every column selected, this method will panic. 13 | MODEL(data interface{}) InsertStatement 14 | MODELS(data interface{}) InsertStatement 15 | QUERY(selectStatement SelectStatement) InsertStatement 16 | 17 | ON_CONFLICT(indexExpressions ...jet.ColumnExpression) onConflict 18 | 19 | RETURNING(projections ...Projection) InsertStatement 20 | } 21 | 22 | func newInsertStatement(table WritableTable, columns []jet.Column) InsertStatement { 23 | newInsert := &insertStatementImpl{} 24 | newInsert.SerializerStatement = jet.NewStatementImpl(Dialect, jet.InsertStatementType, newInsert, 25 | &newInsert.Insert, 26 | &newInsert.ValuesQuery, 27 | &newInsert.OnConflict, 28 | &newInsert.Returning, 29 | ) 30 | 31 | newInsert.Insert.Table = table 32 | newInsert.Insert.Columns = columns 33 | 34 | return newInsert 35 | } 36 | 37 | type insertStatementImpl struct { 38 | jet.SerializerStatement 39 | 40 | Insert jet.ClauseInsert 41 | ValuesQuery jet.ClauseValuesQuery 42 | Returning jet.ClauseReturning 43 | OnConflict onConflictClause 44 | } 45 | 46 | func (i *insertStatementImpl) VALUES(value interface{}, values ...interface{}) InsertStatement { 47 | i.ValuesQuery.Rows = append(i.ValuesQuery.Rows, jet.UnwindRowFromValues(value, values)) 48 | return i 49 | } 50 | 51 | func (i *insertStatementImpl) MODEL(data interface{}) InsertStatement { 52 | i.ValuesQuery.Rows = append(i.ValuesQuery.Rows, jet.UnwindRowFromModel(i.Insert.GetColumns(), data)) 53 | return i 54 | } 55 | 56 | func (i *insertStatementImpl) MODELS(data interface{}) InsertStatement { 57 | i.ValuesQuery.Rows = append(i.ValuesQuery.Rows, jet.UnwindRowsFromModels(i.Insert.GetColumns(), data)...) 58 | return i 59 | } 60 | 61 | func (i *insertStatementImpl) RETURNING(projections ...jet.Projection) InsertStatement { 62 | i.Returning.ProjectionList = projections 63 | return i 64 | } 65 | 66 | func (i *insertStatementImpl) QUERY(selectStatement SelectStatement) InsertStatement { 67 | i.ValuesQuery.Query = selectStatement 68 | return i 69 | } 70 | 71 | func (i *insertStatementImpl) ON_CONFLICT(indexExpressions ...jet.ColumnExpression) onConflict { 72 | i.OnConflict = onConflictClause{ 73 | insertStatement: i, 74 | indexExpressions: indexExpressions, 75 | } 76 | return &i.OnConflict 77 | } 78 | -------------------------------------------------------------------------------- /tests/mysql/sample_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/go-jet/jet/v2/internal/testutils" 5 | "github.com/go-jet/jet/v2/internal/utils/ptr" 6 | "github.com/go-jet/jet/v2/qrm" 7 | "github.com/stretchr/testify/require" 8 | "testing" 9 | 10 | . "github.com/go-jet/jet/v2/mysql" 11 | "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/model" 12 | . "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/table" 13 | 14 | ) 15 | 16 | func TestMutableColumnsExcludeGeneratedColumn(t *testing.T) { 17 | 18 | t.Run("should not have the generated column in mutableColumns", func(t *testing.T) { 19 | require.Equal(t, 2, len(People.MutableColumns)) 20 | require.Equal(t, People.PeopleName, People.MutableColumns[0]) 21 | require.Equal(t, People.PeopleHeightCm, People.MutableColumns[1]) 22 | }) 23 | 24 | t.Run("should query with all columns", func(t *testing.T) { 25 | query := SELECT( 26 | People.AllColumns, 27 | ).FROM( 28 | People, 29 | ).WHERE( 30 | People.PeopleID.EQ(Int(3)), 31 | ) 32 | 33 | var result model.People 34 | 35 | err := query.Query(db, &result) 36 | require.NoError(t, err) 37 | 38 | require.Equal(t, "Carla", result.PeopleName) 39 | require.Equal(t, 155., *result.PeopleHeightCm) 40 | require.InEpsilon(t, 61.02, *result.PeopleHeightIn, 1e-3) 41 | }) 42 | 43 | t.Run("should insert without generated columns", func(t *testing.T) { 44 | testutils.ExecuteInTxAndRollback(t, db, func(tx qrm.DB) { 45 | insertQuery := People.INSERT( 46 | People.MutableColumns, 47 | ).MODEL( 48 | model.People{ 49 | PeopleName: "Dario", 50 | PeopleHeightCm: ptr.Of(120.0), 51 | }, 52 | ) 53 | 54 | testutils.AssertDebugStatementSql(t, insertQuery, ` 55 | INSERT INTO test_sample.people (people_name, people_height_cm) 56 | VALUES ('Dario', 120); 57 | `) 58 | _, err := insertQuery.Exec(tx) 59 | require.NoError(t, err) 60 | 61 | var result model.People 62 | selectQuery := SELECT( 63 | People.MutableColumns, 64 | ).FROM( 65 | People, 66 | ).ORDER_BY( 67 | People.PeopleID.DESC(), 68 | ).LIMIT(1) 69 | 70 | err = selectQuery.Query(tx, &result) 71 | require.NoError(t, err) 72 | 73 | require.Equal(t, "Dario", result.PeopleName) 74 | require.Equal(t, 120., *result.PeopleHeightCm) 75 | 76 | query := SELECT( 77 | People.AllColumns, 78 | ).FROM( 79 | People, 80 | ).ORDER_BY( 81 | People.PeopleID.DESC(), 82 | ).LIMIT(1) 83 | 84 | result = model.People{} 85 | 86 | err = query.Query(tx, &result) 87 | require.NoError(t, err) 88 | 89 | require.Equal(t, "Dario", result.PeopleName) 90 | require.Equal(t, 120., *result.PeopleHeightCm) 91 | require.InEpsilon(t, 47.24, *result.PeopleHeightIn, 1e-3) 92 | }) 93 | }) 94 | } -------------------------------------------------------------------------------- /internal/jet/alias.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | type alias struct { 4 | expression Expression 5 | alias string 6 | } 7 | 8 | func newAlias(expression Expression, aliasName string) Projection { 9 | return &alias{ 10 | expression: expression, 11 | alias: aliasName, 12 | } 13 | } 14 | 15 | func (a *alias) fromImpl(subQuery SelectTable) Projection { 16 | // if alias is in the form "table.column", we break it into two parts so that ProjectionList.As(newAlias) can 17 | // overwrite tableName with a new alias. This method is called only for exporting aliased custom columns. 18 | // Generated columns have default aliasing. 19 | tableName, columnName := extractTableAndColumnName(a.alias) 20 | 21 | newDummyColumn := newDummyColumnForExpression(a.expression, columnName) 22 | newDummyColumn.setTableName(tableName) 23 | newDummyColumn.setSubQuery(subQuery) 24 | 25 | return newDummyColumn 26 | } 27 | 28 | // This function is used to create dummy columns when exporting sub-query columns using subQuery.AllColumns() 29 | // In most case we don't care about type of the column, except when sub-query columns are used as SELECT_JSON projection. 30 | // We need to know type to encode value for json unmarshal. At the moment only bool, time and blob columns are of interest, 31 | // so we don't have to support every column type. 32 | func newDummyColumnForExpression(exp Expression, name string) ColumnExpression { 33 | 34 | switch exp.(type) { 35 | case BoolExpression: 36 | return BoolColumn(name) 37 | case IntegerExpression: 38 | return IntegerColumn(name) 39 | case FloatExpression: 40 | return FloatColumn(name) 41 | case BlobExpression: 42 | return BlobColumn(name) 43 | case DateExpression: 44 | return DateColumn(name) 45 | case TimeExpression: 46 | return TimeColumn(name) 47 | case TimezExpression: 48 | return TimezColumn(name) 49 | case TimestampExpression: 50 | return TimestampColumn(name) 51 | case TimestampzExpression: 52 | return TimestampzColumn(name) 53 | case IntervalExpression: 54 | return IntervalColumn(name) 55 | case StringExpression: 56 | return StringColumn(name) 57 | } 58 | 59 | return StringColumn(name) 60 | } 61 | 62 | func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder) { 63 | a.expression.serialize(statement, out) 64 | 65 | out.WriteString("AS") 66 | out.WriteAlias(a.alias) 67 | } 68 | 69 | func (a *alias) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) { 70 | out.WriteJsonObjKey(a.alias) 71 | a.expression.serializeForJsonValue(statement, out) 72 | } 73 | 74 | func (a *alias) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) { 75 | a.expression.serializeForJsonValue(statement, out) 76 | 77 | out.WriteString("AS") 78 | out.WriteAlias(a.alias) 79 | } 80 | -------------------------------------------------------------------------------- /mysql/expressions_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "testing" 5 | time2 "time" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestRaw(t *testing.T) { 11 | assertSerialize(t, Raw("current_database()"), "(current_database())") 12 | assertDebugSerialize(t, Raw("current_database()"), "(current_database())") 13 | 14 | assertSerialize(t, Raw(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22}), 15 | "(? + table.colInt + ?)", 11, 22) 16 | assertDebugSerialize(t, Raw(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22}), 17 | "(11 + table.colInt + 22)") 18 | 19 | assertSerialize(t, 20 | Int(700).ADD(RawInt("#1 + table.colInt + #2", RawArgs{"#1": 11, "#2": 22})), 21 | "(? + (? + table.colInt + ?))", 22 | int64(700), 11, 22) 23 | assertDebugSerialize(t, 24 | Int(700).ADD(RawInt("#1 + table.colInt + #2", RawArgs{"#1": 11, "#2": 22})), 25 | "(700 + (11 + table.colInt + 22))") 26 | } 27 | 28 | func TestRawDuplicateArguments(t *testing.T) { 29 | assertSerialize(t, Raw(":arg + table.colInt + :arg", RawArgs{":arg": 11}), 30 | "(? + table.colInt + ?)", 11, 11) 31 | 32 | assertSerialize(t, Raw("#age + table.colInt + #year + #age + #year + 11", RawArgs{"#age": 11, "#year": 2000}), 33 | "(? + table.colInt + ? + ? + ? + 11)", 11, 2000, 11, 2000) 34 | 35 | assertSerialize(t, Raw("#1 + all_types.integer + #2 + #1 + #2 + #3 + #4", 36 | RawArgs{"#1": 11, "#2": 22, "#3": 33, "#4": 44}), 37 | `(? + all_types.integer + ? + ? + ? + ? + ?)`, 11, 22, 11, 22, 33, 44) 38 | } 39 | 40 | func TestRawInvalidArguments(t *testing.T) { 41 | defer func() { 42 | r := recover() 43 | require.Equal(t, "jet: named argument 'first_arg' does not appear in raw query", r) 44 | }() 45 | 46 | assertSerialize(t, Raw("table.colInt + :second_arg", RawArgs{"first_arg": 11}), "(table.colInt + ?)", 22) 47 | } 48 | 49 | func TestRawType(t *testing.T) { 50 | assertSerialize(t, RawBool("table.colInt < :float", RawArgs{":float": 11.22}).IS_FALSE(), 51 | "(table.colInt < ?) IS FALSE", 11.22) 52 | 53 | assertSerialize(t, RawFloat("table.colInt + &float", RawArgs{"&float": 11.22}).EQ(Float(3.14)), 54 | "((table.colInt + ?) = ?)", 11.22, 3.14) 55 | assertSerialize(t, RawString("table.colStr || str", RawArgs{"str": "doe"}).EQ(String("john doe")), 56 | "((table.colStr || ?) = ?)", "doe", "john doe") 57 | 58 | time := time2.Now() 59 | assertSerialize(t, RawTime("table.colTime").EQ(TimeT(time)), 60 | "((table.colTime) = CAST(? AS TIME))", time) 61 | assertSerialize(t, RawTimestamp("table.colTimestamp").EQ(TimestampT(time)), 62 | "((table.colTimestamp) = TIMESTAMP(?))", time) 63 | assertSerialize(t, RawDate("table.colDate").EQ(DateT(time)), 64 | "((table.colDate) = CAST(? AS DATE))", time) 65 | } 66 | -------------------------------------------------------------------------------- /mysql/dialect_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBoolExpressionIS_DISTINCT_FROM(t *testing.T) { 8 | assertSerialize(t, table1ColBool.IS_DISTINCT_FROM(table2ColBool), "(NOT(table1.col_bool <=> table2.col_bool))") 9 | assertSerialize(t, table1ColBool.IS_DISTINCT_FROM(Bool(false)), "(NOT(table1.col_bool <=> ?))", false) 10 | } 11 | 12 | func TestBoolExpressionIS_NOT_DISTINCT_FROM(t *testing.T) { 13 | assertSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(table2ColBool), "(table1.col_bool <=> table2.col_bool)") 14 | assertSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(Bool(false)), "(table1.col_bool <=> ?)", false) 15 | } 16 | 17 | func TestBoolLiteral(t *testing.T) { 18 | assertSerialize(t, Bool(true), "?", true) 19 | assertSerialize(t, Bool(false), "?", false) 20 | } 21 | 22 | func TestIntegerExpressionDIV(t *testing.T) { 23 | assertSerialize(t, table1ColInt.DIV(table2ColInt), "(table1.col_int DIV table2.col_int)") 24 | assertSerialize(t, table1ColInt.DIV(Int(11)), "(table1.col_int DIV ?)", int64(11)) 25 | } 26 | 27 | func TestIntExpressionPOW(t *testing.T) { 28 | assertSerialize(t, table1ColInt.POW(table2ColInt), "POW(table1.col_int, table2.col_int)") 29 | assertSerialize(t, table1ColInt.POW(Int(11)), "POW(table1.col_int, ?)", int64(11)) 30 | } 31 | 32 | func TestIntExpressionBIT_XOR(t *testing.T) { 33 | assertSerialize(t, table1ColInt.BIT_XOR(table2ColInt), "(table1.col_int ^ table2.col_int)") 34 | assertSerialize(t, table1ColInt.BIT_XOR(Int(11)), "(table1.col_int ^ ?)", int64(11)) 35 | } 36 | 37 | func TestExists(t *testing.T) { 38 | assertSerialize(t, EXISTS( 39 | table2. 40 | SELECT(Int(1)). 41 | WHERE(table1Col1.EQ(table2Col3)), 42 | ), 43 | `(EXISTS ( 44 | SELECT ? 45 | FROM db.table2 46 | WHERE table1.col1 = table2.col3 47 | ))`, int64(1)) 48 | } 49 | 50 | func TestString_REGEXP_LIKE_operator(t *testing.T) { 51 | assertSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "(table3.col2 REGEXP table2.col_str)") 52 | assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN")), "(table3.col2 REGEXP ?)", "JOHN") 53 | assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), false), "(table3.col2 REGEXP ?)", "JOHN") 54 | assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), true), "(table3.col2 REGEXP BINARY ?)", "JOHN") 55 | } 56 | 57 | func TestString_NOT_REGEXP_LIKE_operator(t *testing.T) { 58 | assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(table2ColStr), "(table3.col2 NOT REGEXP table2.col_str)") 59 | assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN")), "(table3.col2 NOT REGEXP ?)", "JOHN") 60 | assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), false), "(table3.col2 NOT REGEXP ?)", "JOHN") 61 | assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 NOT REGEXP BINARY ?)", "JOHN") 62 | } 63 | -------------------------------------------------------------------------------- /postgres/cast_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestExpressionCAST_AS(t *testing.T) { 8 | assertSerialize(t, CAST(Int(11)).AS("text"), `$1::text`, int64(11)) 9 | } 10 | 11 | func TestExpressionCAST_AS_BOOL(t *testing.T) { 12 | assertSerialize(t, CAST(Int(1)).AS_BOOL(), "$1::boolean", int64(1)) 13 | assertSerialize(t, CAST(table2Col3).AS_BOOL(), "table2.col3::boolean") 14 | assertSerialize(t, CAST(table2Col3.ADD(table2Col3)).AS_BOOL(), "(table2.col3 + table2.col3)::boolean") 15 | } 16 | 17 | func TestExpressionCAST_AS_SMALLINT(t *testing.T) { 18 | assertSerialize(t, CAST(table2Col3).AS_SMALLINT(), "table2.col3::smallint") 19 | } 20 | 21 | func TestExpressionCAST_AS_INTEGER(t *testing.T) { 22 | assertSerialize(t, CAST(table2Col3).AS_INTEGER(), "table2.col3::integer") 23 | } 24 | 25 | func TestExpressionCAST_AS_BIGINT(t *testing.T) { 26 | assertSerialize(t, CAST(table2Col3).AS_BIGINT(), "table2.col3::bigint") 27 | } 28 | 29 | func TestExpressionCAST_AS_NUMERIC(t *testing.T) { 30 | assertSerialize(t, CAST(table2Col3).AS_NUMERIC(11, 11), "table2.col3::numeric(11, 11)") 31 | assertSerialize(t, CAST(table2Col3).AS_NUMERIC(11), "table2.col3::numeric(11)") 32 | } 33 | 34 | func TestExpressionCAST_AS_REAL(t *testing.T) { 35 | assertSerialize(t, CAST(table2Col3).AS_REAL(), "table2.col3::real") 36 | } 37 | 38 | func TestExpressionCAST_AS_DOUBLE(t *testing.T) { 39 | assertSerialize(t, CAST(table2Col3).AS_DOUBLE(), "table2.col3::double precision") 40 | } 41 | 42 | func TestExpressionCAST_AS_TEXT(t *testing.T) { 43 | assertSerialize(t, CAST(table2Col3).AS_TEXT(), "table2.col3::text") 44 | } 45 | 46 | func TestExpressionCAST_AS_DATE(t *testing.T) { 47 | assertSerialize(t, CAST(table2Col3).AS_DATE(), "table2.col3::date") 48 | } 49 | 50 | func TestExpressionCAST_AS_TIME(t *testing.T) { 51 | assertSerialize(t, CAST(table2Col3).AS_TIME(), "table2.col3::time without time zone") 52 | } 53 | 54 | func TestExpressionCAST_AS_TIMEZ(t *testing.T) { 55 | assertSerialize(t, CAST(table2Col3).AS_TIMEZ(), "table2.col3::time with time zone") 56 | } 57 | 58 | func TestExpressionCAST_AS_TIMESTAMP(t *testing.T) { 59 | assertSerialize(t, CAST(table2Col3).AS_TIMESTAMP(), "table2.col3::timestamp without time zone") 60 | } 61 | 62 | func TestExpressionCAST_AS_TIMESTAMPZ(t *testing.T) { 63 | assertSerialize(t, CAST(table2Col3).AS_TIMESTAMPZ(), "table2.col3::timestamp with time zone") 64 | } 65 | 66 | func TestExpressionCAST_AS_INTERVAL(t *testing.T) { 67 | assertSerialize(t, CAST(table2ColTimez).AS_INTERVAL(), "table2.col_timez::interval") 68 | assertSerialize(t, CAST(Time(20, 11, 10)).AS_INTERVAL(), "$1::time without time zone::interval", "20:11:10") 69 | assertSerialize(t, table2ColDate.SUB(CAST(Time(20, 11, 10)).AS_INTERVAL()), 70 | "(table2.col_date - $1::time without time zone::interval)", "20:11:10") 71 | } 72 | -------------------------------------------------------------------------------- /examples/quick-start/.gen/jetdb/dvds/table/category.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package table 9 | 10 | import ( 11 | "github.com/go-jet/jet/v2/postgres" 12 | ) 13 | 14 | var Category = newCategoryTable("dvds", "category", "") 15 | 16 | type categoryTable struct { 17 | postgres.Table 18 | 19 | // Columns 20 | CategoryID postgres.ColumnInteger 21 | Name postgres.ColumnString 22 | LastUpdate postgres.ColumnTimestamp 23 | 24 | AllColumns postgres.ColumnList 25 | MutableColumns postgres.ColumnList 26 | DefaultColumns postgres.ColumnList 27 | } 28 | 29 | type CategoryTable struct { 30 | categoryTable 31 | 32 | EXCLUDED categoryTable 33 | } 34 | 35 | // AS creates new CategoryTable with assigned alias 36 | func (a CategoryTable) AS(alias string) *CategoryTable { 37 | return newCategoryTable(a.SchemaName(), a.TableName(), alias) 38 | } 39 | 40 | // Schema creates new CategoryTable with assigned schema name 41 | func (a CategoryTable) FromSchema(schemaName string) *CategoryTable { 42 | return newCategoryTable(schemaName, a.TableName(), a.Alias()) 43 | } 44 | 45 | // WithPrefix creates new CategoryTable with assigned table prefix 46 | func (a CategoryTable) WithPrefix(prefix string) *CategoryTable { 47 | return newCategoryTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) 48 | } 49 | 50 | // WithSuffix creates new CategoryTable with assigned table suffix 51 | func (a CategoryTable) WithSuffix(suffix string) *CategoryTable { 52 | return newCategoryTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) 53 | } 54 | 55 | func newCategoryTable(schemaName, tableName, alias string) *CategoryTable { 56 | return &CategoryTable{ 57 | categoryTable: newCategoryTableImpl(schemaName, tableName, alias), 58 | EXCLUDED: newCategoryTableImpl("", "excluded", ""), 59 | } 60 | } 61 | 62 | func newCategoryTableImpl(schemaName, tableName, alias string) categoryTable { 63 | var ( 64 | CategoryIDColumn = postgres.IntegerColumn("category_id") 65 | NameColumn = postgres.StringColumn("name") 66 | LastUpdateColumn = postgres.TimestampColumn("last_update") 67 | allColumns = postgres.ColumnList{CategoryIDColumn, NameColumn, LastUpdateColumn} 68 | mutableColumns = postgres.ColumnList{NameColumn, LastUpdateColumn} 69 | defaultColumns = postgres.ColumnList{CategoryIDColumn, LastUpdateColumn} 70 | ) 71 | 72 | return categoryTable{ 73 | Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), 74 | 75 | //Columns 76 | CategoryID: CategoryIDColumn, 77 | Name: NameColumn, 78 | LastUpdate: LastUpdateColumn, 79 | 80 | AllColumns: allColumns, 81 | MutableColumns: mutableColumns, 82 | DefaultColumns: defaultColumns, 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /examples/quick-start/.gen/jetdb/dvds/table/language.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package table 9 | 10 | import ( 11 | "github.com/go-jet/jet/v2/postgres" 12 | ) 13 | 14 | var Language = newLanguageTable("dvds", "language", "") 15 | 16 | type languageTable struct { 17 | postgres.Table 18 | 19 | // Columns 20 | LanguageID postgres.ColumnInteger 21 | Name postgres.ColumnString 22 | LastUpdate postgres.ColumnTimestamp 23 | 24 | AllColumns postgres.ColumnList 25 | MutableColumns postgres.ColumnList 26 | DefaultColumns postgres.ColumnList 27 | } 28 | 29 | type LanguageTable struct { 30 | languageTable 31 | 32 | EXCLUDED languageTable 33 | } 34 | 35 | // AS creates new LanguageTable with assigned alias 36 | func (a LanguageTable) AS(alias string) *LanguageTable { 37 | return newLanguageTable(a.SchemaName(), a.TableName(), alias) 38 | } 39 | 40 | // Schema creates new LanguageTable with assigned schema name 41 | func (a LanguageTable) FromSchema(schemaName string) *LanguageTable { 42 | return newLanguageTable(schemaName, a.TableName(), a.Alias()) 43 | } 44 | 45 | // WithPrefix creates new LanguageTable with assigned table prefix 46 | func (a LanguageTable) WithPrefix(prefix string) *LanguageTable { 47 | return newLanguageTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) 48 | } 49 | 50 | // WithSuffix creates new LanguageTable with assigned table suffix 51 | func (a LanguageTable) WithSuffix(suffix string) *LanguageTable { 52 | return newLanguageTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) 53 | } 54 | 55 | func newLanguageTable(schemaName, tableName, alias string) *LanguageTable { 56 | return &LanguageTable{ 57 | languageTable: newLanguageTableImpl(schemaName, tableName, alias), 58 | EXCLUDED: newLanguageTableImpl("", "excluded", ""), 59 | } 60 | } 61 | 62 | func newLanguageTableImpl(schemaName, tableName, alias string) languageTable { 63 | var ( 64 | LanguageIDColumn = postgres.IntegerColumn("language_id") 65 | NameColumn = postgres.StringColumn("name") 66 | LastUpdateColumn = postgres.TimestampColumn("last_update") 67 | allColumns = postgres.ColumnList{LanguageIDColumn, NameColumn, LastUpdateColumn} 68 | mutableColumns = postgres.ColumnList{NameColumn, LastUpdateColumn} 69 | defaultColumns = postgres.ColumnList{LanguageIDColumn, LastUpdateColumn} 70 | ) 71 | 72 | return languageTable{ 73 | Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), 74 | 75 | //Columns 76 | LanguageID: LanguageIDColumn, 77 | Name: NameColumn, 78 | LastUpdate: LastUpdateColumn, 79 | 80 | AllColumns: allColumns, 81 | MutableColumns: mutableColumns, 82 | DefaultColumns: defaultColumns, 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /examples/quick-start/.gen/jetdb/dvds/table/film_actor.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package table 9 | 10 | import ( 11 | "github.com/go-jet/jet/v2/postgres" 12 | ) 13 | 14 | var FilmActor = newFilmActorTable("dvds", "film_actor", "") 15 | 16 | type filmActorTable struct { 17 | postgres.Table 18 | 19 | // Columns 20 | ActorID postgres.ColumnInteger 21 | FilmID postgres.ColumnInteger 22 | LastUpdate postgres.ColumnTimestamp 23 | 24 | AllColumns postgres.ColumnList 25 | MutableColumns postgres.ColumnList 26 | DefaultColumns postgres.ColumnList 27 | } 28 | 29 | type FilmActorTable struct { 30 | filmActorTable 31 | 32 | EXCLUDED filmActorTable 33 | } 34 | 35 | // AS creates new FilmActorTable with assigned alias 36 | func (a FilmActorTable) AS(alias string) *FilmActorTable { 37 | return newFilmActorTable(a.SchemaName(), a.TableName(), alias) 38 | } 39 | 40 | // Schema creates new FilmActorTable with assigned schema name 41 | func (a FilmActorTable) FromSchema(schemaName string) *FilmActorTable { 42 | return newFilmActorTable(schemaName, a.TableName(), a.Alias()) 43 | } 44 | 45 | // WithPrefix creates new FilmActorTable with assigned table prefix 46 | func (a FilmActorTable) WithPrefix(prefix string) *FilmActorTable { 47 | return newFilmActorTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) 48 | } 49 | 50 | // WithSuffix creates new FilmActorTable with assigned table suffix 51 | func (a FilmActorTable) WithSuffix(suffix string) *FilmActorTable { 52 | return newFilmActorTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) 53 | } 54 | 55 | func newFilmActorTable(schemaName, tableName, alias string) *FilmActorTable { 56 | return &FilmActorTable{ 57 | filmActorTable: newFilmActorTableImpl(schemaName, tableName, alias), 58 | EXCLUDED: newFilmActorTableImpl("", "excluded", ""), 59 | } 60 | } 61 | 62 | func newFilmActorTableImpl(schemaName, tableName, alias string) filmActorTable { 63 | var ( 64 | ActorIDColumn = postgres.IntegerColumn("actor_id") 65 | FilmIDColumn = postgres.IntegerColumn("film_id") 66 | LastUpdateColumn = postgres.TimestampColumn("last_update") 67 | allColumns = postgres.ColumnList{ActorIDColumn, FilmIDColumn, LastUpdateColumn} 68 | mutableColumns = postgres.ColumnList{LastUpdateColumn} 69 | defaultColumns = postgres.ColumnList{LastUpdateColumn} 70 | ) 71 | 72 | return filmActorTable{ 73 | Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), 74 | 75 | //Columns 76 | ActorID: ActorIDColumn, 77 | FilmID: FilmIDColumn, 78 | LastUpdate: LastUpdateColumn, 79 | 80 | AllColumns: allColumns, 81 | MutableColumns: mutableColumns, 82 | DefaultColumns: defaultColumns, 83 | } 84 | } 85 | --------------------------------------------------------------------------------