Merge pull request #525 from go-jet/pg_arrays
Add support for postgres array types
This commit is contained in:
commit
937459b9cd
45 changed files with 2475 additions and 4033 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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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"
|
||||
)
|
||||
|
||||
|
|
@ -28,4 +27,9 @@ type DataType struct {
|
|||
Name string
|
||||
Kind DataTypeKind
|
||||
IsUnsigned bool
|
||||
Dimensions int // The number of array dimensions
|
||||
}
|
||||
|
||||
func (d DataType) IsArray() bool {
|
||||
return d.Dimensions > 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,22 +66,25 @@ select
|
|||
not attr.attnotnull as "column.isNullable",
|
||||
attr.attgenerated = 's' as "column.isGenerated",
|
||||
attr.atthasdef as "column.hasDefault",
|
||||
(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,6 +2,7 @@ package template
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/lib/pq"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
|
@ -247,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))
|
||||
|
|
@ -260,7 +272,7 @@ func getUserDefinedType(column metadata.Column) string {
|
|||
switch column.DataType.Kind {
|
||||
case metadata.EnumType:
|
||||
return dbidentifier.ToGoIdentifier(column.DataType.Name)
|
||||
case metadata.UserDefinedType, metadata.ArrayType:
|
||||
case metadata.UserDefinedType:
|
||||
return "string"
|
||||
}
|
||||
|
||||
|
|
@ -268,17 +280,45 @@ 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{} {
|
||||
|
||||
switch strings.ToLower(column.DataType.Name) {
|
||||
case "user-defined", "enum":
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -162,11 +162,32 @@ 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 {
|
||||
switch columnMetaData.DataType.Kind {
|
||||
case metadata.EnumType, metadata.UserDefinedType:
|
||||
if columnMetaData.DataType.IsArray() {
|
||||
return "StringArray"
|
||||
}
|
||||
return "String"
|
||||
}
|
||||
|
||||
columnType := sqlToColumnType(columnMetaData)
|
||||
|
||||
if columnMetaData.DataType.IsArray() {
|
||||
if columnMetaData.DataType.Dimensions > 1 {
|
||||
fmt.Println("- [SQL Builder] Unsupported sql array with multiple dimensions column '" +
|
||||
columnMetaData.Name + " " + columnMetaData.DataType.Name + "', using StringColumn instead.")
|
||||
return "String"
|
||||
}
|
||||
|
||||
columnType = columnType + "Array"
|
||||
}
|
||||
|
||||
return columnType
|
||||
}
|
||||
|
||||
// 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"
|
||||
|
|
|
|||
136
internal/jet/array_expression.go
Normal file
136
internal/jet/array_expression.go
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
package jet
|
||||
|
||||
// Array interface
|
||||
type Array[E Expression] interface {
|
||||
Expression
|
||||
|
||||
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 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) E
|
||||
}
|
||||
|
||||
type arrayInterfaceImpl[E Expression] struct {
|
||||
parent Array[E]
|
||||
}
|
||||
|
||||
type BinaryBoolOp func(Expression, Expression) BoolExpression
|
||||
|
||||
func (a arrayInterfaceImpl[E]) EQ(rhs Array[E]) BoolExpression {
|
||||
return Eq(a.parent, rhs)
|
||||
}
|
||||
|
||||
func (a arrayInterfaceImpl[E]) NOT_EQ(rhs Array[E]) BoolExpression {
|
||||
return NotEq(a.parent, rhs)
|
||||
}
|
||||
|
||||
func (a arrayInterfaceImpl[E]) LT(rhs Array[E]) BoolExpression {
|
||||
return Lt(a.parent, rhs)
|
||||
}
|
||||
|
||||
func (a arrayInterfaceImpl[E]) GT(rhs Array[E]) BoolExpression {
|
||||
return Gt(a.parent, rhs)
|
||||
}
|
||||
|
||||
func (a arrayInterfaceImpl[E]) LT_EQ(rhs Array[E]) BoolExpression {
|
||||
return LtEq(a.parent, rhs)
|
||||
}
|
||||
|
||||
func (a arrayInterfaceImpl[E]) GT_EQ(rhs Array[E]) BoolExpression {
|
||||
return GtEq(a.parent, rhs)
|
||||
}
|
||||
|
||||
func (a arrayInterfaceImpl[E]) CONTAINS(rhs Array[E]) BoolExpression {
|
||||
return Contains(a.parent, rhs)
|
||||
}
|
||||
|
||||
func (a arrayInterfaceImpl[E]) IS_CONTAINED_BY(rhs Array[E]) BoolExpression {
|
||||
return IsContainedBy(a.parent, rhs)
|
||||
}
|
||||
|
||||
func (a arrayInterfaceImpl[E]) OVERLAP(rhs Array[E]) BoolExpression {
|
||||
return Overlap(a.parent, rhs)
|
||||
}
|
||||
|
||||
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) Array[E] {
|
||||
return ArrayExp[E](NewBinaryOperatorExpression(a.parent, rhs, "||"))
|
||||
}
|
||||
|
||||
func (a arrayInterfaceImpl[E]) AT(at IntegerExpression) E {
|
||||
return CastToArrayElemType[E](a.parent, CustomExpression(a.parent, Token("["), at, Token("]")))
|
||||
}
|
||||
|
||||
type arrayExpressionWrapper[E Expression] struct {
|
||||
arrayInterfaceImpl[E]
|
||||
Expression
|
||||
}
|
||||
|
||||
func newArrayExpressionWrap[E Expression](expression Expression) Array[E] {
|
||||
arrayExpressionWrapper := arrayExpressionWrapper[E]{Expression: expression}
|
||||
arrayExpressionWrapper.arrayInterfaceImpl.parent = &arrayExpressionWrapper
|
||||
return &arrayExpressionWrapper
|
||||
}
|
||||
|
||||
// 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) 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("]")))
|
||||
}
|
||||
68
internal/jet/array_expression_test.go
Normal file
68
internal/jet/array_expression_test.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestArrayExpressionEQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColStringArray.EQ(table2ColArray), "(table1.col_array_string = table2.col_array_string)")
|
||||
}
|
||||
|
||||
func TestArrayExpressionNOT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColStringArray.NOT_EQ(table2ColArray), "(table1.col_array_string != table2.col_array_string)")
|
||||
}
|
||||
|
||||
func TestArrayExpressionLT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColStringArray.LT(table2ColArray), "(table1.col_array_string < table2.col_array_string)")
|
||||
}
|
||||
|
||||
func TestArrayExpressionGT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColStringArray.GT(table2ColArray), "(table1.col_array_string > table2.col_array_string)")
|
||||
}
|
||||
|
||||
func TestArrayExpressionLT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColStringArray.LT_EQ(table2ColArray), "(table1.col_array_string <= table2.col_array_string)")
|
||||
}
|
||||
|
||||
func TestArrayExpressionGT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColStringArray.GT_EQ(table2ColArray), "(table1.col_array_string >= table2.col_array_string)")
|
||||
}
|
||||
|
||||
func TestArrayExpressionCONTAINS(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColStringArray.CONTAINS(table2ColArray), "(table1.col_array_string @> table2.col_array_string)")
|
||||
}
|
||||
|
||||
func TestArrayExpressionCONTAINED_BY(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColStringArray.IS_CONTAINED_BY(table2ColArray), "(table1.col_array_string <@ table2.col_array_string)")
|
||||
}
|
||||
|
||||
func TestArrayExpressionOVERLAP(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColStringArray.OVERLAP(table2ColArray), "(table1.col_array_string && table2.col_array_string)")
|
||||
}
|
||||
|
||||
func TestArrayExpressionCONCAT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColStringArray.CONCAT(table2ColArray), "(table1.col_array_string || table2.col_array_string)")
|
||||
}
|
||||
|
||||
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(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))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
@ -133,6 +133,50 @@ func IntegerColumn(name string) ColumnInteger {
|
|||
|
||||
//------------------------------------------------------//
|
||||
|
||||
type ColumnArray[E Expression] interface {
|
||||
Array[E]
|
||||
Column
|
||||
|
||||
From(subQuery SelectTable) ColumnArray[E]
|
||||
SET(stringExp Array[E]) ColumnAssigment
|
||||
}
|
||||
|
||||
type arrayColumnImpl[E Expression] struct {
|
||||
arrayInterfaceImpl[E]
|
||||
|
||||
*ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (a arrayColumnImpl[E]) fromImpl(subQuery SelectTable) Projection {
|
||||
return a.From(subQuery)
|
||||
}
|
||||
|
||||
func (a arrayColumnImpl[E]) From(subQuery SelectTable) ColumnArray[E] {
|
||||
newArrayColumn := ArrayColumn[E](a.name)
|
||||
newArrayColumn.setTableName(a.tableName)
|
||||
newArrayColumn.setSubQuery(subQuery)
|
||||
|
||||
return newArrayColumn
|
||||
}
|
||||
|
||||
func (a *arrayColumnImpl[E]) SET(stringExp Array[E]) ColumnAssigment {
|
||||
return columnAssigmentImpl{
|
||||
column: a,
|
||||
toAssign: stringExp,
|
||||
}
|
||||
}
|
||||
|
||||
// StringColumn creates named string column.
|
||||
func ArrayColumn[E Expression](name string) ColumnArray[E] {
|
||||
arrayColumn := &arrayColumnImpl[E]{}
|
||||
arrayColumn.arrayInterfaceImpl.parent = arrayColumn
|
||||
arrayColumn.ColumnExpressionImpl = NewColumnImpl(name, "", arrayColumn)
|
||||
|
||||
return arrayColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnString is interface for SQL text, character, character varying
|
||||
// uuid columns and enums types.
|
||||
type ColumnString interface {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,36 @@ var subQuery = &selectTableImpl{
|
|||
alias: "sub_query",
|
||||
}
|
||||
|
||||
func TestNewArrayColumnString(t *testing.T) {
|
||||
stringArrayColumn := ArrayColumn[StringExpression]("colArray").From(subQuery)
|
||||
assertClauseSerialize(t, stringArrayColumn, `sub_query."colArray"`)
|
||||
assertProjectionSerialize(t, stringArrayColumn, `sub_query."colArray" AS "colArray"`)
|
||||
|
||||
arrayColumn2 := table1ColStringArray.From(subQuery)
|
||||
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_string"`)
|
||||
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"`)
|
||||
assertProjectionSerialize(t, boolArrayColumn, `sub_query."colArrayBool" AS "colArrayBool"`)
|
||||
|
||||
arrayColumn2 := table1ColBoolArray.From(subQuery)
|
||||
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_bool"`)
|
||||
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"`)
|
||||
assertProjectionSerialize(t, intArrayColumn, `sub_query."colArrayInt" AS "colArrayInt"`)
|
||||
|
||||
arrayColumn2 := table1ColIntArray.From(subQuery)
|
||||
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_int"`)
|
||||
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_int" AS "table1.col_array_int"`)
|
||||
}
|
||||
|
||||
func TestNewBoolColumn(t *testing.T) {
|
||||
boolColumn := BoolColumn("colBool").From(subQuery)
|
||||
assertClauseSerialize(t, boolColumn, `sub_query."colBool"`)
|
||||
|
|
|
|||
|
|
@ -74,6 +74,11 @@ func Contains(lhs Expression, rhs Expression) BoolExpression {
|
|||
return newBinaryBoolOperatorExpression(lhs, rhs, "@>")
|
||||
}
|
||||
|
||||
// IsContainedBy returns a representation of "a <@ b"
|
||||
func IsContainedBy(lhs Expression, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(lhs, rhs, "<@")
|
||||
}
|
||||
|
||||
// Overlap returns a representation of "a && b"
|
||||
func Overlap(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(lhs, rhs, "&&")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -93,11 +92,11 @@ func (s *SQLBuilder) write(data []byte) {
|
|||
}
|
||||
|
||||
func isPreSeparator(b byte) bool {
|
||||
return b == ' ' || b == '.' || b == ',' || b == '(' || b == '\n' || b == ':'
|
||||
return b == ' ' || b == '.' || b == ',' || b == '(' || b == '\n' || b == ':' || b == '['
|
||||
}
|
||||
|
||||
func isPostSeparator(b byte) bool {
|
||||
return b == ' ' || b == '.' || b == ',' || b == ')' || b == '\n' || b == ':'
|
||||
return b == ' ' || b == '.' || b == ',' || b == ')' || b == '\n' || b == ':' || b == '[' || b == ']'
|
||||
}
|
||||
|
||||
// WriteAlias is used to add alias to output SQL
|
||||
|
|
|
|||
|
|
@ -18,19 +18,22 @@ var defaultDialect = NewDialect(DialectParams{ // just for tests
|
|||
})
|
||||
|
||||
var (
|
||||
table1Col1 = IntegerColumn("col1")
|
||||
table1ColInt = IntegerColumn("col_int")
|
||||
table1ColFloat = FloatColumn("col_float")
|
||||
table1Col3 = IntegerColumn("col3")
|
||||
table1ColTime = TimeColumn("col_time")
|
||||
table1ColTimez = TimezColumn("col_timez")
|
||||
table1ColTimestamp = TimestampColumn("col_timestamp")
|
||||
table1ColTimestampz = TimestampzColumn("col_timestampz")
|
||||
table1ColBool = BoolColumn("col_bool")
|
||||
table1ColDate = DateColumn("col_date")
|
||||
table1ColRange = RangeColumn[Int8Expression]("col_range")
|
||||
table1Col1 = IntegerColumn("col1")
|
||||
table1ColInt = IntegerColumn("col_int")
|
||||
table1ColFloat = FloatColumn("col_float")
|
||||
table1Col3 = IntegerColumn("col3")
|
||||
table1ColTime = TimeColumn("col_time")
|
||||
table1ColTimez = TimezColumn("col_timez")
|
||||
table1ColTimestamp = TimestampColumn("col_timestamp")
|
||||
table1ColTimestampz = TimestampzColumn("col_timestampz")
|
||||
table1ColBool = BoolColumn("col_bool")
|
||||
table1ColDate = DateColumn("col_date")
|
||||
table1ColRange = RangeColumn[Int8Expression]("col_range")
|
||||
table1ColStringArray = ArrayColumn[StringExpression]("col_array_string")
|
||||
table1ColBoolArray = ArrayColumn[BoolExpression]("col_array_bool")
|
||||
table1ColIntArray = ArrayColumn[IntegerExpression]("col_array_int")
|
||||
)
|
||||
var table1 = NewTable("db", "table1", "", table1Col1, table1ColInt, table1ColFloat, table1Col3, table1ColTime, table1ColTimez, table1ColBool, table1ColDate, table1ColRange, table1ColTimestamp, table1ColTimestampz)
|
||||
var table1 = NewTable("db", "table1", "", table1Col1, table1ColInt, table1ColFloat, table1Col3, table1ColTime, table1ColTimez, table1ColBool, table1ColDate, table1ColRange, table1ColTimestamp, table1ColTimestampz, table1ColStringArray, table1ColBoolArray, table1ColIntArray)
|
||||
|
||||
var (
|
||||
table2Col3 = IntegerColumn("col3")
|
||||
|
|
@ -45,8 +48,9 @@ var (
|
|||
table2ColTimestampz = TimestampzColumn("col_timestampz")
|
||||
table2ColDate = DateColumn("col_date")
|
||||
table2ColRange = RangeColumn[Int8Expression]("col_range")
|
||||
table2ColArray = ArrayColumn[StringExpression]("col_array_string")
|
||||
)
|
||||
var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColRange, table2ColTimestamp, table2ColTimestampz)
|
||||
var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColRange, table2ColTimestamp, table2ColTimestampz, table2ColArray)
|
||||
|
||||
var (
|
||||
table3Col1 = IntegerColumn("col1")
|
||||
|
|
|
|||
|
|
@ -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[]"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ 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
|
||||
|
||||
|
|
@ -176,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, start ...IntegerExpression) IntegerExpression {
|
||||
return IntExp(Func("ARRAY_POSITION", optionalAppend([]Expression{arr, elem}, start)...))
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package postgres
|
||||
|
||||
import (
|
||||
"github.com/lib/pq"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -34,3 +35,41 @@ func TestGENERATE_SERIES(t *testing.T) {
|
|||
"GENERATE_SERIES(NOW(), NOW() + INTERVAL '10 DAY', INTERVAL '2 DAY')",
|
||||
)
|
||||
}
|
||||
|
||||
func TestArrayFunctions(t *testing.T) {
|
||||
|
||||
intArray := Int32Array(1, 2, 3)
|
||||
stringArrayColumn := StringArrayColumn("str_arr_col")
|
||||
|
||||
assertSerialize(t, ARRAY_LOWER(intArray), "ARRAY_LOWER($1::integer[])", pq.Int32Array{1, 2, 3})
|
||||
assertSerialize(t, ARRAY_DIMS(stringArrayColumn).EQ(String("[1,1]")),
|
||||
"(ARRAY_DIMS(str_arr_col) = $1::text)",
|
||||
"[1,1]",
|
||||
)
|
||||
assertSerialize(t, ARRAY_NDIMS(stringArrayColumn).EQ(Int(1)), "(ARRAY_NDIMS(str_arr_col) = $1)", int64(1))
|
||||
assertSerialize(t, ARRAY_REVERSE(stringArrayColumn), "ARRAY_REVERSE(str_arr_col)")
|
||||
assertSerialize(t, ARRAY_SAMPLE(stringArrayColumn, Int(2)).AT(Int(1)).EQ(String("john")),
|
||||
"(ARRAY_SAMPLE(str_arr_col, $1)[$2] = $3::text)",
|
||||
int64(2), int64(1), "john",
|
||||
)
|
||||
|
||||
assertSerialize(t, ARRAY_SHUFFLE(intArray).AT(Int(2)).EQ(Int(33)),
|
||||
"(ARRAY_SHUFFLE($1::integer[])[$2] = $3)",
|
||||
pq.Int32Array{1, 2, 3}, int64(2), int64(33),
|
||||
)
|
||||
|
||||
assertSerialize(t, ARRAY_SORT(stringArrayColumn, Bool(true)),
|
||||
"ARRAY_SORT(str_arr_col, $1::boolean)",
|
||||
true,
|
||||
)
|
||||
|
||||
assertSerialize(t, ARRAY_SORT(stringArrayColumn, Bool(true), Bool(false)),
|
||||
"ARRAY_SORT(str_arr_col, $1::boolean, $2::boolean)",
|
||||
true, false,
|
||||
)
|
||||
|
||||
assertSerialize(t, TRIM_ARRAY(stringArrayColumn, Int(6)),
|
||||
"TRIM_ARRAY(str_arr_col, $1)",
|
||||
int64(6),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package postgres
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/lib/pq"
|
||||
"time"
|
||||
|
||||
"github.com/go-jet/jet/v2/internal/jet"
|
||||
|
|
@ -68,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.
|
||||
//
|
||||
|
|
@ -124,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 {
|
||||
|
|
@ -185,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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ var table1ColBool = BoolColumn("col_bool")
|
|||
var table1ColDate = DateColumn("col_date")
|
||||
var table1ColInterval = IntervalColumn("col_interval")
|
||||
var table1ColRange = Int8RangeColumn("col_range")
|
||||
var table1ColStringArray = StringArrayColumn("col_string_array")
|
||||
var table1ColIntArray = IntegerArrayColumn("col_int_array")
|
||||
|
||||
var table1 = NewTable(
|
||||
"db",
|
||||
|
|
@ -34,6 +36,8 @@ var table1 = NewTable(
|
|||
table1ColTimestampz,
|
||||
table1ColInterval,
|
||||
table1ColRange,
|
||||
table1ColStringArray,
|
||||
table1ColIntArray,
|
||||
)
|
||||
|
||||
var table2Col3 = IntegerColumn("col3")
|
||||
|
|
@ -49,8 +53,10 @@ var table2ColTimestampz = TimestampzColumn("col_timestampz")
|
|||
var table2ColDate = DateColumn("col_date")
|
||||
var table2ColInterval = IntervalColumn("col_interval")
|
||||
var table2ColRange = Int8RangeColumn("col_range")
|
||||
var table2ColStringArray = StringArrayColumn("col_string_array")
|
||||
var table2ColIntArray = IntegerArrayColumn("col_int_array")
|
||||
|
||||
var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColTimestamp, table2ColTimestampz, table2ColInterval, table2ColRange)
|
||||
var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColTimestamp, table2ColTimestampz, table2ColInterval, table2ColRange, table2ColStringArray, table2ColIntArray)
|
||||
|
||||
var table3Col1 = IntegerColumn("col1")
|
||||
var table3ColInt = IntegerColumn("col_int")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
version: '3'
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14.1
|
||||
|
|
@ -13,7 +12,7 @@ services:
|
|||
- ./testdata/init/postgres:/docker-entrypoint-initdb.d
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0.27
|
||||
image: mysql/mysql-server:8.0.27
|
||||
command: ['--default-authentication-plugin=mysql_native_password', '--log_bin_trust_function_creators=1']
|
||||
restart: always
|
||||
environment:
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ package postgres
|
|||
|
||||
import (
|
||||
"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"
|
||||
|
||||
|
|
@ -44,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
|
||||
|
|
@ -53,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)
|
||||
|
|
@ -115,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
|
||||
|
|
@ -257,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{}
|
||||
|
|
@ -873,10 +868,7 @@ FROM (
|
|||
|
||||
err := stmtJson.QueryContext(ctx, db, &destSelectJson)
|
||||
require.NoError(t, err)
|
||||
testutils.PrintJson(destSelectJson)
|
||||
|
||||
require.Equal(t, dest, destSelectJson)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1581,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',
|
||||
|
|
@ -2243,11 +2233,11 @@ var allTypesRow0 = model.AllTypes{
|
|||
JSON: `{"a": 1, "b": 3}`,
|
||||
JsonbPtr: ptr.Of(`{"a": 1, "b": 3}`),
|
||||
Jsonb: `{"a": 1, "b": 3}`,
|
||||
IntegerArrayPtr: ptr.Of("{1,2,3}"),
|
||||
IntegerArray: "{1,2,3}",
|
||||
TextArrayPtr: ptr.Of("{breakfast,consulting}"),
|
||||
TextArray: "{breakfast,consulting}",
|
||||
JsonbArray: `{"{\"a\": 1, \"b\": 2}","{\"a\": 3, \"b\": 4}"}`,
|
||||
IntegerArrayPtr: &pq.Int32Array{1, 2, 3},
|
||||
IntegerArray: pq.Int32Array{1, 2, 3},
|
||||
TextArrayPtr: &pq.StringArray{"breakfast", "consulting"},
|
||||
TextArray: pq.StringArray{"breakfast", "consulting"},
|
||||
JsonbArray: pq.StringArray{`{"a": 1, "b": 2}`, `{"a": 3, "b": 4}`},
|
||||
TextMultiDimArrayPtr: ptr.Of("{{meeting,lunch},{training,presentation}}"),
|
||||
TextMultiDimArray: "{{meeting,lunch},{training,presentation}}",
|
||||
MoodPtr: &moodSad,
|
||||
|
|
@ -2312,10 +2302,10 @@ var allTypesRow1 = model.AllTypes{
|
|||
JsonbPtr: nil,
|
||||
Jsonb: `{"a": 1, "b": 3}`,
|
||||
IntegerArrayPtr: nil,
|
||||
IntegerArray: "{1,2,3}",
|
||||
IntegerArray: pq.Int32Array{1, 2, 3},
|
||||
TextArrayPtr: nil,
|
||||
TextArray: "{breakfast,consulting}",
|
||||
JsonbArray: `{"{\"a\": 1, \"b\": 2}","{\"a\": 3, \"b\": 4}"}`,
|
||||
TextArray: pq.StringArray{"breakfast", "consulting"},
|
||||
JsonbArray: pq.StringArray{`{"a": 1, "b": 2}`, `{"a": 3, "b": 4}`},
|
||||
TextMultiDimArrayPtr: nil,
|
||||
TextMultiDimArray: "{{meeting,lunch},{training,presentation}}",
|
||||
MoodPtr: nil,
|
||||
|
|
|
|||
726
tests/postgres/array_test.go
Normal file
726
tests/postgres/array_test.go
Normal file
|
|
@ -0,0 +1,726 @@
|
|||
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"),
|
||||
ARRAY_POSITION(SampleArrays.Int4Array, Int32(30)).AS("position"),
|
||||
//ARRAY_POSITION(SampleArrays.DoubleArray, Float(33.33), Int(1)).AS("position_from"),
|
||||
ARRAY_POSITIONS(stringArray, String("text")).AS("positions"),
|
||||
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_POSITION(sample_arrays.int4_array, $37::integer) AS "position",
|
||||
ARRAY_POSITIONS($38::text[], $39::text) AS "positions",
|
||||
ARRAY_PREPEND($40::text, sample_arrays.text_array) AS "prepend",
|
||||
ARRAY_REMOVE($41::boolean[], $42::boolean) AS "remove",
|
||||
ARRAY_REPLACE(sample_arrays.varchar_array, $43::text, $44::text) AS "replace",
|
||||
ARRAY_TO_STRING(sample_arrays.mood_enum_array, $45::text) AS "to_string",
|
||||
ARRAY_UPPER(sample_arrays.int8_array, $46) AS "upper",
|
||||
CARDINALITY(sample_arrays.double_array) AS "cardinality"
|
||||
FROM test_sample.sample_arrays
|
||||
WHERE sample_arrays.bool_array @> $47::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 *int32
|
||||
PositionFrom *int32
|
||||
Positions 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": 3,
|
||||
"PositionFrom": null,
|
||||
"Positions": [
|
||||
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)
|
||||
|
||||
|
|
@ -844,6 +844,7 @@ package model
|
|||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -902,11 +903,11 @@ type AllTypes struct {
|
|||
JSON string
|
||||
JsonbPtr *string
|
||||
Jsonb string
|
||||
IntegerArrayPtr *string
|
||||
IntegerArray string
|
||||
TextArrayPtr *string
|
||||
TextArray string
|
||||
JsonbArray string
|
||||
IntegerArrayPtr *pq.Int32Array
|
||||
IntegerArray pq.Int32Array
|
||||
TextArrayPtr *pq.StringArray
|
||||
TextArray pq.StringArray
|
||||
JsonbArray pq.StringArray
|
||||
TextMultiDimArrayPtr *string
|
||||
TextMultiDimArray string
|
||||
MoodPtr *Mood
|
||||
|
|
@ -1007,11 +1008,11 @@ type allTypesTable struct {
|
|||
JSON postgres.ColumnString
|
||||
JsonbPtr postgres.ColumnString
|
||||
Jsonb postgres.ColumnString
|
||||
IntegerArrayPtr postgres.ColumnString
|
||||
IntegerArray postgres.ColumnString
|
||||
TextArrayPtr postgres.ColumnString
|
||||
TextArray postgres.ColumnString
|
||||
JsonbArray postgres.ColumnString
|
||||
IntegerArrayPtr postgres.ColumnIntegerArray
|
||||
IntegerArray postgres.ColumnIntegerArray
|
||||
TextArrayPtr postgres.ColumnStringArray
|
||||
TextArray postgres.ColumnStringArray
|
||||
JsonbArray postgres.ColumnStringArray
|
||||
TextMultiDimArrayPtr postgres.ColumnString
|
||||
TextMultiDimArray postgres.ColumnString
|
||||
MoodPtr postgres.ColumnString
|
||||
|
|
@ -1111,11 +1112,11 @@ func newAllTypesTableImpl(schemaName, tableName, alias string) allTypesTable {
|
|||
JSONColumn = postgres.StringColumn("json")
|
||||
JsonbPtrColumn = postgres.StringColumn("jsonb_ptr")
|
||||
JsonbColumn = postgres.StringColumn("jsonb")
|
||||
IntegerArrayPtrColumn = postgres.StringColumn("integer_array_ptr")
|
||||
IntegerArrayColumn = postgres.StringColumn("integer_array")
|
||||
TextArrayPtrColumn = postgres.StringColumn("text_array_ptr")
|
||||
TextArrayColumn = postgres.StringColumn("text_array")
|
||||
JsonbArrayColumn = postgres.StringColumn("jsonb_array")
|
||||
IntegerArrayPtrColumn = postgres.IntegerArrayColumn("integer_array_ptr")
|
||||
IntegerArrayColumn = postgres.IntegerArrayColumn("integer_array")
|
||||
TextArrayPtrColumn = postgres.StringArrayColumn("text_array_ptr")
|
||||
TextArrayColumn = postgres.StringArrayColumn("text_array")
|
||||
JsonbArrayColumn = postgres.StringArrayColumn("jsonb_array")
|
||||
TextMultiDimArrayPtrColumn = postgres.StringColumn("text_multi_dim_array_ptr")
|
||||
TextMultiDimArrayColumn = postgres.StringColumn("text_multi_dim_array")
|
||||
MoodPtrColumn = postgres.StringColumn("mood_ptr")
|
||||
|
|
@ -1687,3 +1688,192 @@ func TestCamelCaseModelJsonTag(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratorTestSample(t *testing.T) {
|
||||
|
||||
err := postgres.GenerateDSN(defaultDSN(), "test_sample", genTestDir2)
|
||||
require.NoError(t, err)
|
||||
|
||||
modelDir := filepath.Join(testRoot, "/postgres/.gentestdata2/jetdb/test_sample/model/")
|
||||
tableDir := filepath.Join(testRoot, "/postgres/.gentestdata2/jetdb/test_sample/table/")
|
||||
|
||||
testutils.AssertFileContent(t, modelDir+"/sample_arrays.go", `
|
||||
//
|
||||
// Code generated by go-jet DO NOT EDIT.
|
||||
//
|
||||
// WARNING: Changes to this file may cause incorrect behavior
|
||||
// and will be lost if the code is regenerated
|
||||
//
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type SampleArrays struct {
|
||||
ID int32 `+"`sql:\"primary_key\"`"+`
|
||||
BoolArray *pq.BoolArray
|
||||
Int2ArrayPtr *pq.StringArray
|
||||
Int4Array pq.Int32Array
|
||||
Int8Array pq.Int64Array
|
||||
NumericArray pq.Float64Array
|
||||
DecimalArray pq.Float64Array
|
||||
RealArray pq.Float32Array
|
||||
DoubleArray pq.Float64Array
|
||||
TextArray pq.StringArray
|
||||
VarcharArray pq.StringArray
|
||||
CharArray pq.StringArray
|
||||
ByteaArray pq.ByteaArray
|
||||
DateArray pq.StringArray
|
||||
TimestampArray *pq.StringArray
|
||||
TimestamptzArray pq.StringArray
|
||||
TimeArray pq.StringArray
|
||||
TimetzArray pq.StringArray
|
||||
IntervalArray pq.StringArray
|
||||
UUIDArray pq.StringArray
|
||||
MoodEnumArray pq.StringArray
|
||||
}
|
||||
`)
|
||||
|
||||
testutils.AssertFileContent(t, tableDir+"/sample_arrays.go", `
|
||||
//
|
||||
// Code generated by go-jet DO NOT EDIT.
|
||||
//
|
||||
// WARNING: Changes to this file may cause incorrect behavior
|
||||
// and will be lost if the code is regenerated
|
||||
//
|
||||
|
||||
package table
|
||||
|
||||
import (
|
||||
"github.com/go-jet/jet/v2/postgres"
|
||||
)
|
||||
|
||||
var SampleArrays = newSampleArraysTable("test_sample", "sample_arrays", "")
|
||||
|
||||
type sampleArraysTable struct {
|
||||
postgres.Table
|
||||
|
||||
// Columns
|
||||
ID postgres.ColumnInteger
|
||||
BoolArray postgres.ColumnBoolArray
|
||||
Int2ArrayPtr postgres.ColumnIntegerArray
|
||||
Int4Array postgres.ColumnIntegerArray
|
||||
Int8Array postgres.ColumnIntegerArray
|
||||
NumericArray postgres.ColumnFloatArray
|
||||
DecimalArray postgres.ColumnFloatArray
|
||||
RealArray postgres.ColumnFloatArray
|
||||
DoubleArray postgres.ColumnFloatArray
|
||||
TextArray postgres.ColumnStringArray
|
||||
VarcharArray postgres.ColumnStringArray
|
||||
CharArray postgres.ColumnStringArray
|
||||
ByteaArray postgres.ColumnByteaArray
|
||||
DateArray postgres.ColumnDateArray
|
||||
TimestampArray postgres.ColumnTimestampArray
|
||||
TimestamptzArray postgres.ColumnTimestampzArray
|
||||
TimeArray postgres.ColumnTimeArray
|
||||
TimetzArray postgres.ColumnTimezArray
|
||||
IntervalArray postgres.ColumnIntervalArray
|
||||
UUIDArray postgres.ColumnStringArray
|
||||
MoodEnumArray postgres.ColumnStringArray
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
DefaultColumns postgres.ColumnList
|
||||
}
|
||||
|
||||
type SampleArraysTable struct {
|
||||
sampleArraysTable
|
||||
|
||||
EXCLUDED sampleArraysTable
|
||||
}
|
||||
|
||||
// AS creates new SampleArraysTable with assigned alias
|
||||
func (a SampleArraysTable) AS(alias string) *SampleArraysTable {
|
||||
return newSampleArraysTable(a.SchemaName(), a.TableName(), alias)
|
||||
}
|
||||
|
||||
// Schema creates new SampleArraysTable with assigned schema name
|
||||
func (a SampleArraysTable) FromSchema(schemaName string) *SampleArraysTable {
|
||||
return newSampleArraysTable(schemaName, a.TableName(), a.Alias())
|
||||
}
|
||||
|
||||
// WithPrefix creates new SampleArraysTable with assigned table prefix
|
||||
func (a SampleArraysTable) WithPrefix(prefix string) *SampleArraysTable {
|
||||
return newSampleArraysTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||
}
|
||||
|
||||
// WithSuffix creates new SampleArraysTable with assigned table suffix
|
||||
func (a SampleArraysTable) WithSuffix(suffix string) *SampleArraysTable {
|
||||
return newSampleArraysTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||
}
|
||||
|
||||
func newSampleArraysTable(schemaName, tableName, alias string) *SampleArraysTable {
|
||||
return &SampleArraysTable{
|
||||
sampleArraysTable: newSampleArraysTableImpl(schemaName, tableName, alias),
|
||||
EXCLUDED: newSampleArraysTableImpl("", "excluded", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func newSampleArraysTableImpl(schemaName, tableName, alias string) sampleArraysTable {
|
||||
var (
|
||||
IDColumn = postgres.IntegerColumn("id")
|
||||
BoolArrayColumn = postgres.BoolArrayColumn("bool_array")
|
||||
Int2ArrayPtrColumn = postgres.IntegerArrayColumn("int2_array_ptr")
|
||||
Int4ArrayColumn = postgres.IntegerArrayColumn("int4_array")
|
||||
Int8ArrayColumn = postgres.IntegerArrayColumn("int8_array")
|
||||
NumericArrayColumn = postgres.FloatArrayColumn("numeric_array")
|
||||
DecimalArrayColumn = postgres.FloatArrayColumn("decimal_array")
|
||||
RealArrayColumn = postgres.FloatArrayColumn("real_array")
|
||||
DoubleArrayColumn = postgres.FloatArrayColumn("double_array")
|
||||
TextArrayColumn = postgres.StringArrayColumn("text_array")
|
||||
VarcharArrayColumn = postgres.StringArrayColumn("varchar_array")
|
||||
CharArrayColumn = postgres.StringArrayColumn("char_array")
|
||||
ByteaArrayColumn = postgres.ByteaArrayColumn("bytea_array")
|
||||
DateArrayColumn = postgres.DateArrayColumn("date_array")
|
||||
TimestampArrayColumn = postgres.TimestampArrayColumn("timestamp_array")
|
||||
TimestamptzArrayColumn = postgres.TimestampzArrayColumn("timestamptz_array")
|
||||
TimeArrayColumn = postgres.TimeArrayColumn("time_array")
|
||||
TimetzArrayColumn = postgres.TimezArrayColumn("timetz_array")
|
||||
IntervalArrayColumn = postgres.IntervalArrayColumn("interval_array")
|
||||
UUIDArrayColumn = postgres.StringArrayColumn("uuid_array")
|
||||
MoodEnumArrayColumn = postgres.StringArrayColumn("mood_enum_array")
|
||||
allColumns = postgres.ColumnList{IDColumn, BoolArrayColumn, Int2ArrayPtrColumn, Int4ArrayColumn, Int8ArrayColumn, NumericArrayColumn, DecimalArrayColumn, RealArrayColumn, DoubleArrayColumn, TextArrayColumn, VarcharArrayColumn, CharArrayColumn, ByteaArrayColumn, DateArrayColumn, TimestampArrayColumn, TimestamptzArrayColumn, TimeArrayColumn, TimetzArrayColumn, IntervalArrayColumn, UUIDArrayColumn, MoodEnumArrayColumn}
|
||||
mutableColumns = postgres.ColumnList{BoolArrayColumn, Int2ArrayPtrColumn, Int4ArrayColumn, Int8ArrayColumn, NumericArrayColumn, DecimalArrayColumn, RealArrayColumn, DoubleArrayColumn, TextArrayColumn, VarcharArrayColumn, CharArrayColumn, ByteaArrayColumn, DateArrayColumn, TimestampArrayColumn, TimestamptzArrayColumn, TimeArrayColumn, TimetzArrayColumn, IntervalArrayColumn, UUIDArrayColumn, MoodEnumArrayColumn}
|
||||
defaultColumns = postgres.ColumnList{IDColumn, Int4ArrayColumn, Int8ArrayColumn, TextArrayColumn}
|
||||
)
|
||||
|
||||
return sampleArraysTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
ID: IDColumn,
|
||||
BoolArray: BoolArrayColumn,
|
||||
Int2ArrayPtr: Int2ArrayPtrColumn,
|
||||
Int4Array: Int4ArrayColumn,
|
||||
Int8Array: Int8ArrayColumn,
|
||||
NumericArray: NumericArrayColumn,
|
||||
DecimalArray: DecimalArrayColumn,
|
||||
RealArray: RealArrayColumn,
|
||||
DoubleArray: DoubleArrayColumn,
|
||||
TextArray: TextArrayColumn,
|
||||
VarcharArray: VarcharArrayColumn,
|
||||
CharArray: CharArrayColumn,
|
||||
ByteaArray: ByteaArrayColumn,
|
||||
DateArray: DateArrayColumn,
|
||||
TimestampArray: TimestampArrayColumn,
|
||||
TimestamptzArray: TimestamptzArrayColumn,
|
||||
TimeArray: TimeArrayColumn,
|
||||
TimetzArray: TimetzArrayColumn,
|
||||
IntervalArray: IntervalArrayColumn,
|
||||
UUIDArray: UUIDArrayColumn,
|
||||
MoodEnumArray: MoodEnumArrayColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
DefaultColumns: defaultColumns,
|
||||
}
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package postgres
|
|||
import (
|
||||
"context"
|
||||
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
||||
"github.com/lib/pq"
|
||||
"github.com/volatiletech/null/v8"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -1020,7 +1021,7 @@ func TestScanIntoCustomBaseTypes(t *testing.T) {
|
|||
ReplacementCost MyFloat64
|
||||
Rating *model.MpaaRating
|
||||
LastUpdate MyTime
|
||||
SpecialFeatures *MyString
|
||||
SpecialFeatures pq.StringArray
|
||||
Fulltext MyString
|
||||
}
|
||||
|
||||
|
|
@ -1032,14 +1033,12 @@ func TestScanIntoCustomBaseTypes(t *testing.T) {
|
|||
Film.FilmID.ASC(),
|
||||
).LIMIT(3)
|
||||
|
||||
var films []model.Film
|
||||
|
||||
err := stmt.Query(db, &films)
|
||||
var myFilms []film
|
||||
err := stmt.Query(db, &myFilms)
|
||||
require.NoError(t, err)
|
||||
|
||||
var myFilms []film
|
||||
|
||||
err = stmt.Query(db, &myFilms)
|
||||
var films []model.Film
|
||||
err = stmt.Query(db, &films)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, testutils.ToJSON(films), testutils.ToJSON(myFilms))
|
||||
|
|
@ -1254,7 +1253,7 @@ var film1 = model.Film{
|
|||
ReplacementCost: 20.99,
|
||||
Rating: &pgRating,
|
||||
LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:50:58.951", 3),
|
||||
SpecialFeatures: ptr.Of("{\"Deleted Scenes\",\"Behind the Scenes\"}"),
|
||||
SpecialFeatures: &pq.StringArray{"Deleted Scenes", "Behind the Scenes"},
|
||||
Fulltext: "'academi':1 'battl':15 'canadian':20 'dinosaur':2 'drama':5 'epic':4 'feminist':8 'mad':11 'must':14 'rocki':21 'scientist':12 'teacher':17",
|
||||
}
|
||||
|
||||
|
|
@ -1270,7 +1269,7 @@ var film2 = model.Film{
|
|||
ReplacementCost: 12.99,
|
||||
Rating: &gRating,
|
||||
LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:50:58.951", 3),
|
||||
SpecialFeatures: ptr.Of(`{Trailers,"Deleted Scenes"}`),
|
||||
SpecialFeatures: &pq.StringArray{"Trailers", "Deleted Scenes"},
|
||||
Fulltext: `'ace':1 'administr':9 'ancient':19 'astound':4 'car':17 'china':20 'databas':8 'epistl':5 'explor':12 'find':15 'goldfing':2 'must':14`,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
||||
"github.com/lib/pq"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -1852,7 +1853,7 @@ ORDER BY film.film_id ASC;
|
|||
Rating: &gRating,
|
||||
RentalDuration: 3,
|
||||
LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:50:58.951", 3),
|
||||
SpecialFeatures: ptr.Of("{Trailers,\"Deleted Scenes\"}"),
|
||||
SpecialFeatures: &pq.StringArray{"Trailers", "Deleted Scenes"},
|
||||
Fulltext: "'ace':1 'administr':9 'ancient':19 'astound':4 'car':17 'china':20 'databas':8 'epistl':5 'explor':12 'find':15 'goldfing':2 'must':14",
|
||||
})
|
||||
}
|
||||
|
|
@ -2709,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",
|
||||
|
|
@ -2729,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"
|
||||
|
|
@ -2739,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
|
||||
|
|
@ -2783,7 +2829,6 @@ ORDER BY actor.actor_id ASC, film.film_id ASC;
|
|||
require.NoError(t, err)
|
||||
|
||||
//testutils.SaveJSONFile(dest, "./testdata/results/postgres/quick-start-dest.json")
|
||||
|
||||
testutils.AssertJSONFile(t, dest, "./testdata/results/postgres/quick-start-dest.json")
|
||||
|
||||
var dest2 []struct {
|
||||
|
|
@ -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.
|
||||
|
|
@ -3389,7 +3438,10 @@ func TestSelectRecursionScanNxM(t *testing.T) {
|
|||
"ReplacementCost": 20.99,
|
||||
"Rating": "PG",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"SpecialFeatures": "{\"Deleted Scenes\",\"Behind the Scenes\"}",
|
||||
"SpecialFeatures": [
|
||||
"Deleted Scenes",
|
||||
"Behind the Scenes"
|
||||
],
|
||||
"Fulltext": "'academi':1 'battl':15 'canadian':20 'dinosaur':2 'drama':5 'epic':4 'feminist':8 'mad':11 'must':14 'rocki':21 'scientist':12 'teacher':17",
|
||||
"Actors": [
|
||||
{
|
||||
|
|
@ -3413,7 +3465,10 @@ func TestSelectRecursionScanNxM(t *testing.T) {
|
|||
"ReplacementCost": 9.99,
|
||||
"Rating": "R",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"SpecialFeatures": "{Trailers,\"Deleted Scenes\"}",
|
||||
"SpecialFeatures": [
|
||||
"Trailers",
|
||||
"Deleted Scenes"
|
||||
],
|
||||
"Fulltext": "'anaconda':1 'australia':18 'confess':2 'dentist':8,11 'display':5 'fight':14 'girl':16 'lacklustur':4 'must':13",
|
||||
"Actors": [
|
||||
{
|
||||
|
|
@ -3461,7 +3516,10 @@ func TestSelectRecursionScanNxM(t *testing.T) {
|
|||
"ReplacementCost": 20.99,
|
||||
"Rating": "PG",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"SpecialFeatures": "{\"Deleted Scenes\",\"Behind the Scenes\"}",
|
||||
"SpecialFeatures": [
|
||||
"Deleted Scenes",
|
||||
"Behind the Scenes"
|
||||
],
|
||||
"Fulltext": "'academi':1 'battl':15 'canadian':20 'dinosaur':2 'drama':5 'epic':4 'feminist':8 'mad':11 'must':14 'rocki':21 'scientist':12 'teacher':17",
|
||||
"Actors": null
|
||||
},
|
||||
|
|
@ -3477,7 +3535,10 @@ func TestSelectRecursionScanNxM(t *testing.T) {
|
|||
"ReplacementCost": 9.99,
|
||||
"Rating": "R",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"SpecialFeatures": "{Trailers,\"Deleted Scenes\"}",
|
||||
"SpecialFeatures": [
|
||||
"Trailers",
|
||||
"Deleted Scenes"
|
||||
],
|
||||
"Fulltext": "'anaconda':1 'australia':18 'confess':2 'dentist':8,11 'display':5 'fight':14 'girl':16 'lacklustur':4 'must':13",
|
||||
"Actors": null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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