Add support for additional array types.

This commit is contained in:
go-jet 2025-10-16 15:09:07 +02:00
parent 45d4ced9b0
commit 4ee047a675
47 changed files with 1994 additions and 4277 deletions

View file

@ -198,10 +198,13 @@ stmt := SELECT(
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
).WHERE(
Language.Name.EQ(Char(20)("English")).
AND(Category.Name.NOT_EQ(Text("Action"))).
AND(Film.Length.GT(Int32(180))).
AND(Film.Rating.NOT_EQ(enum.MpaaRating.R)),
AND(
Language.Name.EQ(Char(20)("English")), // string columns Language.Name and Category.Name can be compared only with string expression
Category.Name.NOT_EQ(Text("Action")),
Film.Length.GT(Int32(180)), // Film.Length is integer column and can be compared only with integer expression
Film.Rating.NOT_EQ(enum.MpaaRating.R),
String("Trailers").EQ(ANY(Film.SpecialFeatures)), // type safety is also enforced on array element types
),
).ORDER_BY(
Actor.ActorID.ASC(),
Film.FilmID.ASC(),
@ -212,7 +215,8 @@ stmt := SELECT(
Note that every column has a type. String columns, such as `Language.Name` and `Category.Name` can only be compared with
string columns and expressions. Similarity, `Actor.ActorID`, `FilmActor.ActorID`, `Film.Length` are integer columns
and can only be compared with integer columns and expressions.
and can only be compared with integer columns and expressions. The same type safety rules apply to arrays and their
element types.
__How to Get a Parametrized SQL Query from the Statement?__
```go
@ -244,7 +248,6 @@ SELECT actor.actor_id AS "actor.actor_id",
film.fulltext AS "film.fulltext",
language.language_id AS "language.language_id",
language.name AS "language.name",
language.last_update AS "language.last_update",
category.category_id AS "category.category_id",
category.name AS "category.name",
category.last_update AS "category.last_update"
@ -254,11 +257,18 @@ FROM dvds.actor
INNER JOIN dvds.language ON (language.language_id = film.language_id)
INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id)
INNER JOIN dvds.category ON (category.category_id = film_category.category_id)
WHERE (((language.name = $1::char(20)) AND (category.name != $2::text)) AND (film.length > $3)) AND (film.rating != 'R')
WHERE (
(language.name = $1::char(20))
AND (category.name != $2::text)
AND (film.length > $3::integer)
AND (film.rating != 'R')
AND ($4::text = ANY(film.special_features))
)
ORDER BY actor.actor_id ASC, film.film_id ASC;
```
```sh
[English Action 180]
[English Action 180 Trailers]
```
@ -295,7 +305,6 @@ SELECT actor.actor_id AS "actor.actor_id",
film.fulltext AS "film.fulltext",
language.language_id AS "language.language_id",
language.name AS "language.name",
language.last_update AS "language.last_update",
category.category_id AS "category.category_id",
category.name AS "category.name",
category.last_update AS "category.last_update"
@ -305,7 +314,13 @@ FROM dvds.actor
INNER JOIN dvds.language ON (language.language_id = film.language_id)
INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id)
INNER JOIN dvds.category ON (category.category_id = film_category.category_id)
WHERE (((language.name = 'English'::char(20)) AND (category.name != 'Action'::text)) AND (film.length > 180)) AND (film.rating != 'R')
WHERE (
(language.name = 'English'::char(20))
AND (category.name != 'Action'::text)
AND (film.length > 180::integer)
AND (film.rating != 'R')
AND ('Trailers'::text = ANY(film.special_features))
)
ORDER BY actor.actor_id ASC, film.film_id ASC;
```
</details>
@ -355,8 +370,8 @@ __And that's it.__
The `dest` variable now contains a list of all actors (each with a list of
films they acted in). Each film includes information about its language and
a list of categories it belongs to. This list is filtered to include only
films longer than 180 minutes, where the film language is 'English', and
the film category is not 'Action'.
films longer than 180 minutes, where the film language is 'English',
the film category is not 'Action' and 'Trailers' are one of the film's special features.
> [!Tip]
> It is recommended to enable **Strict Scan** on application startup, especially when destination contains
@ -388,7 +403,11 @@ fmt.Println(string(jsonText))
"ReplacementCost": 24.99,
"Rating": "NC-17",
"LastUpdate": "2013-05-26T14:50:58.951Z",
"SpecialFeatures": "{Trailers,\"Deleted Scenes\",\"Behind the Scenes\"}",
"SpecialFeatures": [
"Trailers",
"Deleted Scenes",
"Behind the Scenes"
],
"Fulltext": "'action':5 'action-pack':4 'baloon':21 'boy':10 'chase':16 'evolut':2 'king':1 'lumberjack':13 'madman':18 'must':15 'pack':6 'tale':7",
"Language": {
"LanguageID": 1,
@ -423,7 +442,10 @@ fmt.Println(string(jsonText))
"ReplacementCost": 9.99,
"Rating": "G",
"LastUpdate": "2013-05-26T14:50:58.951Z",
"SpecialFeatures": "{Trailers,\"Behind the Scenes\"}",
"SpecialFeatures": [
"Trailers",
"Behind the Scenes"
],
"Fulltext": "'administr':12 'boat':8 'boy':17 'databas':11 'first':20 'languag':2 'man':21 'meet':15 'must':14 'space':22 'station':23 'unbeliev':4 'yarn':5 'young':1",
"Language": {
"LanguageID": 1,
@ -444,8 +466,7 @@ fmt.Println(string(jsonText))
]
```
What if, we also want to have list of films per category and actors per category, where films are longer than 180 minutes, film language is 'English'
and film category is not 'Action'.
What if, we also want to have a list of films per category and actors per category, with the same search conditions.
In that case we can reuse above statement `stmt`, and just change our destination:
```go
@ -481,23 +502,12 @@ handleError(err)
"ReplacementCost": 24.99,
"Rating": "NC-17",
"LastUpdate": "2013-05-26T14:50:58.951Z",
"SpecialFeatures": "{Trailers,\"Deleted Scenes\",\"Behind the Scenes\"}",
"SpecialFeatures": [
"Trailers",
"Deleted Scenes",
"Behind the Scenes"
],
"Fulltext": "'action':5 'action-pack':4 'baloon':21 'boy':10 'chase':16 'evolut':2 'king':1 'lumberjack':13 'madman':18 'must':15 'pack':6 'tale':7"
},
{
"FilmID": 50,
"Title": "Baked Cleopatra",
"Description": "A Stunning Drama of a Forensic Psychologist And a Husband who must Overcome a Waitress in A Monastery",
"ReleaseYear": 2006,
"LanguageID": 1,
"RentalDuration": 3,
"RentalRate": 2.99,
"Length": 182,
"ReplacementCost": 20.99,
"Rating": "G",
"LastUpdate": "2013-05-26T14:50:58.951Z",
"SpecialFeatures": "{Commentaries,\"Behind the Scenes\"}",
"Fulltext": "'bake':1 'cleopatra':2 'drama':5 'forens':8 'husband':12 'monasteri':20 'must':14 'overcom':15 'psychologist':9 'stun':4 'waitress':17"
}
],
"Actors": [
@ -519,12 +529,6 @@ handleError(err)
"LastName": "Dukakis",
"LastUpdate": "2013-05-26T14:47:57.62Z"
},
{
"ActorID": 70,
"FirstName": "Michelle",
"LastName": "Mcconaughey",
"LastUpdate": "2013-05-26T14:47:57.62Z"
},
{
"ActorID": 118,
"FirstName": "Cuba",
@ -545,7 +549,7 @@ handleError(err)
}
]
},
//...
// ...
]
```
</details>

View file

@ -8,6 +8,7 @@
package model
import (
"github.com/lib/pq"
"time"
)
@ -23,6 +24,6 @@ type Film struct {
ReplacementCost float64
Rating *MpaaRating
LastUpdate time.Time
SpecialFeatures *string
SpecialFeatures *pq.StringArray
Fulltext string
}

View file

@ -19,6 +19,14 @@ const (
MpaaRating_Nc17 MpaaRating = "NC-17"
)
var MpaaRatingAllValues = []MpaaRating{
MpaaRating_G,
MpaaRating_Pg,
MpaaRating_Pg13,
MpaaRating_R,
MpaaRating_Nc17,
}
func (e *MpaaRating) Scan(value interface{}) error {
var enumValue string
switch val := value.(type) {

View file

@ -16,7 +16,7 @@ var Actor = newActorTable("dvds", "actor", "")
type actorTable struct {
postgres.Table
//Columns
// Columns
ActorID postgres.ColumnInteger
FirstName postgres.ColumnString
LastName postgres.ColumnString
@ -24,6 +24,7 @@ type actorTable struct {
AllColumns postgres.ColumnList
MutableColumns postgres.ColumnList
DefaultColumns postgres.ColumnList
}
type ActorTable struct {
@ -67,6 +68,7 @@ func newActorTableImpl(schemaName, tableName, alias string) actorTable {
LastUpdateColumn = postgres.TimestampColumn("last_update")
allColumns = postgres.ColumnList{ActorIDColumn, FirstNameColumn, LastNameColumn, LastUpdateColumn}
mutableColumns = postgres.ColumnList{FirstNameColumn, LastNameColumn, LastUpdateColumn}
defaultColumns = postgres.ColumnList{ActorIDColumn}
)
return actorTable{
@ -80,5 +82,6 @@ func newActorTableImpl(schemaName, tableName, alias string) actorTable {
AllColumns: allColumns,
MutableColumns: mutableColumns,
DefaultColumns: defaultColumns,
}
}

View file

@ -16,13 +16,14 @@ var Category = newCategoryTable("dvds", "category", "")
type categoryTable struct {
postgres.Table
//Columns
// Columns
CategoryID postgres.ColumnInteger
Name postgres.ColumnString
LastUpdate postgres.ColumnTimestamp
AllColumns postgres.ColumnList
MutableColumns postgres.ColumnList
DefaultColumns postgres.ColumnList
}
type CategoryTable struct {
@ -65,6 +66,7 @@ func newCategoryTableImpl(schemaName, tableName, alias string) categoryTable {
LastUpdateColumn = postgres.TimestampColumn("last_update")
allColumns = postgres.ColumnList{CategoryIDColumn, NameColumn, LastUpdateColumn}
mutableColumns = postgres.ColumnList{NameColumn, LastUpdateColumn}
defaultColumns = postgres.ColumnList{CategoryIDColumn, LastUpdateColumn}
)
return categoryTable{
@ -77,5 +79,6 @@ func newCategoryTableImpl(schemaName, tableName, alias string) categoryTable {
AllColumns: allColumns,
MutableColumns: mutableColumns,
DefaultColumns: defaultColumns,
}
}

View file

@ -16,7 +16,7 @@ var Film = newFilmTable("dvds", "film", "")
type filmTable struct {
postgres.Table
//Columns
// Columns
FilmID postgres.ColumnInteger
Title postgres.ColumnString
Description postgres.ColumnString
@ -28,11 +28,12 @@ type filmTable struct {
ReplacementCost postgres.ColumnFloat
Rating postgres.ColumnString
LastUpdate postgres.ColumnTimestamp
SpecialFeatures postgres.ColumnString
SpecialFeatures postgres.ColumnStringArray
Fulltext postgres.ColumnString
AllColumns postgres.ColumnList
MutableColumns postgres.ColumnList
DefaultColumns postgres.ColumnList
}
type FilmTable struct {
@ -81,10 +82,11 @@ func newFilmTableImpl(schemaName, tableName, alias string) filmTable {
ReplacementCostColumn = postgres.FloatColumn("replacement_cost")
RatingColumn = postgres.StringColumn("rating")
LastUpdateColumn = postgres.TimestampColumn("last_update")
SpecialFeaturesColumn = postgres.StringColumn("special_features")
SpecialFeaturesColumn = postgres.StringArrayColumn("special_features")
FulltextColumn = postgres.StringColumn("fulltext")
allColumns = postgres.ColumnList{FilmIDColumn, TitleColumn, DescriptionColumn, ReleaseYearColumn, LanguageIDColumn, RentalDurationColumn, RentalRateColumn, LengthColumn, ReplacementCostColumn, RatingColumn, LastUpdateColumn, SpecialFeaturesColumn, FulltextColumn}
mutableColumns = postgres.ColumnList{TitleColumn, DescriptionColumn, ReleaseYearColumn, LanguageIDColumn, RentalDurationColumn, RentalRateColumn, LengthColumn, ReplacementCostColumn, RatingColumn, LastUpdateColumn, SpecialFeaturesColumn, FulltextColumn}
defaultColumns = postgres.ColumnList{FilmIDColumn, RentalDurationColumn, RentalRateColumn, ReplacementCostColumn, RatingColumn, LastUpdateColumn}
)
return filmTable{
@ -107,5 +109,6 @@ func newFilmTableImpl(schemaName, tableName, alias string) filmTable {
AllColumns: allColumns,
MutableColumns: mutableColumns,
DefaultColumns: defaultColumns,
}
}

View file

@ -16,13 +16,14 @@ var FilmActor = newFilmActorTable("dvds", "film_actor", "")
type filmActorTable struct {
postgres.Table
//Columns
// Columns
ActorID postgres.ColumnInteger
FilmID postgres.ColumnInteger
LastUpdate postgres.ColumnTimestamp
AllColumns postgres.ColumnList
MutableColumns postgres.ColumnList
DefaultColumns postgres.ColumnList
}
type FilmActorTable struct {
@ -65,6 +66,7 @@ func newFilmActorTableImpl(schemaName, tableName, alias string) filmActorTable {
LastUpdateColumn = postgres.TimestampColumn("last_update")
allColumns = postgres.ColumnList{ActorIDColumn, FilmIDColumn, LastUpdateColumn}
mutableColumns = postgres.ColumnList{LastUpdateColumn}
defaultColumns = postgres.ColumnList{LastUpdateColumn}
)
return filmActorTable{
@ -77,5 +79,6 @@ func newFilmActorTableImpl(schemaName, tableName, alias string) filmActorTable {
AllColumns: allColumns,
MutableColumns: mutableColumns,
DefaultColumns: defaultColumns,
}
}

View file

@ -16,13 +16,14 @@ var FilmCategory = newFilmCategoryTable("dvds", "film_category", "")
type filmCategoryTable struct {
postgres.Table
//Columns
// Columns
FilmID postgres.ColumnInteger
CategoryID postgres.ColumnInteger
LastUpdate postgres.ColumnTimestamp
AllColumns postgres.ColumnList
MutableColumns postgres.ColumnList
DefaultColumns postgres.ColumnList
}
type FilmCategoryTable struct {
@ -65,6 +66,7 @@ func newFilmCategoryTableImpl(schemaName, tableName, alias string) filmCategoryT
LastUpdateColumn = postgres.TimestampColumn("last_update")
allColumns = postgres.ColumnList{FilmIDColumn, CategoryIDColumn, LastUpdateColumn}
mutableColumns = postgres.ColumnList{LastUpdateColumn}
defaultColumns = postgres.ColumnList{LastUpdateColumn}
)
return filmCategoryTable{
@ -77,5 +79,6 @@ func newFilmCategoryTableImpl(schemaName, tableName, alias string) filmCategoryT
AllColumns: allColumns,
MutableColumns: mutableColumns,
DefaultColumns: defaultColumns,
}
}

View file

@ -16,13 +16,14 @@ var Language = newLanguageTable("dvds", "language", "")
type languageTable struct {
postgres.Table
//Columns
// Columns
LanguageID postgres.ColumnInteger
Name postgres.ColumnString
LastUpdate postgres.ColumnTimestamp
AllColumns postgres.ColumnList
MutableColumns postgres.ColumnList
DefaultColumns postgres.ColumnList
}
type LanguageTable struct {
@ -65,6 +66,7 @@ func newLanguageTableImpl(schemaName, tableName, alias string) languageTable {
LastUpdateColumn = postgres.TimestampColumn("last_update")
allColumns = postgres.ColumnList{LanguageIDColumn, NameColumn, LastUpdateColumn}
mutableColumns = postgres.ColumnList{NameColumn, LastUpdateColumn}
defaultColumns = postgres.ColumnList{LanguageIDColumn, LastUpdateColumn}
)
return languageTable{
@ -77,5 +79,6 @@ func newLanguageTableImpl(schemaName, tableName, alias string) languageTable {
AllColumns: allColumns,
MutableColumns: mutableColumns,
DefaultColumns: defaultColumns,
}
}

View file

@ -16,7 +16,7 @@ var ActorInfo = newActorInfoTable("dvds", "actor_info", "")
type actorInfoTable struct {
postgres.Table
//Columns
// Columns
ActorID postgres.ColumnInteger
FirstName postgres.ColumnString
LastName postgres.ColumnString
@ -24,6 +24,7 @@ type actorInfoTable struct {
AllColumns postgres.ColumnList
MutableColumns postgres.ColumnList
DefaultColumns postgres.ColumnList
}
type ActorInfoTable struct {
@ -67,6 +68,7 @@ func newActorInfoTableImpl(schemaName, tableName, alias string) actorInfoTable {
FilmInfoColumn = postgres.StringColumn("film_info")
allColumns = postgres.ColumnList{ActorIDColumn, FirstNameColumn, LastNameColumn, FilmInfoColumn}
mutableColumns = postgres.ColumnList{ActorIDColumn, FirstNameColumn, LastNameColumn, FilmInfoColumn}
defaultColumns = postgres.ColumnList{}
)
return actorInfoTable{
@ -80,5 +82,6 @@ func newActorInfoTableImpl(schemaName, tableName, alias string) actorInfoTable {
AllColumns: allColumns,
MutableColumns: mutableColumns,
DefaultColumns: defaultColumns,
}
}

View file

@ -16,7 +16,7 @@ var CustomerList = newCustomerListTable("dvds", "customer_list", "")
type customerListTable struct {
postgres.Table
//Columns
// Columns
ID postgres.ColumnInteger
Name postgres.ColumnString
Address postgres.ColumnString
@ -29,6 +29,7 @@ type customerListTable struct {
AllColumns postgres.ColumnList
MutableColumns postgres.ColumnList
DefaultColumns postgres.ColumnList
}
type CustomerListTable struct {
@ -77,6 +78,7 @@ func newCustomerListTableImpl(schemaName, tableName, alias string) customerListT
SidColumn = postgres.IntegerColumn("sid")
allColumns = postgres.ColumnList{IDColumn, NameColumn, AddressColumn, ZipCodeColumn, PhoneColumn, CityColumn, CountryColumn, NotesColumn, SidColumn}
mutableColumns = postgres.ColumnList{IDColumn, NameColumn, AddressColumn, ZipCodeColumn, PhoneColumn, CityColumn, CountryColumn, NotesColumn, SidColumn}
defaultColumns = postgres.ColumnList{}
)
return customerListTable{
@ -95,5 +97,6 @@ func newCustomerListTableImpl(schemaName, tableName, alias string) customerListT
AllColumns: allColumns,
MutableColumns: mutableColumns,
DefaultColumns: defaultColumns,
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -35,9 +35,9 @@ func main() {
// Write query
stmt := SELECT(
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate,
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate, // or just Actor.AllColumns
Film.AllColumns,
Language.AllColumns.Except(Language.LastUpdate),
Language.AllColumns.Except(Language.LastUpdate), // all language columns except last_update
Category.AllColumns,
).FROM(
Actor.
@ -47,10 +47,13 @@ func main() {
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
).WHERE(
Language.Name.EQ(Char(20)("English")).
AND(Category.Name.NOT_EQ(Text("Action"))).
AND(Film.Length.GT(Int(180))).
AND(Film.Rating.NOT_EQ(enum.MpaaRating.R)),
AND(
Language.Name.EQ(Char(20)("English")), // string columns Language.Name and Category.Name can be compared only with string expression
Category.Name.NOT_EQ(Text("Action")),
Film.Length.GT(Int32(180)), // Film.Length is integer column and can be compared only with integer expression
Film.Rating.NOT_EQ(enum.MpaaRating.R),
String("Trailers").EQ(ANY(Film.SpecialFeatures)), // type safety is also enforced on array element types
),
).ORDER_BY(
Actor.ActorID.ASC(),
Film.FilmID.ASC(),

View file

@ -19,7 +19,6 @@ const (
BaseType DataTypeKind = "base"
EnumType DataTypeKind = "enum"
UserDefinedType DataTypeKind = "user-defined"
ArrayType DataTypeKind = "array"
RangeType DataTypeKind = "range"
)
@ -30,3 +29,7 @@ type DataType struct {
IsUnsigned bool
Dimensions int // The number of array dimensions
}
func (d DataType) IsArray() bool {
return d.Dimensions > 0
}

View file

@ -66,16 +66,17 @@ select
not attr.attnotnull as "column.isNullable",
attr.attgenerated = 's' as "column.isGenerated",
attr.atthasdef as "column.hasDefault",
attr.attndims as "dataType.dimensions",
(case
when tp.typtype = 'b' AND tp.typcategory <> 'A' then 'base'
when tp.typtype = 'b' AND tp.typcategory = 'A' then 'array'
when tp.typtype = 'd' then 'base'
when tp.typtype = 'e' then 'enum'
when tp.typtype = 'r' then 'range'
(case when tp.typcategory = 'A' then greatest(1, attr.attndims) --cockroach num dims fix
else 0
end) as "dataType.dimensions",
(case coalesce(elem.typtype, tp.typtype)
when 'b' then 'base'
when 'd' then 'base'
when 'e' then 'enum'
when 'r' then 'range'
end) as "dataType.Kind",
(case when tp.typtype = 'd' then (select pg_type.typname from pg_catalog.pg_type where pg_type.oid = tp.typbasetype)
when tp.typcategory = 'A' then pg_catalog.format_type(attr.atttypid, attr.atttypmod)
when tp.typcategory = 'A' then elem.typname
else tp.typname
end) as "dataType.Name",
false as "dataType.isUnsigned"
@ -83,6 +84,7 @@ from pg_catalog.pg_attribute as attr
join pg_catalog.pg_class as cls on cls.oid = attr.attrelid
join pg_catalog.pg_namespace as ns on ns.oid = cls.relnamespace
join pg_catalog.pg_type as tp on tp.oid = attr.atttypid
left join pg_catalog.pg_type elem ON tp.typelem = elem.oid -- only for arrays
where
ns.nspname = $1 and
cls.relname = $2 and

View file

@ -2,15 +2,17 @@ package template
import (
"fmt"
"github.com/go-jet/jet/v2/generator/metadata"
"github.com/go-jet/jet/v2/internal/utils/dbidentifier"
"github.com/google/uuid"
"github.com/jackc/pgtype"
"github.com/lib/pq"
"path/filepath"
"reflect"
"strings"
"time"
"github.com/google/uuid"
"github.com/jackc/pgtype"
"github.com/go-jet/jet/v2/generator/metadata"
"github.com/go-jet/jet/v2/internal/utils/dbidentifier"
)
// Model is template for model files generation
@ -246,10 +248,21 @@ func getType(columnMetadata metadata.Column) Type {
userDefinedType := getUserDefinedType(columnMetadata)
if userDefinedType != "" {
if columnMetadata.IsNullable {
return Type{Name: "*" + userDefinedType}
var importPath string
if columnMetadata.DataType.IsArray() {
userDefinedType = "pq.StringArray"
importPath = "github.com/lib/pq"
}
if columnMetadata.IsNullable {
userDefinedType = "*" + userDefinedType
}
return Type{
Name: userDefinedType,
ImportPath: importPath,
}
return Type{Name: userDefinedType}
}
return NewType(getGoType(columnMetadata))
@ -267,21 +280,44 @@ func getUserDefinedType(column metadata.Column) string {
}
func getGoType(column metadata.Column) interface{} {
defaultGoType := toGoType(column)
goType := toGoType(column)
if column.IsNullable {
return reflect.New(reflect.TypeOf(defaultGoType)).Interface()
if column.DataType.IsArray() {
goType = toGoArrayType(goType, column)
}
return defaultGoType
if column.IsNullable {
return reflect.New(reflect.TypeOf(goType)).Interface()
}
return goType
}
func toGoArrayType(elemType any, column metadata.Column) any {
if column.DataType.Dimensions > 1 {
return "" // unsupported multidimensional arrays
}
switch elemType.(type) {
case bool:
return pq.BoolArray{}
case int32:
return pq.Int32Array{}
case int64:
return pq.Int64Array{}
case float32:
return pq.Float32Array{}
case float64:
return pq.Float64Array{}
case []byte:
return pq.ByteaArray{}
default:
return pq.StringArray{}
}
}
// toGoType returns model type for column info.
func toGoType(column metadata.Column) interface{} {
// We don't support multi-dimensional arrays
if column.DataType.Dimensions > 1 {
return ""
}
switch strings.ToLower(column.DataType.Name) {
case "user-defined", "enum":
@ -348,14 +384,6 @@ func toGoType(column metadata.Column) interface{} {
return pgtype.Int8range{}
case "numrange":
return pgtype.Numrange{}
case "bool[]":
return pq.BoolArray{}
case "integer[]", "int4[]":
return pq.Int32Array{}
case "bigint[]":
return pq.Int64Array{}
case "text[]", "jsonb[]", "json[]":
return pq.StringArray{}
default:
fmt.Println("- [Model ] Unsupported sql column '" + column.Name + " " + column.DataType.Name + "', using string instead.")
return ""

View file

@ -162,43 +162,33 @@ func DefaultTableSQLBuilderColumn(columnMetaData metadata.Column) TableSQLBuilde
// getSqlBuilderColumnType returns type of jet sql builder column
func getSqlBuilderColumnType(columnMetaData metadata.Column) string {
if columnMetaData.DataType.Kind != metadata.BaseType &&
columnMetaData.DataType.Kind != metadata.RangeType &&
columnMetaData.DataType.Kind != metadata.ArrayType {
switch columnMetaData.DataType.Kind {
case metadata.EnumType, metadata.UserDefinedType:
if columnMetaData.DataType.IsArray() {
return "StringArray"
}
return "String"
}
typeName := columnMetaData.DataType.Name
columnName := columnMetaData.Name
columnType := sqlToColumnType(columnMetaData)
if columnMetaData.DataType.Kind == metadata.ArrayType {
if columnMetaData.DataType.IsArray() {
if columnMetaData.DataType.Dimensions > 1 {
fmt.Println("- [SQL Builder] Unsupported sql array with multiple dimensions column '" + columnName + " " + typeName + "', using StringColumn instead.")
fmt.Println("- [SQL Builder] Unsupported sql array with multiple dimensions column '" +
columnMetaData.Name + " " + columnMetaData.DataType.Name + "', using StringColumn instead.")
return "String"
}
c := sqlToColumnType(strings.TrimSuffix(typeName, "[]"))
// These are the supported array types
if slices.Index([]string{"Bool", "String", "Integer"}, c) == -1 {
fmt.Println("- [SQL Builder] Unsupported sql array column '" + columnName + " " + typeName + "', using StringColumn instead.")
return "String"
}
return c + "Array"
}
columnType := sqlToColumnType(typeName)
if columnType == "" {
fmt.Println("- [SQL Builder] Unsupported sql column '" + columnName + " " + typeName + "', using StringColumn instead.")
return "String"
columnType = columnType + "Array"
}
return columnType
}
func sqlToColumnType(typeName string) string {
switch strings.ToLower(typeName) {
// sqlToColumnType maps the type of a SQL column type to a go jet sql builder column. The second return value returns
// whether the given type is supported.
func sqlToColumnType(columnMetaData metadata.Column) string {
switch strings.ToLower(columnMetaData.DataType.Name) {
case "boolean", "bool":
return "Bool"
case "smallint", "integer", "bigint", "int2", "int4", "int8",
@ -243,7 +233,8 @@ func sqlToColumnType(typeName string) string {
case "numrange":
return "NumericRange"
default:
return ""
fmt.Println("- [SQL Builder] Unsupported sql column '" + columnMetaData.Name + " " + columnMetaData.DataType.Name + "', using StringColumn instead.")
return "String"
}
}

View file

@ -1,77 +1,77 @@
package jet
// ArrayExpression interface
type ArrayExpression[E Expression] interface {
// Array interface
type Array[E Expression] interface {
Expression
EQ(rhs ArrayExpression[E]) BoolExpression
NOT_EQ(rhs ArrayExpression[E]) BoolExpression
LT(rhs ArrayExpression[E]) BoolExpression
GT(rhs ArrayExpression[E]) BoolExpression
LT_EQ(rhs ArrayExpression[E]) BoolExpression
GT_EQ(rhs ArrayExpression[E]) BoolExpression
EQ(rhs Array[E]) BoolExpression
NOT_EQ(rhs Array[E]) BoolExpression
LT(rhs Array[E]) BoolExpression
GT(rhs Array[E]) BoolExpression
LT_EQ(rhs Array[E]) BoolExpression
GT_EQ(rhs Array[E]) BoolExpression
CONTAINS(rhs ArrayExpression[E]) BoolExpression
IS_CONTAINED_BY(rhs ArrayExpression[E]) BoolExpression
OVERLAP(rhs ArrayExpression[E]) BoolExpression
CONCAT(rhs ArrayExpression[E]) ArrayExpression[E]
CONCAT_ELEMENT(E) ArrayExpression[E]
CONTAINS(rhs Array[E]) BoolExpression
IS_CONTAINED_BY(rhs Array[E]) BoolExpression
OVERLAP(rhs Array[E]) BoolExpression
CONCAT(rhs Array[E]) Array[E]
CONCAT_ELEMENT(E) Array[E]
AT(expression IntegerExpression) Expression
AT(expression IntegerExpression) E
}
type arrayInterfaceImpl[E Expression] struct {
parent ArrayExpression[E]
parent Array[E]
}
type BinaryBoolOp func(Expression, Expression) BoolExpression
func (a arrayInterfaceImpl[E]) EQ(rhs ArrayExpression[E]) BoolExpression {
func (a arrayInterfaceImpl[E]) EQ(rhs Array[E]) BoolExpression {
return Eq(a.parent, rhs)
}
func (a arrayInterfaceImpl[E]) NOT_EQ(rhs ArrayExpression[E]) BoolExpression {
func (a arrayInterfaceImpl[E]) NOT_EQ(rhs Array[E]) BoolExpression {
return NotEq(a.parent, rhs)
}
func (a arrayInterfaceImpl[E]) LT(rhs ArrayExpression[E]) BoolExpression {
func (a arrayInterfaceImpl[E]) LT(rhs Array[E]) BoolExpression {
return Lt(a.parent, rhs)
}
func (a arrayInterfaceImpl[E]) GT(rhs ArrayExpression[E]) BoolExpression {
func (a arrayInterfaceImpl[E]) GT(rhs Array[E]) BoolExpression {
return Gt(a.parent, rhs)
}
func (a arrayInterfaceImpl[E]) LT_EQ(rhs ArrayExpression[E]) BoolExpression {
func (a arrayInterfaceImpl[E]) LT_EQ(rhs Array[E]) BoolExpression {
return LtEq(a.parent, rhs)
}
func (a arrayInterfaceImpl[E]) GT_EQ(rhs ArrayExpression[E]) BoolExpression {
func (a arrayInterfaceImpl[E]) GT_EQ(rhs Array[E]) BoolExpression {
return GtEq(a.parent, rhs)
}
func (a arrayInterfaceImpl[E]) CONTAINS(rhs ArrayExpression[E]) BoolExpression {
func (a arrayInterfaceImpl[E]) CONTAINS(rhs Array[E]) BoolExpression {
return Contains(a.parent, rhs)
}
func (a arrayInterfaceImpl[E]) IS_CONTAINED_BY(rhs ArrayExpression[E]) BoolExpression {
func (a arrayInterfaceImpl[E]) IS_CONTAINED_BY(rhs Array[E]) BoolExpression {
return IsContainedBy(a.parent, rhs)
}
func (a arrayInterfaceImpl[E]) OVERLAP(rhs ArrayExpression[E]) BoolExpression {
func (a arrayInterfaceImpl[E]) OVERLAP(rhs Array[E]) BoolExpression {
return Overlap(a.parent, rhs)
}
func (a arrayInterfaceImpl[E]) CONCAT(rhs ArrayExpression[E]) ArrayExpression[E] {
func (a arrayInterfaceImpl[E]) CONCAT(rhs Array[E]) Array[E] {
return ArrayExp[E](NewBinaryOperatorExpression(a.parent, rhs, "||"))
}
func (a arrayInterfaceImpl[E]) CONCAT_ELEMENT(rhs E) ArrayExpression[E] {
func (a arrayInterfaceImpl[E]) CONCAT_ELEMENT(rhs E) Array[E] {
return ArrayExp[E](NewBinaryOperatorExpression(a.parent, rhs, "||"))
}
func (a arrayInterfaceImpl[E]) AT(expression IntegerExpression) Expression {
return arraySubscriptExpr(a.parent, expression)
func (a arrayInterfaceImpl[E]) AT(at IntegerExpression) E {
return CastToArrayElemType[E](a.parent, CustomExpression(a.parent, Token("["), at, Token("]")))
}
type arrayExpressionWrapper[E Expression] struct {
@ -79,7 +79,7 @@ type arrayExpressionWrapper[E Expression] struct {
Expression
}
func newArrayExpressionWrap[E Expression](expression Expression) ArrayExpression[E] {
func newArrayExpressionWrap[E Expression](expression Expression) Array[E] {
arrayExpressionWrapper := arrayExpressionWrapper[E]{Expression: expression}
arrayExpressionWrapper.arrayInterfaceImpl.parent = &arrayExpressionWrapper
return &arrayExpressionWrapper
@ -88,6 +88,49 @@ func newArrayExpressionWrap[E Expression](expression Expression) ArrayExpression
// ArrayExp is array expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as array expression.
// Does not add sql cast to generated sql builder output.
func ArrayExp[E Expression](expression Expression) ArrayExpression[E] {
func ArrayExp[E Expression](expression Expression) Array[E] {
return newArrayExpressionWrap[E](expression)
}
// CastToArrayElemType casts exp to array element type
func CastToArrayElemType[E Expression](array Array[E], exp Expression) E {
var i Expression
switch array.(type) {
case Array[BoolExpression]:
i = BoolExp(exp)
case Array[StringExpression]:
i = StringExp(exp)
case Array[IntegerExpression]:
i = IntExp(exp)
case Array[FloatExpression]:
i = FloatExp(exp)
case Array[BlobExpression]:
i = BlobExp(exp)
case Array[DateExpression]:
i = DateExp(exp)
case Array[TimestampExpression]:
i = TimestampExp(exp)
case Array[TimestampzExpression]:
i = TimestampzExp(exp)
case Array[TimeExpression]:
i = TimeExp(exp)
case Array[TimezExpression]:
i = TimezExp(exp)
case Array[IntervalExpression]:
i = IntervalExp(exp)
}
return i.(E)
}
// ARRAY constructor builds an array value using list of expressions.
func ARRAY[E Expression](elems ...E) Array[E] {
var args = make([]Serializer, len(elems))
for i, each := range elems {
args[i] = each
}
return ArrayExp[E](CustomExpression(Token("ARRAY["), ListSerializer{
Serializers: args,
Separator: ",",
}, Token("]")))
}

View file

@ -1,7 +1,6 @@
package jet
import (
"github.com/lib/pq"
"testing"
)
@ -11,7 +10,6 @@ func TestArrayExpressionEQ(t *testing.T) {
func TestArrayExpressionNOT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColStringArray.NOT_EQ(table2ColArray), "(table1.col_array_string != table2.col_array_string)")
assertClauseSerialize(t, table1ColStringArray.NOT_EQ(StringArray([]string{"x"})), "(table1.col_array_string != $1)", pq.StringArray{"x"})
}
func TestArrayExpressionLT(t *testing.T) {
@ -32,12 +30,10 @@ func TestArrayExpressionGT_EQ(t *testing.T) {
func TestArrayExpressionCONTAINS(t *testing.T) {
assertClauseSerialize(t, table1ColStringArray.CONTAINS(table2ColArray), "(table1.col_array_string @> table2.col_array_string)")
assertClauseSerialize(t, table1ColStringArray.CONTAINS(StringArray([]string{"x"})), "(table1.col_array_string @> $1)", pq.StringArray{"x"})
}
func TestArrayExpressionCONTAINED_BY(t *testing.T) {
assertClauseSerialize(t, table1ColStringArray.IS_CONTAINED_BY(table2ColArray), "(table1.col_array_string <@ table2.col_array_string)")
assertClauseSerialize(t, table1ColStringArray.IS_CONTAINED_BY(StringArray([]string{"x"})), "(table1.col_array_string <@ $1)", pq.StringArray{"x"})
}
func TestArrayExpressionOVERLAP(t *testing.T) {
@ -46,14 +42,27 @@ func TestArrayExpressionOVERLAP(t *testing.T) {
func TestArrayExpressionCONCAT(t *testing.T) {
assertClauseSerialize(t, table1ColStringArray.CONCAT(table2ColArray), "(table1.col_array_string || table2.col_array_string)")
assertClauseSerialize(t, table1ColStringArray.CONCAT(StringArray([]string{"x"})), "(table1.col_array_string || $1)", pq.StringArray{"x"})
}
func TestArrayExpressionCONCAT_ELEMENT(t *testing.T) {
assertClauseSerialize(t, table1ColStringArray.CONCAT_ELEMENT(StringExp(table2ColArray.AT(Int(1)))), "(table1.col_array_string || (table2.col_array_string[$1]))", int64(1))
assertClauseSerialize(t, table1ColStringArray.CONCAT_ELEMENT(StringExp(table2ColArray.AT(Int(1)))), "(table1.col_array_string || table2.col_array_string[$1])", int64(1))
assertClauseSerialize(t, table1ColStringArray.CONCAT_ELEMENT(String("x")), "(table1.col_array_string || $1)", "x")
}
func TestArrayExpressionAT(t *testing.T) {
assertClauseSerialize(t, table1ColStringArray.AT(Int(1)), "(table1.col_array_string[$1])", int64(1))
assertClauseSerialize(t, table1ColStringArray.AT(Int(1)), "table1.col_array_string[$1]", int64(1))
}
func TestCastToArrayElemType(t *testing.T) {
var _ BoolExpression = CastToArrayElemType[BoolExpression](ARRAY[BoolExpression](), table1Col1)
var _ IntegerExpression = CastToArrayElemType[IntegerExpression](ARRAY[IntegerExpression](), table1Col1)
var _ FloatExpression = CastToArrayElemType[FloatExpression](ARRAY[FloatExpression](), table1Col1)
var _ StringExpression = CastToArrayElemType[StringExpression](ARRAY[StringExpression](), table1Col1)
var _ BlobExpression = CastToArrayElemType[BlobExpression](ARRAY[BlobExpression](), table1Col1)
var _ DateExpression = CastToArrayElemType[DateExpression](ARRAY[DateExpression](), table1Col1)
var _ TimestampExpression = CastToArrayElemType[TimestampExpression](ARRAY[TimestampExpression](), table1Col1)
var _ TimestampzExpression = CastToArrayElemType[TimestampzExpression](ARRAY[TimestampzExpression](), table1Col1)
var _ TimeExpression = CastToArrayElemType[TimeExpression](ARRAY[TimeExpression](), table1Col1)
var _ TimezExpression = CastToArrayElemType[TimezExpression](ARRAY[TimezExpression](), table1Col1)
var _ IntervalExpression = CastToArrayElemType[IntervalExpression](ARRAY[IntervalExpression](), table1Col1)
}

View file

@ -134,17 +134,21 @@ func IntegerColumn(name string) ColumnInteger {
//------------------------------------------------------//
type ColumnArray[E Expression] interface {
ArrayExpression[E]
Array[E]
Column
From(subQuery SelectTable) ColumnArray[E]
SET(stringExp ArrayExpression[E]) ColumnAssigment
SET(stringExp Array[E]) ColumnAssigment
}
type arrayColumnImpl[E Expression] struct {
arrayInterfaceImpl[E]
ColumnExpressionImpl
*ColumnExpressionImpl
}
func (a arrayColumnImpl[E]) fromImpl(subQuery SelectTable) Projection {
return a.From(subQuery)
}
func (a arrayColumnImpl[E]) From(subQuery SelectTable) ColumnArray[E] {
@ -155,10 +159,10 @@ func (a arrayColumnImpl[E]) From(subQuery SelectTable) ColumnArray[E] {
return newArrayColumn
}
func (a *arrayColumnImpl[E]) SET(stringExp ArrayExpression[E]) ColumnAssigment {
func (a *arrayColumnImpl[E]) SET(stringExp Array[E]) ColumnAssigment {
return columnAssigmentImpl{
column: a,
expression: stringExp,
toAssign: stringExp,
}
}

View file

@ -1,7 +1,6 @@
package jet
import (
"github.com/lib/pq"
"testing"
)
@ -12,36 +11,30 @@ var subQuery = &selectTableImpl{
func TestNewArrayColumnString(t *testing.T) {
stringArrayColumn := ArrayColumn[StringExpression]("colArray").From(subQuery)
assertClauseSerialize(t, stringArrayColumn, `sub_query."colArray"`)
assertClauseSerialize(t, stringArrayColumn.EQ(StringArray([]string{"X"})), `(sub_query."colArray" = $1)`, pq.StringArray{"X"})
assertProjectionSerialize(t, stringArrayColumn, `sub_query."colArray" AS "colArray"`)
arrayColumn2 := table1ColStringArray.From(subQuery)
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_string"`)
assertClauseSerialize(t, arrayColumn2.EQ(StringArray([]string{"X"})), `(sub_query."table1.col_array_string" = $1)`, pq.StringArray{"X"})
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_string" AS "table1.col_array_string"`)
}
func TestNewArrayColumnBool(t *testing.T) {
boolArrayColumn := ArrayColumn[BoolExpression]("colArrayBool").From(subQuery)
assertClauseSerialize(t, boolArrayColumn, `sub_query."colArrayBool"`)
assertClauseSerialize(t, boolArrayColumn.EQ(BoolArray([]bool{true})), `(sub_query."colArrayBool" = $1)`, pq.BoolArray{true})
assertProjectionSerialize(t, boolArrayColumn, `sub_query."colArrayBool" AS "colArrayBool"`)
arrayColumn2 := table1ColBoolArray.From(subQuery)
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_bool"`)
assertClauseSerialize(t, arrayColumn2.EQ(BoolArray([]bool{true})), `(sub_query."table1.col_array_bool" = $1)`, pq.BoolArray{true})
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_bool" AS "table1.col_array_bool"`)
}
func TestNewArrayColumnInteger(t *testing.T) {
intArrayColumn := ArrayColumn[IntegerExpression]("colArrayInt").From(subQuery)
assertClauseSerialize(t, intArrayColumn, `sub_query."colArrayInt"`)
assertClauseSerialize(t, intArrayColumn.EQ(Int32Array([]int32{42})), `(sub_query."colArrayInt" = $1)`, pq.Int32Array{42})
assertProjectionSerialize(t, intArrayColumn, `sub_query."colArrayInt" AS "colArrayInt"`)
arrayColumn2 := table1ColIntArray.From(subQuery)
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_int"`)
assertClauseSerialize(t, arrayColumn2.EQ(Int32Array([]int32{42})), `(sub_query."table1.col_array_int" = $1)`, pq.Int32Array{42})
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_int" AS "table1.col_array_int"`)
}

View file

@ -341,42 +341,3 @@ func (s *complexExpression) serialize(statement StatementType, out *SQLBuilder,
func wrap(expressions ...Expression) Expression {
return NewFunc("", expressions, nil)
}
type arraySubscriptExpression struct {
ExpressionInterfaceImpl
array Expression
subscript IntegerExpression
}
func (a arraySubscriptExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if !contains(options, NoWrap) {
out.WriteString("(")
}
a.array.serialize(statement, out, FallTrough(options)...) // FallTrough here because complexExpression is just a wrapper
out.WriteString("[")
a.subscript.serialize(statement, out, FallTrough(options)...) // FallTrough here because complexExpression is just a wrapper
out.WriteString("]")
if !contains(options, NoWrap) {
out.WriteString(")")
}
}
func arraySubscriptExpr(array Expression, subscript IntegerExpression) Expression {
arraySubscriptExpression := &arraySubscriptExpression{array: array, subscript: subscript}
arraySubscriptExpression.ExpressionInterfaceImpl.Parent = arraySubscriptExpression
return arraySubscriptExpression
}
type skipParenthesisWrap struct {
Expression
}
func skipWrap(expression Expression) Expression {
return &skipParenthesisWrap{expression}
}
// since the expression is a function parameter, there is no need to wrap it in parentheses
func (s *skipParenthesisWrap) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
s.Expression.serialize(statement, out, append(options, NoWrap)...)
}

View file

@ -2,7 +2,6 @@ package jet
import (
"fmt"
"github.com/lib/pq"
"time"
)
@ -161,66 +160,6 @@ func Decimal(value string) FloatExpression {
return &floatLiteral
}
// ---------------------------------------------------//
type boolArrayLiteral struct {
arrayInterfaceImpl[BoolExpression]
literalExpressionImpl
}
func BoolArray(values []bool) ArrayExpression[BoolExpression] {
l := boolArrayLiteral{}
l.literalExpressionImpl = *literal(pq.BoolArray(values))
l.arrayInterfaceImpl.parent = &l
return &l
}
type integerArrayLiteral struct {
arrayInterfaceImpl[IntegerExpression]
literalExpressionImpl
}
func Int64Array(values []int64) ArrayExpression[IntegerExpression] {
l := integerArrayLiteral{}
l.literalExpressionImpl = *literal(pq.Int64Array(values))
l.arrayInterfaceImpl.parent = &l
return &l
}
func Int32Array(values []int32) ArrayExpression[IntegerExpression] {
l := integerArrayLiteral{}
l.literalExpressionImpl = *literal(pq.Int32Array(values))
l.arrayInterfaceImpl.parent = &l
return &l
}
type stringArrayLiteral struct {
arrayInterfaceImpl[StringExpression]
literalExpressionImpl
}
func StringArray(values []string) ArrayExpression[StringExpression] {
l := stringArrayLiteral{}
l.literalExpressionImpl = *literal(pq.StringArray(values))
l.arrayInterfaceImpl.parent = &l
return &l
}
type unsafeArrayLiteral[E Expression] struct {
arrayInterfaceImpl[E]
literalExpressionImpl
}
func UnsafeArray[E LiteralExpression](values []interface{}) ArrayExpression[E] {
l := unsafeArrayLiteral[E]{}
l.literalExpressionImpl = *literal(pq.Array(values))
l.arrayInterfaceImpl.parent = &l
return &l
}
// ---------------------------------------------------//
type stringLiteral struct {
stringInterfaceImpl

View file

@ -22,15 +22,6 @@ func BIT_NOT(expr IntegerExpression) IntegerExpression {
return newPrefixIntegerOperatorExpression(expr, "~")
}
// ----------- Array operators -------------- //
func Any(lhs Expression, op BinaryBoolOp, rhs Expression) BoolExpression {
return op(lhs, Func("ANY", rhs))
}
func All(lhs Expression, op BinaryBoolOp, rhs Expression) BoolExpression {
return op(lhs, Func("ALL", rhs))
}
//----------- Comparison operators ---------------//
// EXISTS checks for existence of the rows in subQuery

View file

@ -4,16 +4,15 @@ import (
"bytes"
"database/sql/driver"
"fmt"
"github.com/go-jet/jet/v2/internal/3rdparty/pq"
"github.com/go-jet/jet/v2/internal/utils/is"
"github.com/google/uuid"
"reflect"
"sort"
"strconv"
"strings"
"time"
"unicode"
"github.com/go-jet/jet/v2/internal/3rdparty/pq"
"github.com/go-jet/jet/v2/internal/utils/is"
"github.com/google/uuid"
)
// SQLBuilder generates output SQL
@ -249,8 +248,6 @@ func (s *SQLBuilder) argToString(value interface{}) string {
case string:
return stringQuote(bindVal)
case []string:
return stringArrayQuote(bindVal)
case []byte:
return stringQuote(string(bindVal))
case uuid.UUID:
@ -278,19 +275,6 @@ func (s *SQLBuilder) argToString(value interface{}) string {
}
}
func stringArrayQuote(val []string) string {
var sb strings.Builder
sb.WriteString(`'{`)
for i := 0; i < len(val); i++ {
if i > 0 {
sb.WriteString(`, `)
}
sb.WriteString(stringDoubleQuote(val[i]))
}
sb.WriteString(`}'`)
return sb.String()
}
func integerTypesToString(value interface{}) string {
switch bindVal := value.(type) {
case int:
@ -339,7 +323,3 @@ func shouldQuoteIdentifier(identifier string) bool {
func stringQuote(value string) string {
return `'` + strings.Replace(value, "'", "''", -1) + `'`
}
func stringDoubleQuote(value string) string {
return `"` + strings.Replace(value, `"`, `""`, -1) + `"`
}

View file

@ -17,9 +17,6 @@ type StringExpression interface {
BETWEEN(min, max StringExpression) BoolExpression
NOT_BETWEEN(min, max StringExpression) BoolExpression
ANY_EQ(rhs ArrayExpression[StringExpression]) BoolExpression
ALL_EQ(rhs ArrayExpression[StringExpression]) BoolExpression
CONCAT(rhs Expression) StringExpression
LIKE(pattern StringExpression) BoolExpression
@ -75,14 +72,6 @@ func (s *stringInterfaceImpl) NOT_BETWEEN(min, max StringExpression) BoolExpress
return NewBetweenOperatorExpression(s.root, min, max, true)
}
func (i *stringInterfaceImpl) ANY_EQ(rhs ArrayExpression[StringExpression]) BoolExpression {
return Any(i.parent, Eq, rhs)
}
func (i *stringInterfaceImpl) ALL_EQ(rhs ArrayExpression[StringExpression]) BoolExpression {
return All(i.parent, Eq, rhs)
}
func (s *stringInterfaceImpl) CONCAT(rhs Expression) StringExpression {
return newBinaryStringOperatorExpression(s.root, rhs, StringConcatOperator)
}

View file

@ -76,14 +76,6 @@ func TestStringNOT_REGEXP_LIKE(t *testing.T) {
assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 NOT REGEXP $1)", "JOHN")
}
func TestStringANY_EQ(t *testing.T) {
assertClauseSerialize(t, table2ColStr.ANY_EQ(table1ColStringArray), "(table2.col_str = ANY(table1.col_array_string))")
}
func TestStringALL_EQ(t *testing.T) {
assertClauseSerialize(t, table2ColStr.ALL_EQ(table1ColStringArray), "(table2.col_str = ALL(table1.col_array_string))")
}
func TestStringExp(t *testing.T) {
assertClauseSerialize(t, StringExp(table2ColFloat), "table2.col_float")
assertClauseSerialize(t, StringExp(table2ColFloat).NOT_LIKE(String("abc")), "(table2.col_float NOT LIKE $1)", "abc")

View file

@ -299,6 +299,16 @@ func AssertFileNamesEqual(t *testing.T, dirPath string, fileNames ...string) {
}
}
// DeepCopy create deep copy of src
func DeepCopy[T any](t require.TestingT, src T) T {
var dst T
data, err := json.Marshal(src)
require.NoError(t, err)
err = json.Unmarshal(data, &dst)
require.NoError(t, err)
return dst
}
// AssertDeepEqual checks if actual and expected objects are deeply equal.
func AssertDeepEqual(t require.TestingT, actual, expected interface{}, option ...cmp.Option) {
if !assert.True(t, cmp.Equal(actual, expected, option...)) {

33
postgres/array_columns.go Executable file
View file

@ -0,0 +1,33 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// Interfaces for different postgres array column types
type (
ColumnBoolArray jet.ColumnArray[BoolExpression]
ColumnStringArray jet.ColumnArray[StringExpression]
ColumnIntegerArray jet.ColumnArray[IntegerExpression]
ColumnFloatArray jet.ColumnArray[FloatExpression]
ColumnByteaArray jet.ColumnArray[ByteaExpression]
ColumnDateArray jet.ColumnArray[DateExpression]
ColumnTimestampArray jet.ColumnArray[TimestampExpression]
ColumnTimestampzArray jet.ColumnArray[TimestampzExpression]
ColumnTimeArray jet.ColumnArray[TimeExpression]
ColumnTimezArray jet.ColumnArray[TimezExpression]
ColumnIntervalArray jet.ColumnArray[IntervalExpression]
)
// Column constructors for different postgres array column types
var (
BoolArrayColumn = jet.ArrayColumn[BoolExpression]
StringArrayColumn = jet.ArrayColumn[StringExpression]
IntegerArrayColumn = jet.ArrayColumn[IntegerExpression]
FloatArrayColumn = jet.ArrayColumn[FloatExpression]
ByteaArrayColumn = jet.ArrayColumn[ByteaExpression]
DateArrayColumn = jet.ArrayColumn[DateExpression]
TimestampArrayColumn = jet.ArrayColumn[TimestampExpression]
TimestampzArrayColumn = jet.ArrayColumn[TimestampzExpression]
TimeArrayColumn = jet.ArrayColumn[TimeExpression]
TimezArrayColumn = jet.ArrayColumn[TimezExpression]
IntervalArrayColumn = jet.ArrayColumn[IntervalExpression]
)

View file

@ -96,7 +96,7 @@ func (b *cast) AS_DATE() DateExpression {
return DateExp(b.AS("date"))
}
// AS_DECIMAL casts expression AS date type
// AS_DECIMAL casts expression AS decimal type
func (b *cast) AS_DECIMAL() FloatExpression {
return FloatExp(b.AS("decimal"))
}
@ -130,3 +130,68 @@ func (b *cast) AS_TIMESTAMPZ() TimestampzExpression {
func (b *cast) AS_INTERVAL() IntervalExpression {
return IntervalExp(b.AS("interval"))
}
// AS_UUID casts expression AS uuid type
func (b *cast) AS_UUID() StringExpression {
return StringExp(b.AS("uuid"))
}
// AS_BOOL_ARRAY casts expression as boolean array type
func (b *cast) AS_BOOL_ARRAY() Array[BoolExpression] {
return ArrayExp[BoolExpression](b.AS("boolean[]"))
}
// AS_INTEGER_ARRAY casts expression as integer array type
func (b *cast) AS_INTEGER_ARRAY() Array[IntegerExpression] {
return ArrayExp[IntegerExpression](b.AS("integer[]"))
}
// AS_BIGINT_ARRAY casts expression as bigint array type
func (b *cast) AS_BIGINT_ARRAY() Array[IntegerExpression] {
return ArrayExp[IntegerExpression](b.AS("bigint[]"))
}
// AS_REAL_ARRAY casts expression as real array
func (b *cast) AS_REAL_ARRAY() Array[FloatExpression] {
return ArrayExp[FloatExpression](b.AS("real[]"))
}
// AS_DOUBLE_ARRAY casts expression as double precision array
func (b *cast) AS_DOUBLE_ARRAY() Array[FloatExpression] {
return ArrayExp[FloatExpression](b.AS("double precision[]"))
}
// AS_TEXT_ARRAY casts expression as text array
func (b *cast) AS_TEXT_ARRAY() Array[StringExpression] {
return ArrayExp[StringExpression](b.AS("text[]"))
}
// AS_BYTEA_ARRAY casts expression as bytea array
func (b *cast) AS_BYTEA_ARRAY() Array[ByteaExpression] {
return ArrayExp[ByteaExpression](b.AS("bytea[]"))
}
// AS_DATE_ARRAY casts expression as date array
func (b *cast) AS_DATE_ARRAY() Array[DateExpression] {
return ArrayExp[DateExpression](b.AS("date[]"))
}
// AS_TIMESTAMP_ARRAY casts expression as timestamp array
func (b *cast) AS_TIMESTAMP_ARRAY() Array[TimestampExpression] {
return ArrayExp[TimestampExpression](b.AS("timestamp without time zone[]"))
}
// AS_TIMESTAMPZ_ARRAY casts expression as timestamp with time zone array
func (b *cast) AS_TIMESTAMPZ_ARRAY() Array[TimestampzExpression] {
return ArrayExp[TimestampzExpression](b.AS("timestamp with time zone[]"))
}
// AS_TIME_ARRAY casts expression as time array
func (b *cast) AS_TIME_ARRAY() Array[TimeExpression] {
return ArrayExp[TimeExpression](b.AS("time without time zone[]"))
}
// AS_TIMEZ_ARRAY casts expression as time with timezone array
func (b *cast) AS_TIMEZ_ARRAY() Array[TimezExpression] {
return ArrayExp[TimezExpression](b.AS("time with time zone[]"))
}

View file

@ -112,21 +112,3 @@ type ColumnInt8Range jet.ColumnRange[jet.Int8Expression]
// Int8RangeColumn creates named range with range column
var Int8RangeColumn = jet.RangeColumn[jet.Int8Expression]
// ColumnStringArray is interface of column
type ColumnStringArray jet.ColumnArray[jet.StringExpression]
// StringArrayColumn creates named string array column
var StringArrayColumn = jet.ArrayColumn[jet.StringExpression]
// ColumnIntegerArray is interface of column
type ColumnIntegerArray jet.ColumnArray[jet.IntegerExpression]
// IntegerArrayColumn creates named integer array column
var IntegerArrayColumn = jet.ArrayColumn[jet.IntegerExpression]
// ColumnBoolArray is interface of column
type ColumnBoolArray jet.ColumnArray[jet.BoolExpression]
// BoolArrayColumn creates named bool array column
var BoolArrayColumn = jet.ArrayColumn[jet.BoolExpression]

View file

@ -2,33 +2,24 @@ package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// Expression is common interface for all expressions.
// Expression is a common interface for all expressions.
// Can be Bool, Int, Float, String, Date, Time, Timez, Timestamp or Timestampz expressions.
type Expression = jet.Expression
// BoolExpression interface
type BoolExpression = jet.BoolExpression
// BoolArrayExpression interface
type BoolArrayExpression = jet.ArrayExpression[BoolExpression]
// StringExpression interface
type StringExpression = jet.StringExpression
type ByteaExpression = jet.BlobExpression
// StringArrayExpression interface
type StringArrayExpression = jet.ArrayExpression[StringExpression]
// NumericExpression interface
type NumericExpression = jet.NumericExpression
// IntegerExpression interface
type IntegerExpression = jet.IntegerExpression
// IntegerArrayExpression interface
type IntegerArrayExpression = jet.ArrayExpression[IntegerExpression]
// FloatExpression is interface
type FloatExpression = jet.FloatExpression
@ -185,3 +176,13 @@ var NewEnumValue = jet.NewEnumValue
// BinaryOperator can be used to use custom or unsupported operators that take two operands.
var BinaryOperator = jet.BinaryOperator
// Array is a common template interface for all array expressions.
type Array[T Expression] jet.Array[T]
// ArrayExp serves as a wrapper for an arbitrary expression, treating it as an array expression of type T.
// This enables the Go compiler to interpret any expression as an array expression of type T.
// Note: This does not modify the generated SQL builder output by adding an SQL CAST operation.
func ArrayExp[T Expression](exp Expression) Array[T] {
return jet.ArrayExp[T](exp)
}

View file

@ -335,7 +335,7 @@ var TO_ASCII = jet.TO_ASCII
// TO_HEX converts number to its equivalent hexadecimal representation
var TO_HEX = jet.TO_HEX
//----------Data Type Formatting Functions ----------------------//
//---------- Range Functions ----------------------//
// LOWER_BOUND returns range expressions lower bound
func LOWER_BOUND[T Expression](expression jet.Range[T]) T {
@ -347,7 +347,144 @@ func UPPER_BOUND[T Expression](expression jet.Range[T]) T {
return jet.UPPER_BOUND[T](expression)
}
//----------Data Type Formatting Functions ----------------------//
// ---------- Array Functions ----------------------//
// ANY should be used in combination with a boolean operator. The result of ANY is "true" if any true result is obtained
func ANY[E Expression](arr Array[E]) E {
return jet.CastToArrayElemType(arr, Func("ANY", arr))
}
// ALL should be used in combination with a boolean operator. The result of ALL is “true” if all comparisons yield true
func ALL[E Expression](arr Array[E]) E {
return jet.CastToArrayElemType(arr, Func("ALL", arr))
}
// ARRAY_APPEND appends an element to the end of an array
func ARRAY_APPEND[E Expression](arr Array[E], elem E) Array[E] {
return ArrayExp[E](Func("ARRAY_APPEND", arr, elem))
}
// ARRAY_CAT concatenates two arrays
func ARRAY_CAT[E Expression](arr1, arr2 Array[E]) Array[E] {
return ArrayExp[E](Func("ARRAY_CAT", arr1, arr2))
}
// ARRAY_DIMS returns a text representation of the array's dimensions.
func ARRAY_DIMS[E Expression](arr Array[E]) StringExpression {
return StringExp(Func("ARRAY_DIMS", arr))
}
// ARRAY_LENGTH returns the length of the requested array dimension.
// Produces NULL instead of 0 for empty or missing array dimensions.
func ARRAY_LENGTH[E Expression](arr Array[E], elem IntegerExpression) IntegerExpression {
return IntExp(Func("ARRAY_LENGTH", arr, elem))
}
// ARRAY_LOWER returns the lower bound of the requested array dimension.
func ARRAY_LOWER[E Expression](arr Array[E]) IntegerExpression {
return IntExp(Func("ARRAY_LOWER", arr))
}
// ARRAY_NDIMS returns the number of dimensions of the array.
func ARRAY_NDIMS[E Expression](arr Array[E]) IntegerExpression {
return IntExp(Func("ARRAY_NDIMS", arr))
}
// ARRAY_POSITION returns the subscript of the first occurrence of the second argument in the array, or NULL if it's not present.
// If the third argument is given, the search begins at that subscript.
// The array must be one-dimensional.
// Comparisons are done using IS NOT DISTINCT FROM semantics, so it is possible to search for NULL.
func ARRAY_POSITION[E Expression](arr Array[E], elem E, beginAt ...IntegerExpression) IntegerExpression {
return IntExp(Func("ARRAY_POSITION", optionalAppend([]Expression{arr, elem}, beginAt)...))
}
// ARRAY_POSITIONS returns an array of the subscripts of all occurrences of the second argument in the array given as first argument.
// The array must be one-dimensional.
// Comparisons are done using IS NOT DISTINCT FROM semantics, so it is possible to search for NULL.
// NULL is returned only if the array is NULL; if the value is not found in the array, an empty array is returned.
func ARRAY_POSITIONS[E Expression](arr Array[E], elem E) Array[IntegerExpression] {
return ArrayExp[IntegerExpression](Func("ARRAY_POSITIONS", arr, elem))
}
// ARRAY_PREPEND prepends an element to the beginning of an array
func ARRAY_PREPEND[E Expression](el E, arr Array[E]) Array[E] {
return ArrayExp[E](Func("ARRAY_PREPEND", el, arr))
}
// ARRAY_REMOVE removes all elements equal to the given value from the array. The array must be one-dimensional.
// Comparisons are done using IS NOT DISTINCT FROM semantics, so it is possible to remove NULLs.
func ARRAY_REMOVE[E Expression](arr Array[E], elem Expression) IntegerExpression {
return IntExp(Func("ARRAY_REMOVE", arr, elem))
}
// ARRAY_REPLACE replaces each array element equal to the second argument with the third argument.
func ARRAY_REPLACE[E Expression](arr Array[E], existing E, new E) Array[E] {
return ArrayExp[E](Func("ARRAY_REPLACE", arr, existing, new))
}
// ARRAY_REVERSE reverses the first dimension of the array.
func ARRAY_REVERSE[E Expression](arr Array[E]) Array[E] {
return ArrayExp[E](Func("ARRAY_REVERSE", arr))
}
// ARRAY_SAMPLE returns an array of n items randomly selected from array.
// n may not exceed the length of array's first dimension.
// If array is multi-dimensional, an “item” is a slice having a given first subscript.
func ARRAY_SAMPLE[E Expression](arr Array[E], n IntegerExpression) Array[E] {
return ArrayExp[E](Func("ARRAY_SAMPLE", arr, n))
}
// ARRAY_SHUFFLE randomly shuffles the first dimension of the array.
func ARRAY_SHUFFLE[E Expression](arr Array[E]) Array[E] {
return ArrayExp[E](Func("ARRAY_SHUFFLE", arr))
}
// ARRAY_SORT sorts the first dimension of the array.
// The sort order is determined by the default sort ordering of the array's element type; however, if the element type is collatable, the collation to use can be specified by adding a COLLATE clause to the array argument.
//
// If descending is true then sort in descending order, otherwise ascending order.
// If omitted, the default is ascending order.
// If nulls_first is true then nulls appear before non-null values, otherwise nulls appear after non-null values.
// If omitted, nulls_first is taken to have the same value as descending.
func ARRAY_SORT[E Expression](arr Array[E], desc BoolExpression, nullFirst ...BoolExpression) Array[E] {
return ArrayExp[E](Func("ARRAY_SORT", optionalAppend([]Expression{arr, desc}, nullFirst)...))
}
// ARRAY_TO_STRING Converts each array element to its text representation, and concatenates those separated by the delimiter string.
// If null_string is given and is not NULL, then NULL array entries are represented by that string; otherwise, they are omitted.
func ARRAY_TO_STRING[T Expression](arr Array[T], delim StringExpression) StringExpression {
return StringExp(Func("ARRAY_TO_STRING", arr, delim))
}
// ARRAY_UPPER returns the upper bound of the requested array dimension.
func ARRAY_UPPER[E Expression](arr Array[E], dim IntegerExpression) IntegerExpression {
return IntExp(Func("ARRAY_UPPER", arr, dim))
}
// CARDINALITY returns the total number of elements in the array, or 0 if the array is empty.
func CARDINALITY[E Expression](arr Array[E]) IntegerExpression {
return IntExp(Func("CARDINALITY", arr))
}
// TRIM_ARRAY trims an array by removing the last n elements. If the array is multidimensional, only the first dimension is trimmed.
func TRIM_ARRAY[E Expression](arr Array[E], n IntegerExpression) Array[E] {
return ArrayExp[E](Func("TRIM_ARRAY", arr, n))
}
// ARRAY constructor is an expression that builds an array value using values for its member elements.
func ARRAY[T Expression](elems ...T) Array[T] {
return jet.ARRAY[T](elems...)
}
func optionalAppend[O Expression](elem []Expression, optional []O) []Expression {
if len(optional) == 0 {
return elem
}
return append(elem, optional[0])
}
//---------- Data Type Formatting Functions ----------------------//
// TO_CHAR converts expression to string with format
var TO_CHAR = jet.TO_CHAR

View file

@ -175,30 +175,27 @@ RETURNING table1.col1 AS "table1.col1",
}
func TestInsert_ON_CONFLICT_ON_CONSTRAINT(t *testing.T) {
stmt := table1.INSERT(table1Col1, table1ColBool, table1ColStringArray).
VALUES("one", "two", "three").
VALUES("1", "2", "3").
ON_CONFLICT().ON_CONSTRAINT("idk_primary_key").DO_UPDATE(
stmt := table1.INSERT(table1Col1, table1ColBool).
VALUES("one", "two").
VALUES("1", "2").
ON_CONFLICT().ON_CONSTRAINT("idk_primary_key").
DO_UPDATE(
SET(table1ColBool.SET(Bool(false)),
table2ColInt.SET(Int(1)),
table1ColStringArray.SET(StringArray([]string{"one"})),
ColumnList{table1Col1, table1ColBool, table1ColStringArray}.SET(jet.ROW(Int(2), String("two"), StringArray([]string{"two"}))),
).WHERE(table1Col1.GT(Int(2))),
).
RETURNING(table1Col1, table1ColBool, table1ColStringArray)
ColumnList{table1Col1, table1ColBool}.SET(ROW(Int(2), String("two"))),
).WHERE(table1Col1.GT(Int(2)))).
RETURNING(table1Col1, table1ColBool)
assertDebugStatementSql(t, stmt, `
INSERT INTO db.table1 (col1, col_bool, col_string_array)
VALUES ('one', 'two', 'three'),
('1', '2', '3')
INSERT INTO db.table1 (col1, col_bool)
VALUES ('one', 'two'),
('1', '2')
ON CONFLICT ON CONSTRAINT idk_primary_key DO UPDATE
SET col_bool = FALSE::boolean,
col_int = 1,
col_string_array = '{"one"}',
(col1, col_bool, col_string_array) = ROW(2, 'two'::text, '{"two"}')
(col1, col_bool) = ROW(2, 'two'::text)
WHERE table1.col1 > 2
RETURNING table1.col1 AS "table1.col1",
table1.col_bool AS "table1.col_bool",
table1.col_string_array AS "table1.col_string_array";
table1.col_bool AS "table1.col_bool";
`)
}

View file

@ -1,6 +1,8 @@
package postgres
import (
"fmt"
"github.com/lib/pq"
"time"
"github.com/go-jet/jet/v2/internal/jet"
@ -11,11 +13,6 @@ func Bool(value bool) BoolExpression {
return CAST(jet.Bool(value)).AS_BOOL()
}
// BoolArray creates new bool array literal expression
func BoolArray(elements []bool) BoolArrayExpression {
return jet.BoolArray(elements)
}
// Int is constructor for 64 bit signed integer expressions literals.
var Int = jet.Int
@ -73,8 +70,11 @@ func Double(value float64) FloatExpression {
}
// Decimal creates new float literal expression
var Decimal = jet.Decimal
func Decimal(value string) FloatExpression {
return CAST(jet.Literal(value)).AS_DECIMAL()
}
// String creates new string literal expression
// String is a parameter constructor for the PostgreSQL text type. Using the `Text` constructor is
// generally preferable.
//
@ -85,11 +85,6 @@ func String(value string) StringExpression {
return CAST(jet.String(value)).AS_TEXT()
}
// StringArray creates new string array literal expression
func StringArray(elements []string) StringArrayExpression {
return jet.StringArray(elements)
}
// Text is a parameter constructor for the PostgreSQL text type. This constructor also adds an
// explicit placeholder type cast to text in the generated query, such as `$3::text`.
// Example usage:
@ -134,7 +129,9 @@ func Json(value interface{}) StringExpression {
// UUID is a helper function to create string literal expression from uuid object
// value can be any uuid type with a String method
var UUID = jet.UUID
func UUID(value fmt.Stringer) StringExpression {
return CAST(jet.Literal(value.String())).AS_UUID()
}
// Bytea creates new bytea literal expression
func Bytea(value interface{}) ByteaExpression {
@ -195,3 +192,63 @@ func Timestampz(year int, month time.Month, day, hour, minute, second int, milli
func TimestampzT(t time.Time) TimestampzExpression {
return CAST(jet.TimestampzT(t)).AS_TIMESTAMPZ()
}
// BoolArray creates new bool array literal expression from list of values
func BoolArray(values ...bool) Array[BoolExpression] {
return CAST(jet.Literal(pq.BoolArray(values))).AS_BOOL_ARRAY()
}
// Int32Array creates new integer array literal expression from list of values
func Int32Array(values ...int32) Array[IntegerExpression] {
return CAST(jet.Literal(pq.Int32Array(values))).AS_INTEGER_ARRAY()
}
// Int64Array creates new bigint array literal expression from list of values
func Int64Array(values ...int64) Array[IntegerExpression] {
return CAST(jet.Literal(pq.Int64Array(values))).AS_BIGINT_ARRAY()
}
// Float32Array creates new real array literal expression from list of values
func Float32Array(values ...float32) Array[FloatExpression] {
return CAST(jet.Literal(pq.Float32Array(values))).AS_REAL_ARRAY()
}
// Float64Array creates new double precision array literal expression from list of values
func Float64Array(values ...float64) Array[FloatExpression] {
return CAST(jet.Literal(pq.Float64Array(values))).AS_DOUBLE_ARRAY()
}
// StringArray creates new string array literal expression from list of values
func StringArray(values ...string) Array[StringExpression] {
return CAST(jet.Literal(pq.StringArray(values))).AS_TEXT_ARRAY()
}
// ByteaArray creates new bytea array literal expression from list of values
func ByteaArray(values ...[]byte) Array[ByteaExpression] {
return CAST(jet.Literal(pq.ByteaArray(values))).AS_BYTEA_ARRAY()
}
// DateArray creates new date array literal expression from list of values
func DateArray(values ...time.Time) Array[DateExpression] {
return CAST(jet.Literal(pq.Array(values))).AS_DATE_ARRAY()
}
// TimestampArray creates new timestamp array literal expression from list of values
func TimestampArray(values ...time.Time) Array[TimestampExpression] {
return CAST(jet.Literal(pq.Array(values))).AS_TIMESTAMP_ARRAY()
}
// TimestampzArray creates new timestampt with timezone array literal expression from list of values
func TimestampzArray(values ...time.Time) Array[TimestampzExpression] {
return CAST(jet.Literal(pq.Array(values))).AS_TIMESTAMPZ_ARRAY()
}
// TimeArray creates new time array literal expression from list of values
func TimeArray(values ...time.Time) Array[TimeExpression] {
return CAST(jet.Literal(pq.Array(values))).AS_TIME_ARRAY()
}
// TimezArray creates new time with timezone array literal expression from list of values
func TimezArray(values ...time.Time) Array[TimezExpression] {
return CAST(jet.Literal(pq.Array(values))).AS_TIMEZ_ARRAY()
}

View file

@ -1,15 +1,13 @@
package postgres
import (
"database/sql"
"github.com/lib/pq"
"encoding/base64"
"fmt"
"github.com/go-jet/jet/v2/internal/utils/ptr"
"github.com/stretchr/testify/assert"
"math"
"github.com/go-jet/jet/v2/qrm"
"github.com/lib/pq"
"testing"
"time"
@ -46,8 +44,7 @@ func TestAllTypesSelectJson(t *testing.T) {
AllTypesAllColumns.Except(
AllTypes.JSON, AllTypes.JSONPtr,
AllTypes.Jsonb, AllTypes.JsonbPtr,
AllTypes.TextArray, AllTypes.TextArrayPtr,
AllTypes.JsonbArray, AllTypes.IntegerArray, AllTypes.IntegerArrayPtr,
AllTypes.JsonbArray,
AllTypes.TextMultiDimArray, AllTypes.TextMultiDimArrayPtr,
),
// unsupported at the moment, casting to text allows these columns to be assigned to string fields
@ -55,11 +52,7 @@ func TestAllTypesSelectJson(t *testing.T) {
CAST(AllTypes.JSON).AS_TEXT().AS("JSON"),
CAST(AllTypes.JsonbPtr).AS_TEXT().AS("jsonbPtr"),
CAST(AllTypes.Jsonb).AS_TEXT().AS("Jsonb"),
CAST(AllTypes.TextArrayPtr).AS_TEXT().AS("TextArrayPtr"),
CAST(AllTypes.TextArray).AS_TEXT().AS("TextArray"),
CAST(AllTypes.JsonbArray).AS_TEXT().AS("JsonbArray"),
CAST(AllTypes.IntegerArray).AS_TEXT().AS("IntegerArray"),
CAST(AllTypes.IntegerArrayPtr).AS_TEXT().AS("IntegerArrayPtr"),
CAST(AllTypes.JsonbArray).AS_TEXT_ARRAY().AS("JsonbArray"),
CAST(AllTypes.TextMultiDimArray).AS_TEXT().AS("TextMultiDimArray"),
CAST(AllTypes.TextMultiDimArrayPtr).AS_TEXT().AS("TextMultiDimArrayPtr"),
).FROM(AllTypes)
@ -117,17 +110,17 @@ FROM (
all_types.uuid AS "uuid",
all_types.xml_ptr AS "xmlPtr",
all_types.xml AS "xml",
all_types.integer_array_ptr AS "integerArrayPtr",
all_types.integer_array AS "integerArray",
all_types.text_array_ptr AS "textArrayPtr",
all_types.text_array AS "textArray",
all_types.mood_ptr AS "moodPtr",
all_types.mood AS "mood",
all_types.json_ptr::text AS "jsonPtr",
all_types.json::text AS "JSON",
all_types.jsonb_ptr::text AS "jsonbPtr",
all_types.jsonb::text AS "Jsonb",
all_types.text_array_ptr::text AS "TextArrayPtr",
all_types.text_array::text AS "TextArray",
all_types.jsonb_array::text AS "JsonbArray",
all_types.integer_array::text AS "IntegerArray",
all_types.integer_array_ptr::text AS "IntegerArrayPtr",
all_types.jsonb_array::text[] AS "JsonbArray",
all_types.text_multi_dim_array::text AS "TextMultiDimArray",
all_types.text_multi_dim_array_ptr::text AS "TextMultiDimArrayPtr"
FROM test_sample.all_types
@ -259,7 +252,7 @@ func TestUUIDType(t *testing.T) {
SELECT all_types.uuid AS "all_types.uuid",
all_types.uuid_ptr AS "all_types.uuid_ptr"
FROM test_sample.all_types
WHERE all_types.uuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
WHERE all_types.uuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid;
`, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")
result := model.AllTypes{}
@ -875,10 +868,7 @@ FROM (
err := stmtJson.QueryContext(ctx, db, &destSelectJson)
require.NoError(t, err)
testutils.PrintJson(destSelectJson)
require.Equal(t, dest, destSelectJson)
})
}
@ -1583,8 +1573,6 @@ func TestInterval(t *testing.T) {
AllTypes.IntervalPtr.DIV(Float(22.222)).EQ(AllTypes.IntervalPtr),
).FROM(AllTypes)
fmt.Println(stmt.Sql())
testutils.AssertDebugStatementSql(t, stmt, `
SELECT INTERVAL '1 YEAR',
INTERVAL '1 MONTH',
@ -2250,7 +2238,7 @@ var allTypesRow0 = model.AllTypes{
TextArrayPtr: &pq.StringArray{"breakfast", "consulting"},
TextArray: pq.StringArray{"breakfast", "consulting"},
JsonbArray: pq.StringArray{`{"a": 1, "b": 2}`, `{"a": 3, "b": 4}`},
TextMultiDimArrayPtr: testutils.StringPtr("{{meeting,lunch},{training,presentation}}"),
TextMultiDimArrayPtr: ptr.Of("{{meeting,lunch},{training,presentation}}"),
TextMultiDimArray: "{{meeting,lunch},{training,presentation}}",
MoodPtr: &moodSad,
Mood: model.Mood_Happy,

View file

@ -0,0 +1,719 @@
package postgres
import (
"github.com/go-jet/jet/v2/internal/testutils"
"github.com/go-jet/jet/v2/internal/utils/ptr"
. "github.com/go-jet/jet/v2/postgres"
"github.com/go-jet/jet/v2/qrm"
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/enum"
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/model"
. "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/table"
"github.com/google/uuid"
"github.com/lib/pq"
"github.com/stretchr/testify/require"
"testing"
"time"
)
func TestArraySelect(t *testing.T) {
stmt := SELECT(
SampleArrays.AllColumns,
).FROM(
SampleArrays,
)
testutils.AssertDebugStatementSql(t, stmt, `
SELECT sample_arrays.id AS "sample_arrays.id",
sample_arrays.bool_array AS "sample_arrays.bool_array",
sample_arrays.int2_array_ptr AS "sample_arrays.int2_array_ptr",
sample_arrays.int4_array AS "sample_arrays.int4_array",
sample_arrays.int8_array AS "sample_arrays.int8_array",
sample_arrays.numeric_array AS "sample_arrays.numeric_array",
sample_arrays.decimal_array AS "sample_arrays.decimal_array",
sample_arrays.real_array AS "sample_arrays.real_array",
sample_arrays.double_array AS "sample_arrays.double_array",
sample_arrays.text_array AS "sample_arrays.text_array",
sample_arrays.varchar_array AS "sample_arrays.varchar_array",
sample_arrays.char_array AS "sample_arrays.char_array",
sample_arrays.bytea_array AS "sample_arrays.bytea_array",
sample_arrays.date_array AS "sample_arrays.date_array",
sample_arrays.timestamp_array AS "sample_arrays.timestamp_array",
sample_arrays.timestamptz_array AS "sample_arrays.timestamptz_array",
sample_arrays.time_array AS "sample_arrays.time_array",
sample_arrays.timetz_array AS "sample_arrays.timetz_array",
sample_arrays.interval_array AS "sample_arrays.interval_array",
sample_arrays.uuid_array AS "sample_arrays.uuid_array",
sample_arrays.mood_enum_array AS "sample_arrays.mood_enum_array"
FROM test_sample.sample_arrays;
`)
var dest []model.SampleArrays
err := stmt.Query(db, &dest)
require.NoError(t, err)
require.Len(t, dest, 1)
require.Equal(t, dest[0], sampleArrayRow0)
}
func TestArrayOperations(t *testing.T) {
boolArray := BoolArray(true, false, false, true)
int32Array := Int32Array(-1, 2, -3, 4, 5)
int64Array := Int64Array(10, -11, 12, -13, 14, 1515551)
float32Array := Float32Array(1.01, -2.02, 3.03, 4.04)
float64Array := Float64Array(10.001, 20.002, -3.003, 400050.04)
stringArray := StringArray("temp", "text", "array")
byteaArray := ByteaArray([]byte("temporal"), []byte("byte array"), []byte("array"))
dateArray := DateArray(*testutils.Date("2022-04-06"), *testutils.Date("2018-05-06"))
dateArray2 := ARRAY(Date(2022, 2, 3), Date(2023, 2, 2))
timestampArray := TimestampArray(*testutils.TimestampWithoutTimeZone("1999-01-08 04:05:06", 0))
timestampzArray := TimestampzArray(time.Date(2022, 1, 1, 8, 30, 50, 0, time.UTC))
timestampzArray2 := ARRAY(Timestampz(2023, 2, 3, 10, 29, 34, 1000, "UTC"))
timeArray := TimeArray(*testutils.TimeWithoutTimeZone("14:20:45"), *testutils.TimeWithoutTimeZone("8:10:15"))
timezArray := TimezArray(*testutils.TimeWithTimeZone("04:05:06 -0800"), *testutils.TimeWithTimeZone("01:02:03 -0600"))
timeArray2 := ARRAY(Time(10, 20, 30), Time(5, 45, 45))
timezArray2 := ARRAY(Timez(10, 20, 30, 0, "UTC"), Timez(5, 45, 45, 0, "UTC"))
timestampz := Timestampz(1950, 2, 3, 10, 30, 40, 0, "UTC")
query := SELECT(
// array constructors
boolArray.AS("bool_array"),
int32Array.AS("int32_array"),
int64Array.AS("int64_array"),
float32Array.AS("float32_array"),
float64Array.AS("float64_array"),
stringArray.AS("string_array"),
byteaArray.AS("bytea_array"),
dateArray.AS("date_array"),
timestampArray.AS("timestamp_array"),
timestampzArray.AS("timestampz_array"),
timeArray.AS("time_array"),
timezArray.AS("timez_array"),
// operators
SampleArrays.BoolArray.EQ(boolArray).AS("bool_eq"),
SampleArrays.TextArray.EQ(SampleArrays.TextArray).AS("text_eq"),
SampleArrays.TextArray.NOT_EQ(stringArray).AS("text_neq"),
SampleArrays.Int4Array.LT(int32Array).IS_TRUE().AS("int4_lt"),
SampleArrays.Int8Array.LT_EQ(int64Array).IS_FALSE().AS("int8_lteq"),
SampleArrays.RealArray.GT(float32Array).AS("decimal_gt"),
SampleArrays.DoubleArray.GT_EQ(float64Array).AS("numeric_gt_eq"),
SampleArrays.ByteaArray.CONTAINS(byteaArray).AS("bytea_contains"),
byteaArray.IS_CONTAINED_BY(SampleArrays.ByteaArray).AS("bytea_contained_by"),
SampleArrays.DateArray.OVERLAP(dateArray2).AS("date_overlaps"),
SampleArrays.TimestampArray.CONCAT(timestampArray).AS("timestamp_concat"),
timestampzArray2.CONCAT_ELEMENT(timestampz).AS("timestampz_concat_elem"),
SampleArrays.TimeArray.AT(Int32(1)).AS("time_at"),
Int32(22).EQ(ANY(SampleArrays.Int4Array)).AS("int32_eq_any"),
Double(7.89).NOT_EQ(ANY(SampleArrays.DoubleArray)).AS("double_neq_any"),
String("temp").EQ(ALL(stringArray)).AS("string_eq_all"),
// functions
ARRAY_APPEND(SampleArrays.TextArray, String("after")).AS("append"),
ARRAY_CAT(SampleArrays.TimeArray, timeArray2).AS("cat"),
ARRAY_LENGTH(timezArray2, Int32(1)).AS("length"),
// ARRAY_LOWER(SampleArrays.UUIDArray).AS("lower"), //newer postgres versions
ARRAY_POSITIONS(stringArray, String("text")).AS("position"),
ARRAY_PREPEND(String("before"), SampleArrays.TextArray).AS("prepend"),
ARRAY_REMOVE(boolArray, Bool(true)).AS("remove"),
ARRAY_REPLACE(SampleArrays.VarcharArray, String("hello"), String("hi")).AS("replace"),
//ARRAY_REVERSE(SampleArrays.TextArray).AS("reverse"),
//ARRAY_SAMPLE(SampleArrays.TimestampArray, Int(2)).AS("sample"),
//ARRAY_SHUFFLE(SampleArrays.BoolArray).AS("shuffle"),
//ARRAY_SORT(SampleArrays.Int8Array, Bool(true)),
ARRAY_TO_STRING(SampleArrays.MoodEnumArray, String(", ")).AS("to_string"),
ARRAY_UPPER(SampleArrays.Int8Array, Int(1)).AS("upper"),
CARDINALITY(SampleArrays.DoubleArray).AS("cardinality"),
// unsupported by cockroachdb
//ARRAY_DIMS(SampleArrays.IntervalArray).AS("dims"),
//ARRAY_NDIMS(SampleArrays.TextArray).AS("ndims"),
//TRIM_ARRAY(SampleArrays.DateArray, Int(1)).AS("trim"),
).FROM(
SampleArrays,
).WHERE(
SampleArrays.BoolArray.CONTAINS(BoolArray(true)),
)
testutils.AssertStatementSql(t, query, `
SELECT $1::boolean[] AS "bool_array",
$2::integer[] AS "int32_array",
$3::bigint[] AS "int64_array",
$4::real[] AS "float32_array",
$5::double precision[] AS "float64_array",
$6::text[] AS "string_array",
$7::bytea[] AS "bytea_array",
$8::date[] AS "date_array",
$9::timestamp without time zone[] AS "timestamp_array",
$10::timestamp with time zone[] AS "timestampz_array",
$11::time without time zone[] AS "time_array",
$12::time with time zone[] AS "timez_array",
(sample_arrays.bool_array = $13::boolean[]) AS "bool_eq",
(sample_arrays.text_array = sample_arrays.text_array) AS "text_eq",
(sample_arrays.text_array != $14::text[]) AS "text_neq",
(sample_arrays.int4_array < $15::integer[]) IS TRUE AS "int4_lt",
(sample_arrays.int8_array <= $16::bigint[]) IS FALSE AS "int8_lteq",
(sample_arrays.real_array > $17::real[]) AS "decimal_gt",
(sample_arrays.double_array >= $18::double precision[]) AS "numeric_gt_eq",
(sample_arrays.bytea_array @> $19::bytea[]) AS "bytea_contains",
($20::bytea[] <@ sample_arrays.bytea_array) AS "bytea_contained_by",
(sample_arrays.date_array && ARRAY[$21::date,$22::date]) AS "date_overlaps",
(sample_arrays.timestamp_array || $23::timestamp without time zone[]) AS "timestamp_concat",
(ARRAY[$24::timestamp with time zone] || $25::timestamp with time zone) AS "timestampz_concat_elem",
sample_arrays.time_array[$26::integer] AS "time_at",
($27::integer = ANY(sample_arrays.int4_array)) AS "int32_eq_any",
($28::double precision != ANY(sample_arrays.double_array)) AS "double_neq_any",
($29::text = ALL($30::text[])) AS "string_eq_all",
ARRAY_APPEND(sample_arrays.text_array, $31::text) AS "append",
ARRAY_CAT(sample_arrays.time_array, ARRAY[$32::time without time zone,$33::time without time zone]) AS "cat",
ARRAY_LENGTH(ARRAY[$34::time with time zone,$35::time with time zone], $36::integer) AS "length",
ARRAY_POSITIONS($37::text[], $38::text) AS "position",
ARRAY_PREPEND($39::text, sample_arrays.text_array) AS "prepend",
ARRAY_REMOVE($40::boolean[], $41::boolean) AS "remove",
ARRAY_REPLACE(sample_arrays.varchar_array, $42::text, $43::text) AS "replace",
ARRAY_TO_STRING(sample_arrays.mood_enum_array, $44::text) AS "to_string",
ARRAY_UPPER(sample_arrays.int8_array, $45) AS "upper",
CARDINALITY(sample_arrays.double_array) AS "cardinality"
FROM test_sample.sample_arrays
WHERE sample_arrays.bool_array @> $46::boolean[];
`)
var dest struct {
// array constructors
BoolArray pq.BoolArray
Int32Array pq.Int32Array
Int64Array pq.Int64Array
Float32Array pq.Float32Array
Float64Array pq.Float64Array
StringArray pq.StringArray
ByteaArray pq.ByteaArray
DateArray pq.StringArray
TimestampArray pq.StringArray
TimestampzArray pq.StringArray
TimeArray pq.StringArray
TimezArray pq.StringArray
// array operators
TextEq bool
BoolEq bool
TextNeq bool
Int4Lt bool
Int8Lteq bool
DecimalGt bool
NumericGtEq bool
ByteaContains bool
ByteaContainedBy bool
DateOverlaps bool
TimestampConcat pq.StringArray
TimestampzConcatElem pq.StringArray
TimeAt time.Time
Int32EqAny bool
DoubleNeqAny bool
StringEqAll bool
// functions
Append pq.StringArray
Cat pq.StringArray
Dims string
Length int32
Lower int32
NDims int32
Position pq.Int32Array
Prepend pq.StringArray
Remove pq.BoolArray
Replace pq.StringArray
ToString string
Upper int32
Cardinality int32
Trim pq.StringArray
}
err := query.Query(db, &dest)
require.NoError(t, err)
testutils.AssertJSON(t, dest, `
{
"BoolArray": [
true,
false,
false,
true
],
"Int32Array": [
-1,
2,
-3,
4,
5
],
"Int64Array": [
10,
-11,
12,
-13,
14,
1515551
],
"Float32Array": [
1.01,
-2.02,
3.03,
4.04
],
"Float64Array": [
10.001,
20.002,
-3.003,
400050.04
],
"StringArray": [
"temp",
"text",
"array"
],
"ByteaArray": [
"dGVtcG9yYWw=",
"Ynl0ZSBhcnJheQ==",
"YXJyYXk="
],
"DateArray": [
"2022-04-06",
"2018-05-06"
],
"TimestampArray": [
"1999-01-08 04:05:06"
],
"TimestampzArray": [
"2022-01-01 08:30:50+00"
],
"TimeArray": [
"14:20:45",
"08:10:15"
],
"TimezArray": [
"04:05:06-08",
"01:02:03-06"
],
"TextEq": true,
"BoolEq": false,
"TextNeq": true,
"Int4Lt": false,
"Int8Lteq": true,
"DecimalGt": true,
"NumericGtEq": true,
"ByteaContains": false,
"ByteaContainedBy": false,
"DateOverlaps": false,
"TimestampConcat": [
"2025-01-01 10:00:00",
"2025-02-01 10:00:00",
"1999-01-08 04:05:06"
],
"TimestampzConcatElem": [
"2023-02-03 10:29:34.000001+00",
"1950-02-03 10:30:40+00"
],
"TimeAt": "0000-01-01T12:00:00Z",
"Int32EqAny": false,
"DoubleNeqAny": true,
"StringEqAll": false,
"Append": [
"alpha",
"beta",
"gama",
"after"
],
"Cat": [
"12:00:00",
"13:00:00",
"10:20:30",
"05:45:45"
],
"Dims": "",
"Length": 2,
"Lower": 0,
"NDims": 0,
"Position": [
2
],
"Prepend": [
"before",
"alpha",
"beta",
"gama"
],
"Remove": [
false,
false
],
"Replace": [
"hi",
"world"
],
"ToString": "happy, ok",
"Upper": 4,
"Cardinality": 3,
"Trim": null
}
`)
requireLogged(t, query)
}
func TestArraySelectColumnsFromSubQuery(t *testing.T) {
subQuery := SELECT(
SampleArrays.AllColumns,
Int64Array(10, -11, 12, -13, 14, 1515551).AS("int64_array"),
).FROM(
SampleArrays,
).AsTable("sub_query")
int64Array := IntegerArrayColumn("int64_array").From(subQuery)
stmt := SELECT(
subQuery.AllColumns().Except(int64Array),
int64Array,
).FROM(
subQuery,
)
testutils.AssertDebugStatementSql(t, stmt, `
SELECT sub_query."sample_arrays.id" AS "sample_arrays.id",
sub_query."sample_arrays.bool_array" AS "sample_arrays.bool_array",
sub_query."sample_arrays.int2_array_ptr" AS "sample_arrays.int2_array_ptr",
sub_query."sample_arrays.int4_array" AS "sample_arrays.int4_array",
sub_query."sample_arrays.int8_array" AS "sample_arrays.int8_array",
sub_query."sample_arrays.numeric_array" AS "sample_arrays.numeric_array",
sub_query."sample_arrays.decimal_array" AS "sample_arrays.decimal_array",
sub_query."sample_arrays.real_array" AS "sample_arrays.real_array",
sub_query."sample_arrays.double_array" AS "sample_arrays.double_array",
sub_query."sample_arrays.text_array" AS "sample_arrays.text_array",
sub_query."sample_arrays.varchar_array" AS "sample_arrays.varchar_array",
sub_query."sample_arrays.char_array" AS "sample_arrays.char_array",
sub_query."sample_arrays.bytea_array" AS "sample_arrays.bytea_array",
sub_query."sample_arrays.date_array" AS "sample_arrays.date_array",
sub_query."sample_arrays.timestamp_array" AS "sample_arrays.timestamp_array",
sub_query."sample_arrays.timestamptz_array" AS "sample_arrays.timestamptz_array",
sub_query."sample_arrays.time_array" AS "sample_arrays.time_array",
sub_query."sample_arrays.timetz_array" AS "sample_arrays.timetz_array",
sub_query."sample_arrays.interval_array" AS "sample_arrays.interval_array",
sub_query."sample_arrays.uuid_array" AS "sample_arrays.uuid_array",
sub_query."sample_arrays.mood_enum_array" AS "sample_arrays.mood_enum_array",
sub_query.int64_array AS "int64_array"
FROM (
SELECT sample_arrays.id AS "sample_arrays.id",
sample_arrays.bool_array AS "sample_arrays.bool_array",
sample_arrays.int2_array_ptr AS "sample_arrays.int2_array_ptr",
sample_arrays.int4_array AS "sample_arrays.int4_array",
sample_arrays.int8_array AS "sample_arrays.int8_array",
sample_arrays.numeric_array AS "sample_arrays.numeric_array",
sample_arrays.decimal_array AS "sample_arrays.decimal_array",
sample_arrays.real_array AS "sample_arrays.real_array",
sample_arrays.double_array AS "sample_arrays.double_array",
sample_arrays.text_array AS "sample_arrays.text_array",
sample_arrays.varchar_array AS "sample_arrays.varchar_array",
sample_arrays.char_array AS "sample_arrays.char_array",
sample_arrays.bytea_array AS "sample_arrays.bytea_array",
sample_arrays.date_array AS "sample_arrays.date_array",
sample_arrays.timestamp_array AS "sample_arrays.timestamp_array",
sample_arrays.timestamptz_array AS "sample_arrays.timestamptz_array",
sample_arrays.time_array AS "sample_arrays.time_array",
sample_arrays.timetz_array AS "sample_arrays.timetz_array",
sample_arrays.interval_array AS "sample_arrays.interval_array",
sample_arrays.uuid_array AS "sample_arrays.uuid_array",
sample_arrays.mood_enum_array AS "sample_arrays.mood_enum_array",
'{10,-11,12,-13,14,1515551}'::bigint[] AS "int64_array"
FROM test_sample.sample_arrays
) AS sub_query;
`)
var dest struct {
model.SampleArrays
Int64Array pq.Int64Array
}
err := stmt.Query(db, &dest)
require.NoError(t, err)
testutils.AssertDeepEqual(t, dest.SampleArrays, sampleArrayRow0)
testutils.AssertDeepEqual(t, dest.Int64Array, pq.Int64Array{10, -11, 12, -13, 14, 1515551})
}
func TestArrayTableInsert(t *testing.T) {
sampleArrayRow3 := testutils.DeepCopy(t, sampleArrayRow0)
sampleArrayRow3.ID = 3
insertQuery := SampleArrays.INSERT(SampleArrays.AllColumns).
VALUES(
Int64(2),
ARRAY(Bool(false), Bool(false), Bool(true)),
ARRAY(Int16(4), Int16(5), Int(6)),
Int32Array(40, 50, 60),
Int64Array(400, 500, 600),
ARRAY(Decimal("1.11"), Decimal("2.22")),
ARRAY(Decimal("1.11"), Decimal("2.22")),
Float32Array(4.4, 5.5, 6.6, 7.7),
Float64Array(40.04, 50.05, 60.06, 70.07),
StringArray("john", "doe"),
ARRAY(VarChar(10)("Andy"), VarChar(10)("Bob")),
ARRAY(Char(1)("q"), Char(1)("w"), Char(1)("e")),
ByteaArray([]byte("title"), []byte("name")),
ARRAY(Date(2010, 2, 3), Date(2025, 4, 5)),
ARRAY(Timestamp(2025, 2, 3, 0, 10, 20, 0)),
ARRAY(Timestampz(2025, 2, 3, 0, 10, 20, 0, "UTC")),
ARRAY(Time(12, 15, 45), Time(2, 30, 40)),
ARRAY(Timez(12, 15, 45, 0, "UTC"), Timez(2, 30, 40, 0, "UTC")),
ARRAY(INTERVAL(1, DAY, 3, MINUTE), INTERVAL(2, YEAR, 30, DAY)),
ARRAY(UUID(uuid.MustParse("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"))),
CAST(ARRAY(enum.Mood.Happy, enum.Mood.Sad)).AS("test_sample.mood[]"),
).
MODEL(
sampleArrayRow3,
).
RETURNING(SampleArrays.AllColumns)
testutils.AssertStatementSql(t, insertQuery, `
INSERT INTO test_sample.sample_arrays (id, bool_array, int2_array_ptr, int4_array, int8_array, numeric_array, decimal_array, real_array, double_array, text_array, varchar_array, char_array, bytea_array, date_array, timestamp_array, timestamptz_array, time_array, timetz_array, interval_array, uuid_array, mood_enum_array)
VALUES ($1::bigint, ARRAY[$2::boolean,$3::boolean,$4::boolean], ARRAY[$5::smallint,$6::smallint,$7], $8::integer[], $9::bigint[], ARRAY[$10::decimal,$11::decimal], ARRAY[$12::decimal,$13::decimal], $14::real[], $15::double precision[], $16::text[], ARRAY[$17::varchar(10),$18::varchar(10)], ARRAY[$19::char(1),$20::char(1),$21::char(1)], $22::bytea[], ARRAY[$23::date,$24::date], ARRAY[$25::timestamp without time zone], ARRAY[$26::timestamp with time zone], ARRAY[$27::time without time zone,$28::time without time zone], ARRAY[$29::time with time zone,$30::time with time zone], ARRAY[INTERVAL '1 DAY 3 MINUTE',INTERVAL '2 YEAR 30 DAY'], ARRAY[$31::uuid], ARRAY['happy','sad']::test_sample.mood[]),
($32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $50, $51, $52)
RETURNING sample_arrays.id AS "sample_arrays.id",
sample_arrays.bool_array AS "sample_arrays.bool_array",
sample_arrays.int2_array_ptr AS "sample_arrays.int2_array_ptr",
sample_arrays.int4_array AS "sample_arrays.int4_array",
sample_arrays.int8_array AS "sample_arrays.int8_array",
sample_arrays.numeric_array AS "sample_arrays.numeric_array",
sample_arrays.decimal_array AS "sample_arrays.decimal_array",
sample_arrays.real_array AS "sample_arrays.real_array",
sample_arrays.double_array AS "sample_arrays.double_array",
sample_arrays.text_array AS "sample_arrays.text_array",
sample_arrays.varchar_array AS "sample_arrays.varchar_array",
sample_arrays.char_array AS "sample_arrays.char_array",
sample_arrays.bytea_array AS "sample_arrays.bytea_array",
sample_arrays.date_array AS "sample_arrays.date_array",
sample_arrays.timestamp_array AS "sample_arrays.timestamp_array",
sample_arrays.timestamptz_array AS "sample_arrays.timestamptz_array",
sample_arrays.time_array AS "sample_arrays.time_array",
sample_arrays.timetz_array AS "sample_arrays.timetz_array",
sample_arrays.interval_array AS "sample_arrays.interval_array",
sample_arrays.uuid_array AS "sample_arrays.uuid_array",
sample_arrays.mood_enum_array AS "sample_arrays.mood_enum_array";
`)
testutils.ExecuteInTxAndRollback(t, db, func(tx qrm.DB) {
var dest []model.SampleArrays
err := insertQuery.Query(tx, &dest)
require.NoError(t, err)
require.Len(t, dest, 2)
testutils.AssertDeepEqual(t, sampleArrayRow3, dest[1])
testutils.AssertJSON(t, dest[0], `
{
"ID": 2,
"BoolArray": [
false,
false,
true
],
"Int2ArrayPtr": [
"4",
"5",
"6"
],
"Int4Array": [
40,
50,
60
],
"Int8Array": [
400,
500,
600
],
"NumericArray": [
1.11,
2.22
],
"DecimalArray": [
1.11,
2.22
],
"RealArray": [
4.4,
5.5,
6.6,
7.7
],
"DoubleArray": [
40.04,
50.05,
60.06,
70.07
],
"TextArray": [
"john",
"doe"
],
"VarcharArray": [
"Andy",
"Bob"
],
"CharArray": [
"q",
"w",
"e"
],
"ByteaArray": [
"dGl0bGU=",
"bmFtZQ=="
],
"DateArray": [
"2010-02-03",
"2025-04-05"
],
"TimestampArray": [
"2025-02-03 00:10:20"
],
"TimestamptzArray": [
"2025-02-03 00:10:20+00"
],
"TimeArray": [
"12:15:45",
"02:30:40"
],
"TimetzArray": [
"12:15:45+00",
"02:30:40+00"
],
"IntervalArray": [
"1 day 00:03:00",
"2 years 30 days"
],
"UUIDArray": [
"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
],
"MoodEnumArray": [
"happy",
"sad"
]
}
`)
})
}
func TestArrayTableUpdate(t *testing.T) {
t.Run("using model", func(t *testing.T) {
sampleArrayRow1Clone := testutils.DeepCopy(t, sampleArrayRow0)
sampleArrayRow1Clone.RealArray = pq.Float32Array{100.11, 200.22, 300.33}
stmt := SampleArrays.UPDATE(SampleArrays.MutableColumns).
MODEL(sampleArrayRow1Clone).
WHERE(String("alpha").EQ(ANY(SampleArrays.TextArray))).
RETURNING(SampleArrays.AllColumns)
testutils.AssertDebugStatementSql(t, stmt, `
UPDATE test_sample.sample_arrays
SET (bool_array, int2_array_ptr, int4_array, int8_array, numeric_array, decimal_array, real_array, double_array, text_array, varchar_array, char_array, bytea_array, date_array, timestamp_array, timestamptz_array, time_array, timetz_array, interval_array, uuid_array, mood_enum_array) = ('{t,f,t}', '{"1","2","3","4"}', '{10,20,30,40}', '{100,200,300,400}', '{1.8881,2.8882,3.8883,4.8884}', '{1.0001,2.0002,3.0003,4.0004}', '{100.11,200.22,300.33}', '{11.11,22.22,33.33}', '{"alpha","beta","gama"}', '{"hello","world"}', '{"a","b","c"}', '{"\\x01020304","\\x11223344"}', '{"2024-11-01","2025-02-28"}', '{"2025-01-01 10:00:00","2025-02-01 10:00:00"}', '{"2025-01-01 09:00:00+00","2025-02-01 09:00:00+00"}', '{"12:00:00","13:00:00"}', '{"12:00:00+01","13:00:00+02"}', '{"1 day","02:00:00"}', '{"550e8400-e29b-41d4-a716-446655440000"}', '{"happy","ok"}')
WHERE 'alpha'::text = ANY(sample_arrays.text_array)
RETURNING sample_arrays.id AS "sample_arrays.id",
sample_arrays.bool_array AS "sample_arrays.bool_array",
sample_arrays.int2_array_ptr AS "sample_arrays.int2_array_ptr",
sample_arrays.int4_array AS "sample_arrays.int4_array",
sample_arrays.int8_array AS "sample_arrays.int8_array",
sample_arrays.numeric_array AS "sample_arrays.numeric_array",
sample_arrays.decimal_array AS "sample_arrays.decimal_array",
sample_arrays.real_array AS "sample_arrays.real_array",
sample_arrays.double_array AS "sample_arrays.double_array",
sample_arrays.text_array AS "sample_arrays.text_array",
sample_arrays.varchar_array AS "sample_arrays.varchar_array",
sample_arrays.char_array AS "sample_arrays.char_array",
sample_arrays.bytea_array AS "sample_arrays.bytea_array",
sample_arrays.date_array AS "sample_arrays.date_array",
sample_arrays.timestamp_array AS "sample_arrays.timestamp_array",
sample_arrays.timestamptz_array AS "sample_arrays.timestamptz_array",
sample_arrays.time_array AS "sample_arrays.time_array",
sample_arrays.timetz_array AS "sample_arrays.timetz_array",
sample_arrays.interval_array AS "sample_arrays.interval_array",
sample_arrays.uuid_array AS "sample_arrays.uuid_array",
sample_arrays.mood_enum_array AS "sample_arrays.mood_enum_array";
`)
testutils.ExecuteInTxAndRollback(t, db, func(tx qrm.DB) {
var dest []model.SampleArrays
err := stmt.Query(tx, &dest)
require.NoError(t, err)
require.Len(t, dest, 1)
testutils.AssertDeepEqual(t, sampleArrayRow1Clone, dest[0])
})
})
t.Run("update using SET", func(t *testing.T) {
stmt := SampleArrays.UPDATE().
SET(
SampleArrays.Int4Array.SET(ARRAY(Int32(-10), Int32(11))),
SampleArrays.Int8Array.SET(ARRAY(Int64(-1200), Int64(7800))),
).
WHERE(String("alpha").EQ(ANY(SampleArrays.TextArray))).
RETURNING(
SampleArrays.Int4Array,
SampleArrays.Int8Array,
)
testutils.AssertStatementSql(t, stmt, `
UPDATE test_sample.sample_arrays
SET int4_array = ARRAY[$1::integer,$2::integer],
int8_array = ARRAY[$3::bigint,$4::bigint]
WHERE $5::text = ANY(sample_arrays.text_array)
RETURNING sample_arrays.int4_array AS "sample_arrays.int4_array",
sample_arrays.int8_array AS "sample_arrays.int8_array";
`)
testutils.ExecuteInTxAndRollback(t, db, func(tx qrm.DB) {
var dest []model.SampleArrays
err := stmt.Query(tx, &dest)
require.NoError(t, err)
require.Len(t, dest, 1)
testutils.AssertDeepEqual(t, dest[0].Int4Array, pq.Int32Array{-10, 11})
testutils.AssertDeepEqual(t, dest[0].Int8Array, pq.Int64Array{-1200, 7800})
})
})
}
var sampleArrayRow0 = model.SampleArrays{
ID: 1,
BoolArray: ptr.Of(pq.BoolArray{true, false, true}),
Int2ArrayPtr: ptr.Of(pq.StringArray{"1", "2", "3", "4"}),
Int4Array: pq.Int32Array{10, 20, 30, 40},
Int8Array: pq.Int64Array{100, 200, 300, 400},
NumericArray: pq.Float64Array{1.8881, 2.8882, 3.8883, 4.8884},
DecimalArray: pq.Float64Array{1.0001, 2.0002, 3.0003, 4.0004},
RealArray: pq.Float32Array{1.0099999904632568, 2.0199999809265137, 3.0299999713897705, 4.039999961853027},
DoubleArray: pq.Float64Array{11.11, 22.22, 33.33},
TextArray: pq.StringArray{"alpha", "beta", "gama"},
CharArray: pq.StringArray{"a", "b", "c"},
VarcharArray: pq.StringArray{"hello", "world"},
ByteaArray: pq.ByteaArray{{0x01, 0x02, 0x03, 0x04}, {0x11, 0x22, 0x33, 0x44}},
DateArray: pq.StringArray{"2024-11-01", "2025-02-28"},
TimestampArray: &pq.StringArray{"2025-01-01 10:00:00", "2025-02-01 10:00:00"},
TimestamptzArray: pq.StringArray{"2025-01-01 09:00:00+00", "2025-02-01 09:00:00+00"},
TimeArray: pq.StringArray{"12:00:00", "13:00:00"},
TimetzArray: pq.StringArray{"12:00:00+01", "13:00:00+02"},
IntervalArray: pq.StringArray{"1 day", "02:00:00"},
UUIDArray: pq.StringArray{"550e8400-e29b-41d4-a716-446655440000"},
MoodEnumArray: pq.StringArray{"happy", "ok"},
}

View file

@ -450,7 +450,7 @@ func TestGeneratorTemplate_Model_ChangeFieldTypes(t *testing.T) {
require.Contains(t, data, `"github.com/google/uuid"`)
require.Contains(t, data, "Description sql.NullString")
require.Contains(t, data, "ReleaseYear sql.NullInt32")
require.Contains(t, data, "SpecialFeatures sql.NullString")
require.Contains(t, data, "SpecialFeatures *pq.StringArray")
require.Contains(t, data, "LastUpdate sql.Null[uuid.UUID]")
}

View file

@ -319,7 +319,7 @@ func TestGenerator_TableMetadata(t *testing.T) {
},
}
require.Equal(t, want, got)
require.Equal(t, metadata.ArrayType, specialFeatures.DataType.Kind)
require.Equal(t, 1, specialFeatures.DataType.Dimensions)
}
func TestGeneratorSpecialCharacters(t *testing.T) {
@ -765,13 +765,13 @@ func TestGeneratedAllTypesSQLBuilderFiles(t *testing.T) {
testutils.AssertFileNamesEqual(t, modelDir, "all_types.go", "all_types_view.go", "employee.go", "link.go",
"mood.go", "person.go", "person_phone.go", "weird_names_table.go", "level.go", "user.go", "floats.go", "people.go",
"components.go", "vulnerabilities.go", "all_types_materialized_view.go", "sample_ranges.go")
"components.go", "vulnerabilities.go", "all_types_materialized_view.go", "sample_ranges.go", "sample_arrays.go")
testutils.AssertFileContent(t, modelDir+"/all_types.go", allTypesModelContent)
testutils.AssertFileContent(t, modelDir+"/link.go", linkModelContent)
testutils.AssertFileNamesEqual(t, tableDir, "all_types.go", "employee.go", "link.go",
"person.go", "person_phone.go", "weird_names_table.go", "user.go", "floats.go", "people.go", "table_use_schema.go",
"components.go", "vulnerabilities.go", "sample_ranges.go")
"components.go", "vulnerabilities.go", "sample_ranges.go", "sample_arrays.go")
testutils.AssertFileContent(t, tableDir+"/all_types.go", allTypesTableContent)
testutils.AssertFileContent(t, tableDir+"/sample_ranges.go", sampleRangeTableContent)

View file

@ -53,8 +53,8 @@ func TestRangeTableSelect(t *testing.T) {
table.SampleRanges.TimestampzRange.DIFFERENCE(tstzRange).AS("sample.tstz_diff"),
table.SampleRanges.Int4Range.UPPER_BOUND().ADD(Int(5)).AS("sample.int4_upper"),
table.SampleRanges.Int8Range.LOWER_BOUND().SUB(Int(12)).AS("sample.int8_lower"),
LOWER_BOUND[DateExpression](table.SampleRanges.DateRange),
UPPER_BOUND[NumericExpression](table.SampleRanges.NumRange).AS("sample.num_upper"),
LOWER_BOUND(table.SampleRanges.DateRange),
UPPER_BOUND(table.SampleRanges.NumRange).AS("sample.num_upper"),
table.SampleRanges.TimestampRange.UPPER_BOUND(),
table.SampleRanges.DateRange.IS_EMPTY().AS("sample.date_empty"),
table.SampleRanges.TimestampRange.LOWER_INC().AS("sample.ts_low_inc"),

View file

@ -2,8 +2,8 @@ package postgres
import (
"context"
"github.com/lib/pq"
"github.com/go-jet/jet/v2/internal/utils/ptr"
"github.com/lib/pq"
"github.com/volatiletech/null/v8"
"testing"
"time"
@ -1007,7 +1007,6 @@ func TestScanIntoCustomBaseTypes(t *testing.T) {
type MyFloat32 float32
type MyFloat64 float64
type MyString string
type MyStringArray pq.StringArray
type MyTime = time.Time
type film struct {
@ -1022,13 +1021,12 @@ func TestScanIntoCustomBaseTypes(t *testing.T) {
ReplacementCost MyFloat64
Rating *model.MpaaRating
LastUpdate MyTime
SpecialFeatures MyStringArray
SpecialFeatures pq.StringArray
Fulltext MyString
}
// We'll skip special features, because it's a slice and it does not implement sql.Scanner
stmt := SELECT(
Film.AllColumns.Except(Film.SpecialFeatures),
Film.AllColumns,
).FROM(
Film,
).ORDER_BY(

View file

@ -358,8 +358,7 @@ func TestSelectQuickStartJSON(t *testing.T) {
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate,
SELECT_JSON_ARR(
Film.AllColumns.Except(Film.SpecialFeatures),
CAST(Film.SpecialFeatures).AS_TEXT().AS("SpecialFeatures"),
Film.AllColumns,
SELECT_JSON_OBJ(
Language.AllColumns,
@ -385,7 +384,11 @@ func TestSelectQuickStartJSON(t *testing.T) {
Film.
INNER_JOIN(FilmActor, FilmActor.FilmID.EQ(Film.FilmID)),
).WHERE(
FilmActor.ActorID.EQ(Actor.ActorID).AND(Film.Length.GT(Int32(180))),
AND(
FilmActor.ActorID.EQ(Actor.ActorID),
Film.Length.GT(Int32(180)),
String("Trailers").EQ(ANY(Film.SpecialFeatures)),
),
).ORDER_BY(
Film.FilmID.ASC(),
).AS("Films"),
@ -416,8 +419,8 @@ FROM (
film.replacement_cost AS "replacementCost",
film.rating AS "rating",
to_char(film.last_update, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "lastUpdate",
film.special_features AS "specialFeatures",
film.fulltext AS "fulltext",
film.special_features::text AS "SpecialFeatures",
(
SELECT row_to_json(language_records) AS "language_json"
FROM (
@ -441,7 +444,11 @@ FROM (
) AS "Categories"
FROM dvds.film
INNER JOIN dvds.film_actor ON (film_actor.film_id = film.film_id)
WHERE (film_actor.actor_id = actor.actor_id) AND (film.length > 180::integer)
WHERE (
(film_actor.actor_id = actor.actor_id)
AND (film.length > 180::integer)
AND ('Trailers'::text = ANY(film.special_features))
)
ORDER BY film.film_id ASC
) AS films_records
) AS "Films"
@ -469,8 +476,8 @@ FROM (
return // char[n] columns whitespaces are trimmed when returned as json in cockroachdb
}
//testutils.SaveJSONFile(dest, "./testdata/results/postgres/quick-start-json-dest2.json")
//testutils.AssertJSONFile(t, dest, "./testdata/results/postgres/quick-start-json-dest.json")
//testutils.SaveJSONFile(dest, "./testdata/results/postgres/quick-start-json-dest.json")
testutils.AssertJSONFile(t, dest, "./testdata/results/postgres/quick-start-json-dest.json")
}
func TestSelectJsonInReturning(t *testing.T) {

View file

@ -2710,7 +2710,32 @@ FOR UPDATE OF "myFilm";
func TestSelectQuickStart(t *testing.T) {
var expectedSQL = `
stmt := SELECT(
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate, // list of all actor columns (equivalent to Actor.AllColumns)
Film.AllColumns, // list of all film columns (equivalent to Film.FilmID, Film.Title, ...)
Language.AllColumns.Except(Language.LastUpdate),
Category.AllColumns,
).FROM(
Actor.
INNER_JOIN(FilmActor, Actor.ActorID.EQ(FilmActor.ActorID)). // INNER JOIN Actor with FilmActor on condition Actor.ActorID = FilmActor.ActorID
INNER_JOIN(Film, Film.FilmID.EQ(FilmActor.FilmID)). // then with Film, Language, FilmCategory and Category.
INNER_JOIN(Language, Language.LanguageID.EQ(Film.LanguageID)).
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
).WHERE(
AND(
Language.Name.EQ(Char(20)("English")), // String column Language.Name and Category.Name can be compared only with string expression
Category.Name.NOT_EQ(Text("Action")),
Film.Length.GT(Int32(180)), // Film.Length is integer column and can be compared only with integer expression
Film.Rating.NOT_EQ(enum.MpaaRating.R),
String("Trailers").EQ(ANY(Film.SpecialFeatures)), // arrays element type safety is also enforced
),
).ORDER_BY(
Actor.ActorID.ASC(),
Film.FilmID.ASC(),
)
testutils.AssertStatementSql(t, stmt, `
SELECT actor.actor_id AS "actor.actor_id",
actor.first_name AS "actor.first_name",
actor.last_name AS "actor.last_name",
@ -2730,7 +2755,6 @@ SELECT actor.actor_id AS "actor.actor_id",
film.fulltext AS "film.fulltext",
language.language_id AS "language.language_id",
language.name AS "language.name",
language.last_update AS "language.last_update",
category.category_id AS "category.category_id",
category.name AS "category.name",
category.last_update AS "category.last_update"
@ -2740,33 +2764,54 @@ FROM dvds.actor
INNER JOIN dvds.language ON (language.language_id = film.language_id)
INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id)
INNER JOIN dvds.category ON (category.category_id = film_category.category_id)
WHERE (((language.name = 'English'::char(20)) AND (category.name != 'Action'::text)) AND (film.length > 180::integer)) AND (film.rating != 'R')
ORDER BY actor.actor_id ASC, film.film_id ASC;
`
stmt := SELECT(
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate, // list of all actor columns (equivalent to Actor.AllColumns)
Film.AllColumns, // list of all film columns (equivalent to Film.FilmID, Film.Title, ...)
Language.AllColumns,
Category.AllColumns,
).FROM(
Actor.
INNER_JOIN(FilmActor, Actor.ActorID.EQ(FilmActor.ActorID)). // INNER JOIN Actor with FilmActor on condition Actor.ActorID = FilmActor.ActorID
INNER_JOIN(Film, Film.FilmID.EQ(FilmActor.FilmID)). // then with Film, Language, FilmCategory and Category.
INNER_JOIN(Language, Language.LanguageID.EQ(Film.LanguageID)).
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
).WHERE(
Language.Name.EQ(Char(20)("English")). // note that every column has type.
AND(Category.Name.NOT_EQ(Text("Action"))). // String column Language.Name and Category.Name can be compared only with string expression
AND(Film.Length.GT(Int32(180))). // Film.Length is integer column and can be compared only with integer expression
AND(Film.Rating.NOT_EQ(enum.MpaaRating.R)),
).ORDER_BY(
Actor.ActorID.ASC(),
Film.FilmID.ASC(),
WHERE (
(language.name = $1::char(20))
AND (category.name != $2::text)
AND (film.length > $3::integer)
AND (film.rating != 'R')
AND ($4::text = ANY(film.special_features))
)
ORDER BY actor.actor_id ASC, film.film_id ASC;
`, "English", "Action", int32(180), "Trailers")
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, "English", "Action", int32(180))
testutils.AssertDebugStatementSql(t, stmt, `
SELECT actor.actor_id AS "actor.actor_id",
actor.first_name AS "actor.first_name",
actor.last_name AS "actor.last_name",
actor.last_update AS "actor.last_update",
film.film_id AS "film.film_id",
film.title AS "film.title",
film.description AS "film.description",
film.release_year AS "film.release_year",
film.language_id AS "film.language_id",
film.rental_duration AS "film.rental_duration",
film.rental_rate AS "film.rental_rate",
film.length AS "film.length",
film.replacement_cost AS "film.replacement_cost",
film.rating AS "film.rating",
film.last_update AS "film.last_update",
film.special_features AS "film.special_features",
film.fulltext AS "film.fulltext",
language.language_id AS "language.language_id",
language.name AS "language.name",
category.category_id AS "category.category_id",
category.name AS "category.name",
category.last_update AS "category.last_update"
FROM dvds.actor
INNER JOIN dvds.film_actor ON (actor.actor_id = film_actor.actor_id)
INNER JOIN dvds.film ON (film.film_id = film_actor.film_id)
INNER JOIN dvds.language ON (language.language_id = film.language_id)
INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id)
INNER JOIN dvds.category ON (category.category_id = film_category.category_id)
WHERE (
(language.name = 'English'::char(20))
AND (category.name != 'Action'::text)
AND (film.length > 180::integer)
AND (film.rating != 'R')
AND ('Trailers'::text = ANY(film.special_features))
)
ORDER BY actor.actor_id ASC, film.film_id ASC;
`)
var dest []struct {
model.Actor
@ -2798,7 +2843,7 @@ ORDER BY actor.actor_id ASC, film.film_id ASC;
require.NoError(t, err)
})
//testutils.SaveJSONFile(dest, "./testdata/results/postgres/quick-start-dest2.json")
//testutils.SaveJSONFile(dest2, "./testdata/results/postgres/quick-start-dest2.json")
testutils.AssertJSONFile(t, dest2, "./testdata/results/postgres/quick-start-dest2.json")
}
@ -2806,7 +2851,11 @@ func TestSelectQuickStartWithSubQueries(t *testing.T) {
filmLogerThan180 := Film.
SELECT(Film.AllColumns).
WHERE(Film.Length.GT(Int(180)).AND(Film.Rating.NOT_EQ(enum.MpaaRating.R))).
WHERE(
Film.Length.GT(Int(180)).
AND(Film.Rating.NOT_EQ(enum.MpaaRating.R)).
AND(String("Trailers").EQ(ANY(Film.SpecialFeatures))),
).
AsTable("films")
filmID := Film.FilmID.From(filmLogerThan180)
@ -2822,7 +2871,7 @@ func TestSelectQuickStartWithSubQueries(t *testing.T) {
stmt := SELECT(
Actor.AllColumns,
filmLogerThan180.AllColumns(),
Language.AllColumns,
Language.AllColumns.Except(Language.LastUpdate),
categoriesNotAction.AllColumns(),
).FROM(
Actor.

View file

@ -172,7 +172,9 @@ ORDER BY film_values.title;
"ReplacementCost": 15.99,
"Rating": "R",
"LastUpdate": "2013-05-26T14:50:58.951Z",
"SpecialFeatures": "{Trailers}",
"SpecialFeatures": [
"Trailers"
],
"Fulltext": "'airport':1 'ancient':18 'confront':14 'epic':4 'girl':11 'india':19 'monkey':16 'moos':8 'must':13 'pollock':2 'tale':5"
},
"Title": "Airport Pollock",
@ -194,7 +196,9 @@ ORDER BY film_values.title;
"ReplacementCost": 12.99,
"Rating": "PG-13",
"LastUpdate": "2013-05-26T14:50:58.951Z",
"SpecialFeatures": "{Trailers}",
"SpecialFeatures": [
"Trailers"
],
"Fulltext": "'boat':20 'bright':1 'conquer':14 'encount':2 'fate':4 'feminist':11 'jet':19 'lumberjack':8 'must':13 'student':16 'yarn':5"
},
"Title": "Bright Encounters",

@ -1 +1 @@
Subproject commit 94f3504ef8245b8f76ca391f068705ba1375c03b
Subproject commit 0387785d9e9ceacba2247d477181436f27bf2068