Add support for additional array types.
This commit is contained in:
parent
45d4ced9b0
commit
4ee047a675
47 changed files with 1994 additions and 4277 deletions
390
README.md
390
README.md
|
|
@ -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]
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -277,35 +287,40 @@ debugSql - this query string can be copy-pasted into sql editor and executed.
|
|||
|
||||
```sql
|
||||
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",
|
||||
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"
|
||||
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)) AND (film.rating != 'R')
|
||||
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;
|
||||
```
|
||||
</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
|
||||
|
|
@ -370,82 +385,88 @@ fmt.Println(string(jsonText))
|
|||
|
||||
```js
|
||||
[
|
||||
{
|
||||
"ActorID": 1,
|
||||
"FirstName": "Penelope",
|
||||
"LastName": "Guiness",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z",
|
||||
"Films": [
|
||||
{
|
||||
"FilmID": 499,
|
||||
"Title": "King Evolution",
|
||||
"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 3,
|
||||
"RentalRate": 4.99,
|
||||
"Length": 184,
|
||||
"ReplacementCost": 24.99,
|
||||
"Rating": "NC-17",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"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,
|
||||
"Name": "English ",
|
||||
"LastUpdate": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
"Categories": [
|
||||
{
|
||||
"CategoryID": 8,
|
||||
"Name": "Family",
|
||||
"LastUpdate": "2006-02-15T09:46:27Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ActorID": 3,
|
||||
"FirstName": "Ed",
|
||||
"LastName": "Chase",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z",
|
||||
"Films": [
|
||||
{
|
||||
"FilmID": 996,
|
||||
"Title": "Young Language",
|
||||
"Description": "A Unbelieveable Yarn of a Boat And a Database Administrator who must Meet a Boy in The First Manned Space Station",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 6,
|
||||
"RentalRate": 0.99,
|
||||
"Length": 183,
|
||||
"ReplacementCost": 9.99,
|
||||
"Rating": "G",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"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,
|
||||
"Name": "English ",
|
||||
"LastUpdate": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
"Categories": [
|
||||
{
|
||||
"CategoryID": 6,
|
||||
"Name": "Documentary",
|
||||
"LastUpdate": "2006-02-15T09:46:27Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
//...(125 more items)
|
||||
{
|
||||
"ActorID": 1,
|
||||
"FirstName": "Penelope",
|
||||
"LastName": "Guiness",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z",
|
||||
"Films": [
|
||||
{
|
||||
"FilmID": 499,
|
||||
"Title": "King Evolution",
|
||||
"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 3,
|
||||
"RentalRate": 4.99,
|
||||
"Length": 184,
|
||||
"ReplacementCost": 24.99,
|
||||
"Rating": "NC-17",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"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,
|
||||
"Name": "English ",
|
||||
"LastUpdate": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
"Categories": [
|
||||
{
|
||||
"CategoryID": 8,
|
||||
"Name": "Family",
|
||||
"LastUpdate": "2006-02-15T09:46:27Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ActorID": 3,
|
||||
"FirstName": "Ed",
|
||||
"LastName": "Chase",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z",
|
||||
"Films": [
|
||||
{
|
||||
"FilmID": 996,
|
||||
"Title": "Young Language",
|
||||
"Description": "A Unbelieveable Yarn of a Boat And a Database Administrator who must Meet a Boy in The First Manned Space Station",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 6,
|
||||
"RentalRate": 0.99,
|
||||
"Length": 183,
|
||||
"ReplacementCost": 9.99,
|
||||
"Rating": "G",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"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,
|
||||
"Name": "English ",
|
||||
"LastUpdate": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
"Categories": [
|
||||
{
|
||||
"CategoryID": 6,
|
||||
"Name": "Documentary",
|
||||
"LastUpdate": "2006-02-15T09:46:27Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
//...(125 more items)
|
||||
]
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -464,88 +485,71 @@ handleError(err)
|
|||
|
||||
```js
|
||||
[
|
||||
{
|
||||
"CategoryID": 8,
|
||||
"Name": "Family",
|
||||
"LastUpdate": "2006-02-15T09:46:27Z",
|
||||
"Films": [
|
||||
{
|
||||
"FilmID": 499,
|
||||
"Title": "King Evolution",
|
||||
"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 3,
|
||||
"RentalRate": 4.99,
|
||||
"Length": 184,
|
||||
"ReplacementCost": 24.99,
|
||||
"Rating": "NC-17",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"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": [
|
||||
{
|
||||
"ActorID": 1,
|
||||
"FirstName": "Penelope",
|
||||
"LastName": "Guiness",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 20,
|
||||
"FirstName": "Lucille",
|
||||
"LastName": "Tracy",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 36,
|
||||
"FirstName": "Burt",
|
||||
"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",
|
||||
"LastName": "Allen",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 187,
|
||||
"FirstName": "Renee",
|
||||
"LastName": "Ball",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 198,
|
||||
"FirstName": "Mary",
|
||||
"LastName": "Keitel",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
//...
|
||||
{
|
||||
"CategoryID": 8,
|
||||
"Name": "Family",
|
||||
"LastUpdate": "2006-02-15T09:46:27Z",
|
||||
"Films": [
|
||||
{
|
||||
"FilmID": 499,
|
||||
"Title": "King Evolution",
|
||||
"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 3,
|
||||
"RentalRate": 4.99,
|
||||
"Length": 184,
|
||||
"ReplacementCost": 24.99,
|
||||
"Rating": "NC-17",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"Actors": [
|
||||
{
|
||||
"ActorID": 1,
|
||||
"FirstName": "Penelope",
|
||||
"LastName": "Guiness",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 20,
|
||||
"FirstName": "Lucille",
|
||||
"LastName": "Tracy",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 36,
|
||||
"FirstName": "Burt",
|
||||
"LastName": "Dukakis",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 118,
|
||||
"FirstName": "Cuba",
|
||||
"LastName": "Allen",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 187,
|
||||
"FirstName": "Renee",
|
||||
"LastName": "Ball",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 198,
|
||||
"FirstName": "Mary",
|
||||
"LastName": "Keitel",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
// ...
|
||||
]
|
||||
```
|
||||
</details>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
|
||||
# Quick start example
|
||||
|
||||
This package contains sample usage for Jet framework.
|
||||
|
||||
Jet generated files of interest are in `./gen` folder.
|
||||
|
||||
`quick-start.go` - contains code explained at main [README.md](../../README.md#quick-start),
|
||||
with a difference of redirecting json output to files(`dest.json` and `dest2.json`) rather then to a
|
||||
standard output.
|
||||
|
||||
`./gen`, `dest.json` and `dest2.json` - added into git for presentation purposes.
|
||||
|
||||
# Quick start example
|
||||
|
||||
This package contains sample usage for Jet framework.
|
||||
|
||||
Jet generated files of interest are in `./gen` folder.
|
||||
|
||||
`quick-start.go` - contains code explained at main [README.md](../../README.md#quick-start),
|
||||
with a difference of redirecting json output to files(`dest.json` and `dest2.json`) rather then to a
|
||||
standard output.
|
||||
|
||||
`./gen`, `dest.json` and `dest2.json` - added into git for presentation purposes.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,23 +66,25 @@ 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'
|
||||
end) as "dataType.Kind",
|
||||
(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",
|
||||
end) as "dataType.Name",
|
||||
false as "dataType.isUnsigned"
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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("]")))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
column: a,
|
||||
toAssign: stringExp,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"`)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) + `"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
33
postgres/array_columns.go
Executable 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]
|
||||
)
|
||||
|
|
@ -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[]"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
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)
|
||||
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)),
|
||||
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";
|
||||
`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
719
tests/postgres/array_test.go
Normal file
719
tests/postgres/array_test.go
Normal 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"},
|
||||
}
|
||||
|
|
@ -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]")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
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")
|
||||
|
||||
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(),
|
||||
)
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue