Add support for additional array types.
This commit is contained in:
parent
45d4ced9b0
commit
4ee047a675
47 changed files with 1994 additions and 4277 deletions
390
README.md
390
README.md
|
|
@ -198,10 +198,13 @@ stmt := SELECT(
|
||||||
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
|
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
|
||||||
INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
|
INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
|
||||||
).WHERE(
|
).WHERE(
|
||||||
Language.Name.EQ(Char(20)("English")).
|
AND(
|
||||||
AND(Category.Name.NOT_EQ(Text("Action"))).
|
Language.Name.EQ(Char(20)("English")), // string columns Language.Name and Category.Name can be compared only with string expression
|
||||||
AND(Film.Length.GT(Int32(180))).
|
Category.Name.NOT_EQ(Text("Action")),
|
||||||
AND(Film.Rating.NOT_EQ(enum.MpaaRating.R)),
|
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(
|
).ORDER_BY(
|
||||||
Actor.ActorID.ASC(),
|
Actor.ActorID.ASC(),
|
||||||
Film.FilmID.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
|
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
|
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?__
|
__How to Get a Parametrized SQL Query from the Statement?__
|
||||||
```go
|
```go
|
||||||
|
|
@ -244,7 +248,6 @@ SELECT actor.actor_id AS "actor.actor_id",
|
||||||
film.fulltext AS "film.fulltext",
|
film.fulltext AS "film.fulltext",
|
||||||
language.language_id AS "language.language_id",
|
language.language_id AS "language.language_id",
|
||||||
language.name AS "language.name",
|
language.name AS "language.name",
|
||||||
language.last_update AS "language.last_update",
|
|
||||||
category.category_id AS "category.category_id",
|
category.category_id AS "category.category_id",
|
||||||
category.name AS "category.name",
|
category.name AS "category.name",
|
||||||
category.last_update AS "category.last_update"
|
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.language ON (language.language_id = film.language_id)
|
||||||
INNER JOIN dvds.film_category ON (film_category.film_id = film.film_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)
|
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;
|
ORDER BY actor.actor_id ASC, film.film_id ASC;
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```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
|
```sql
|
||||||
SELECT actor.actor_id AS "actor.actor_id",
|
SELECT actor.actor_id AS "actor.actor_id",
|
||||||
actor.first_name AS "actor.first_name",
|
actor.first_name AS "actor.first_name",
|
||||||
actor.last_name AS "actor.last_name",
|
actor.last_name AS "actor.last_name",
|
||||||
actor.last_update AS "actor.last_update",
|
actor.last_update AS "actor.last_update",
|
||||||
film.film_id AS "film.film_id",
|
film.film_id AS "film.film_id",
|
||||||
film.title AS "film.title",
|
film.title AS "film.title",
|
||||||
film.description AS "film.description",
|
film.description AS "film.description",
|
||||||
film.release_year AS "film.release_year",
|
film.release_year AS "film.release_year",
|
||||||
film.language_id AS "film.language_id",
|
film.language_id AS "film.language_id",
|
||||||
film.rental_duration AS "film.rental_duration",
|
film.rental_duration AS "film.rental_duration",
|
||||||
film.rental_rate AS "film.rental_rate",
|
film.rental_rate AS "film.rental_rate",
|
||||||
film.length AS "film.length",
|
film.length AS "film.length",
|
||||||
film.replacement_cost AS "film.replacement_cost",
|
film.replacement_cost AS "film.replacement_cost",
|
||||||
film.rating AS "film.rating",
|
film.rating AS "film.rating",
|
||||||
film.last_update AS "film.last_update",
|
film.last_update AS "film.last_update",
|
||||||
film.special_features AS "film.special_features",
|
film.special_features AS "film.special_features",
|
||||||
film.fulltext AS "film.fulltext",
|
film.fulltext AS "film.fulltext",
|
||||||
language.language_id AS "language.language_id",
|
language.language_id AS "language.language_id",
|
||||||
language.name AS "language.name",
|
language.name AS "language.name",
|
||||||
language.last_update AS "language.last_update",
|
category.category_id AS "category.category_id",
|
||||||
category.category_id AS "category.category_id",
|
category.name AS "category.name",
|
||||||
category.name AS "category.name",
|
category.last_update AS "category.last_update"
|
||||||
category.last_update AS "category.last_update"
|
|
||||||
FROM dvds.actor
|
FROM dvds.actor
|
||||||
INNER JOIN dvds.film_actor ON (actor.actor_id = film_actor.actor_id)
|
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.film ON (film.film_id = film_actor.film_id)
|
||||||
INNER JOIN dvds.language ON (language.language_id = film.language_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.film_category ON (film_category.film_id = film.film_id)
|
||||||
INNER JOIN dvds.category ON (category.category_id = film_category.category_id)
|
INNER JOIN dvds.category ON (category.category_id = film_category.category_id)
|
||||||
WHERE (((language.name = 'English'::char(20)) AND (category.name != 'Action'::text)) AND (film.length > 180)) AND (film.rating != 'R')
|
WHERE (
|
||||||
|
(language.name = 'English'::char(20))
|
||||||
|
AND (category.name != 'Action'::text)
|
||||||
|
AND (film.length > 180::integer)
|
||||||
|
AND (film.rating != 'R')
|
||||||
|
AND ('Trailers'::text = ANY(film.special_features))
|
||||||
|
)
|
||||||
ORDER BY actor.actor_id ASC, film.film_id ASC;
|
ORDER BY actor.actor_id ASC, film.film_id ASC;
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
@ -355,8 +370,8 @@ __And that's it.__
|
||||||
The `dest` variable now contains a list of all actors (each with a list of
|
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
|
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
|
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
|
films longer than 180 minutes, where the film language is 'English',
|
||||||
the film category is not 'Action'.
|
the film category is not 'Action' and 'Trailers' are one of the film's special features.
|
||||||
|
|
||||||
> [!Tip]
|
> [!Tip]
|
||||||
> It is recommended to enable **Strict Scan** on application startup, especially when destination contains
|
> It is recommended to enable **Strict Scan** on application startup, especially when destination contains
|
||||||
|
|
@ -370,82 +385,88 @@ fmt.Println(string(jsonText))
|
||||||
|
|
||||||
```js
|
```js
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"ActorID": 1,
|
"ActorID": 1,
|
||||||
"FirstName": "Penelope",
|
"FirstName": "Penelope",
|
||||||
"LastName": "Guiness",
|
"LastName": "Guiness",
|
||||||
"LastUpdate": "2013-05-26T14:47:57.62Z",
|
"LastUpdate": "2013-05-26T14:47:57.62Z",
|
||||||
"Films": [
|
"Films": [
|
||||||
{
|
{
|
||||||
"FilmID": 499,
|
"FilmID": 499,
|
||||||
"Title": "King Evolution",
|
"Title": "King Evolution",
|
||||||
"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
|
"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
|
||||||
"ReleaseYear": 2006,
|
"ReleaseYear": 2006,
|
||||||
"LanguageID": 1,
|
"LanguageID": 1,
|
||||||
"RentalDuration": 3,
|
"RentalDuration": 3,
|
||||||
"RentalRate": 4.99,
|
"RentalRate": 4.99,
|
||||||
"Length": 184,
|
"Length": 184,
|
||||||
"ReplacementCost": 24.99,
|
"ReplacementCost": 24.99,
|
||||||
"Rating": "NC-17",
|
"Rating": "NC-17",
|
||||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||||
"SpecialFeatures": "{Trailers,\"Deleted Scenes\",\"Behind the Scenes\"}",
|
"SpecialFeatures": [
|
||||||
"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",
|
"Trailers",
|
||||||
"Language": {
|
"Deleted Scenes",
|
||||||
"LanguageID": 1,
|
"Behind the Scenes"
|
||||||
"Name": "English ",
|
],
|
||||||
"LastUpdate": "0001-01-01T00:00:00Z"
|
"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": {
|
||||||
"Categories": [
|
"LanguageID": 1,
|
||||||
{
|
"Name": "English ",
|
||||||
"CategoryID": 8,
|
"LastUpdate": "0001-01-01T00:00:00Z"
|
||||||
"Name": "Family",
|
},
|
||||||
"LastUpdate": "2006-02-15T09:46:27Z"
|
"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": [
|
"ActorID": 3,
|
||||||
{
|
"FirstName": "Ed",
|
||||||
"FilmID": 996,
|
"LastName": "Chase",
|
||||||
"Title": "Young Language",
|
"LastUpdate": "2013-05-26T14:47:57.62Z",
|
||||||
"Description": "A Unbelieveable Yarn of a Boat And a Database Administrator who must Meet a Boy in The First Manned Space Station",
|
"Films": [
|
||||||
"ReleaseYear": 2006,
|
{
|
||||||
"LanguageID": 1,
|
"FilmID": 996,
|
||||||
"RentalDuration": 6,
|
"Title": "Young Language",
|
||||||
"RentalRate": 0.99,
|
"Description": "A Unbelieveable Yarn of a Boat And a Database Administrator who must Meet a Boy in The First Manned Space Station",
|
||||||
"Length": 183,
|
"ReleaseYear": 2006,
|
||||||
"ReplacementCost": 9.99,
|
"LanguageID": 1,
|
||||||
"Rating": "G",
|
"RentalDuration": 6,
|
||||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
"RentalRate": 0.99,
|
||||||
"SpecialFeatures": "{Trailers,\"Behind the Scenes\"}",
|
"Length": 183,
|
||||||
"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",
|
"ReplacementCost": 9.99,
|
||||||
"Language": {
|
"Rating": "G",
|
||||||
"LanguageID": 1,
|
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||||
"Name": "English ",
|
"SpecialFeatures": [
|
||||||
"LastUpdate": "0001-01-01T00:00:00Z"
|
"Trailers",
|
||||||
},
|
"Behind the Scenes"
|
||||||
"Categories": [
|
],
|
||||||
{
|
"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",
|
||||||
"CategoryID": 6,
|
"Language": {
|
||||||
"Name": "Documentary",
|
"LanguageID": 1,
|
||||||
"LastUpdate": "2006-02-15T09:46:27Z"
|
"Name": "English ",
|
||||||
}
|
"LastUpdate": "0001-01-01T00:00:00Z"
|
||||||
]
|
},
|
||||||
}
|
"Categories": [
|
||||||
]
|
{
|
||||||
},
|
"CategoryID": 6,
|
||||||
//...(125 more items)
|
"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'
|
What if, we also want to have a list of films per category and actors per category, with the same search conditions.
|
||||||
and film category is not 'Action'.
|
|
||||||
In that case we can reuse above statement `stmt`, and just change our destination:
|
In that case we can reuse above statement `stmt`, and just change our destination:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
@ -464,88 +485,71 @@ handleError(err)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"CategoryID": 8,
|
"CategoryID": 8,
|
||||||
"Name": "Family",
|
"Name": "Family",
|
||||||
"LastUpdate": "2006-02-15T09:46:27Z",
|
"LastUpdate": "2006-02-15T09:46:27Z",
|
||||||
"Films": [
|
"Films": [
|
||||||
{
|
{
|
||||||
"FilmID": 499,
|
"FilmID": 499,
|
||||||
"Title": "King Evolution",
|
"Title": "King Evolution",
|
||||||
"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
|
"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
|
||||||
"ReleaseYear": 2006,
|
"ReleaseYear": 2006,
|
||||||
"LanguageID": 1,
|
"LanguageID": 1,
|
||||||
"RentalDuration": 3,
|
"RentalDuration": 3,
|
||||||
"RentalRate": 4.99,
|
"RentalRate": 4.99,
|
||||||
"Length": 184,
|
"Length": 184,
|
||||||
"ReplacementCost": 24.99,
|
"ReplacementCost": 24.99,
|
||||||
"Rating": "NC-17",
|
"Rating": "NC-17",
|
||||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||||
"SpecialFeatures": "{Trailers,\"Deleted Scenes\",\"Behind the Scenes\"}",
|
"SpecialFeatures": [
|
||||||
"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"
|
"Trailers",
|
||||||
},
|
"Deleted Scenes",
|
||||||
{
|
"Behind the Scenes"
|
||||||
"FilmID": 50,
|
],
|
||||||
"Title": "Baked Cleopatra",
|
"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"
|
||||||
"Description": "A Stunning Drama of a Forensic Psychologist And a Husband who must Overcome a Waitress in A Monastery",
|
}
|
||||||
"ReleaseYear": 2006,
|
],
|
||||||
"LanguageID": 1,
|
"Actors": [
|
||||||
"RentalDuration": 3,
|
{
|
||||||
"RentalRate": 2.99,
|
"ActorID": 1,
|
||||||
"Length": 182,
|
"FirstName": "Penelope",
|
||||||
"ReplacementCost": 20.99,
|
"LastName": "Guiness",
|
||||||
"Rating": "G",
|
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||||
"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"
|
"ActorID": 20,
|
||||||
}
|
"FirstName": "Lucille",
|
||||||
],
|
"LastName": "Tracy",
|
||||||
"Actors": [
|
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||||
{
|
},
|
||||||
"ActorID": 1,
|
{
|
||||||
"FirstName": "Penelope",
|
"ActorID": 36,
|
||||||
"LastName": "Guiness",
|
"FirstName": "Burt",
|
||||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
"LastName": "Dukakis",
|
||||||
},
|
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||||
{
|
},
|
||||||
"ActorID": 20,
|
{
|
||||||
"FirstName": "Lucille",
|
"ActorID": 118,
|
||||||
"LastName": "Tracy",
|
"FirstName": "Cuba",
|
||||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
"LastName": "Allen",
|
||||||
},
|
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||||
{
|
},
|
||||||
"ActorID": 36,
|
{
|
||||||
"FirstName": "Burt",
|
"ActorID": 187,
|
||||||
"LastName": "Dukakis",
|
"FirstName": "Renee",
|
||||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
"LastName": "Ball",
|
||||||
},
|
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||||
{
|
},
|
||||||
"ActorID": 70,
|
{
|
||||||
"FirstName": "Michelle",
|
"ActorID": 198,
|
||||||
"LastName": "Mcconaughey",
|
"FirstName": "Mary",
|
||||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
"LastName": "Keitel",
|
||||||
},
|
"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>
|
</details>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/lib/pq"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -23,6 +24,6 @@ type Film struct {
|
||||||
ReplacementCost float64
|
ReplacementCost float64
|
||||||
Rating *MpaaRating
|
Rating *MpaaRating
|
||||||
LastUpdate time.Time
|
LastUpdate time.Time
|
||||||
SpecialFeatures *string
|
SpecialFeatures *pq.StringArray
|
||||||
Fulltext string
|
Fulltext string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,14 @@ const (
|
||||||
MpaaRating_Nc17 MpaaRating = "NC-17"
|
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 {
|
func (e *MpaaRating) Scan(value interface{}) error {
|
||||||
var enumValue string
|
var enumValue string
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ var Actor = newActorTable("dvds", "actor", "")
|
||||||
type actorTable struct {
|
type actorTable struct {
|
||||||
postgres.Table
|
postgres.Table
|
||||||
|
|
||||||
//Columns
|
// Columns
|
||||||
ActorID postgres.ColumnInteger
|
ActorID postgres.ColumnInteger
|
||||||
FirstName postgres.ColumnString
|
FirstName postgres.ColumnString
|
||||||
LastName postgres.ColumnString
|
LastName postgres.ColumnString
|
||||||
|
|
@ -24,6 +24,7 @@ type actorTable struct {
|
||||||
|
|
||||||
AllColumns postgres.ColumnList
|
AllColumns postgres.ColumnList
|
||||||
MutableColumns postgres.ColumnList
|
MutableColumns postgres.ColumnList
|
||||||
|
DefaultColumns postgres.ColumnList
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActorTable struct {
|
type ActorTable struct {
|
||||||
|
|
@ -67,6 +68,7 @@ func newActorTableImpl(schemaName, tableName, alias string) actorTable {
|
||||||
LastUpdateColumn = postgres.TimestampColumn("last_update")
|
LastUpdateColumn = postgres.TimestampColumn("last_update")
|
||||||
allColumns = postgres.ColumnList{ActorIDColumn, FirstNameColumn, LastNameColumn, LastUpdateColumn}
|
allColumns = postgres.ColumnList{ActorIDColumn, FirstNameColumn, LastNameColumn, LastUpdateColumn}
|
||||||
mutableColumns = postgres.ColumnList{FirstNameColumn, LastNameColumn, LastUpdateColumn}
|
mutableColumns = postgres.ColumnList{FirstNameColumn, LastNameColumn, LastUpdateColumn}
|
||||||
|
defaultColumns = postgres.ColumnList{ActorIDColumn}
|
||||||
)
|
)
|
||||||
|
|
||||||
return actorTable{
|
return actorTable{
|
||||||
|
|
@ -80,5 +82,6 @@ func newActorTableImpl(schemaName, tableName, alias string) actorTable {
|
||||||
|
|
||||||
AllColumns: allColumns,
|
AllColumns: allColumns,
|
||||||
MutableColumns: mutableColumns,
|
MutableColumns: mutableColumns,
|
||||||
|
DefaultColumns: defaultColumns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,14 @@ var Category = newCategoryTable("dvds", "category", "")
|
||||||
type categoryTable struct {
|
type categoryTable struct {
|
||||||
postgres.Table
|
postgres.Table
|
||||||
|
|
||||||
//Columns
|
// Columns
|
||||||
CategoryID postgres.ColumnInteger
|
CategoryID postgres.ColumnInteger
|
||||||
Name postgres.ColumnString
|
Name postgres.ColumnString
|
||||||
LastUpdate postgres.ColumnTimestamp
|
LastUpdate postgres.ColumnTimestamp
|
||||||
|
|
||||||
AllColumns postgres.ColumnList
|
AllColumns postgres.ColumnList
|
||||||
MutableColumns postgres.ColumnList
|
MutableColumns postgres.ColumnList
|
||||||
|
DefaultColumns postgres.ColumnList
|
||||||
}
|
}
|
||||||
|
|
||||||
type CategoryTable struct {
|
type CategoryTable struct {
|
||||||
|
|
@ -65,6 +66,7 @@ func newCategoryTableImpl(schemaName, tableName, alias string) categoryTable {
|
||||||
LastUpdateColumn = postgres.TimestampColumn("last_update")
|
LastUpdateColumn = postgres.TimestampColumn("last_update")
|
||||||
allColumns = postgres.ColumnList{CategoryIDColumn, NameColumn, LastUpdateColumn}
|
allColumns = postgres.ColumnList{CategoryIDColumn, NameColumn, LastUpdateColumn}
|
||||||
mutableColumns = postgres.ColumnList{NameColumn, LastUpdateColumn}
|
mutableColumns = postgres.ColumnList{NameColumn, LastUpdateColumn}
|
||||||
|
defaultColumns = postgres.ColumnList{CategoryIDColumn, LastUpdateColumn}
|
||||||
)
|
)
|
||||||
|
|
||||||
return categoryTable{
|
return categoryTable{
|
||||||
|
|
@ -77,5 +79,6 @@ func newCategoryTableImpl(schemaName, tableName, alias string) categoryTable {
|
||||||
|
|
||||||
AllColumns: allColumns,
|
AllColumns: allColumns,
|
||||||
MutableColumns: mutableColumns,
|
MutableColumns: mutableColumns,
|
||||||
|
DefaultColumns: defaultColumns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ var Film = newFilmTable("dvds", "film", "")
|
||||||
type filmTable struct {
|
type filmTable struct {
|
||||||
postgres.Table
|
postgres.Table
|
||||||
|
|
||||||
//Columns
|
// Columns
|
||||||
FilmID postgres.ColumnInteger
|
FilmID postgres.ColumnInteger
|
||||||
Title postgres.ColumnString
|
Title postgres.ColumnString
|
||||||
Description postgres.ColumnString
|
Description postgres.ColumnString
|
||||||
|
|
@ -28,11 +28,12 @@ type filmTable struct {
|
||||||
ReplacementCost postgres.ColumnFloat
|
ReplacementCost postgres.ColumnFloat
|
||||||
Rating postgres.ColumnString
|
Rating postgres.ColumnString
|
||||||
LastUpdate postgres.ColumnTimestamp
|
LastUpdate postgres.ColumnTimestamp
|
||||||
SpecialFeatures postgres.ColumnString
|
SpecialFeatures postgres.ColumnStringArray
|
||||||
Fulltext postgres.ColumnString
|
Fulltext postgres.ColumnString
|
||||||
|
|
||||||
AllColumns postgres.ColumnList
|
AllColumns postgres.ColumnList
|
||||||
MutableColumns postgres.ColumnList
|
MutableColumns postgres.ColumnList
|
||||||
|
DefaultColumns postgres.ColumnList
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilmTable struct {
|
type FilmTable struct {
|
||||||
|
|
@ -81,10 +82,11 @@ func newFilmTableImpl(schemaName, tableName, alias string) filmTable {
|
||||||
ReplacementCostColumn = postgres.FloatColumn("replacement_cost")
|
ReplacementCostColumn = postgres.FloatColumn("replacement_cost")
|
||||||
RatingColumn = postgres.StringColumn("rating")
|
RatingColumn = postgres.StringColumn("rating")
|
||||||
LastUpdateColumn = postgres.TimestampColumn("last_update")
|
LastUpdateColumn = postgres.TimestampColumn("last_update")
|
||||||
SpecialFeaturesColumn = postgres.StringColumn("special_features")
|
SpecialFeaturesColumn = postgres.StringArrayColumn("special_features")
|
||||||
FulltextColumn = postgres.StringColumn("fulltext")
|
FulltextColumn = postgres.StringColumn("fulltext")
|
||||||
allColumns = postgres.ColumnList{FilmIDColumn, TitleColumn, DescriptionColumn, ReleaseYearColumn, LanguageIDColumn, RentalDurationColumn, RentalRateColumn, LengthColumn, ReplacementCostColumn, RatingColumn, LastUpdateColumn, SpecialFeaturesColumn, FulltextColumn}
|
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}
|
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{
|
return filmTable{
|
||||||
|
|
@ -107,5 +109,6 @@ func newFilmTableImpl(schemaName, tableName, alias string) filmTable {
|
||||||
|
|
||||||
AllColumns: allColumns,
|
AllColumns: allColumns,
|
||||||
MutableColumns: mutableColumns,
|
MutableColumns: mutableColumns,
|
||||||
|
DefaultColumns: defaultColumns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,14 @@ var FilmActor = newFilmActorTable("dvds", "film_actor", "")
|
||||||
type filmActorTable struct {
|
type filmActorTable struct {
|
||||||
postgres.Table
|
postgres.Table
|
||||||
|
|
||||||
//Columns
|
// Columns
|
||||||
ActorID postgres.ColumnInteger
|
ActorID postgres.ColumnInteger
|
||||||
FilmID postgres.ColumnInteger
|
FilmID postgres.ColumnInteger
|
||||||
LastUpdate postgres.ColumnTimestamp
|
LastUpdate postgres.ColumnTimestamp
|
||||||
|
|
||||||
AllColumns postgres.ColumnList
|
AllColumns postgres.ColumnList
|
||||||
MutableColumns postgres.ColumnList
|
MutableColumns postgres.ColumnList
|
||||||
|
DefaultColumns postgres.ColumnList
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilmActorTable struct {
|
type FilmActorTable struct {
|
||||||
|
|
@ -65,6 +66,7 @@ func newFilmActorTableImpl(schemaName, tableName, alias string) filmActorTable {
|
||||||
LastUpdateColumn = postgres.TimestampColumn("last_update")
|
LastUpdateColumn = postgres.TimestampColumn("last_update")
|
||||||
allColumns = postgres.ColumnList{ActorIDColumn, FilmIDColumn, LastUpdateColumn}
|
allColumns = postgres.ColumnList{ActorIDColumn, FilmIDColumn, LastUpdateColumn}
|
||||||
mutableColumns = postgres.ColumnList{LastUpdateColumn}
|
mutableColumns = postgres.ColumnList{LastUpdateColumn}
|
||||||
|
defaultColumns = postgres.ColumnList{LastUpdateColumn}
|
||||||
)
|
)
|
||||||
|
|
||||||
return filmActorTable{
|
return filmActorTable{
|
||||||
|
|
@ -77,5 +79,6 @@ func newFilmActorTableImpl(schemaName, tableName, alias string) filmActorTable {
|
||||||
|
|
||||||
AllColumns: allColumns,
|
AllColumns: allColumns,
|
||||||
MutableColumns: mutableColumns,
|
MutableColumns: mutableColumns,
|
||||||
|
DefaultColumns: defaultColumns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,14 @@ var FilmCategory = newFilmCategoryTable("dvds", "film_category", "")
|
||||||
type filmCategoryTable struct {
|
type filmCategoryTable struct {
|
||||||
postgres.Table
|
postgres.Table
|
||||||
|
|
||||||
//Columns
|
// Columns
|
||||||
FilmID postgres.ColumnInteger
|
FilmID postgres.ColumnInteger
|
||||||
CategoryID postgres.ColumnInteger
|
CategoryID postgres.ColumnInteger
|
||||||
LastUpdate postgres.ColumnTimestamp
|
LastUpdate postgres.ColumnTimestamp
|
||||||
|
|
||||||
AllColumns postgres.ColumnList
|
AllColumns postgres.ColumnList
|
||||||
MutableColumns postgres.ColumnList
|
MutableColumns postgres.ColumnList
|
||||||
|
DefaultColumns postgres.ColumnList
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilmCategoryTable struct {
|
type FilmCategoryTable struct {
|
||||||
|
|
@ -65,6 +66,7 @@ func newFilmCategoryTableImpl(schemaName, tableName, alias string) filmCategoryT
|
||||||
LastUpdateColumn = postgres.TimestampColumn("last_update")
|
LastUpdateColumn = postgres.TimestampColumn("last_update")
|
||||||
allColumns = postgres.ColumnList{FilmIDColumn, CategoryIDColumn, LastUpdateColumn}
|
allColumns = postgres.ColumnList{FilmIDColumn, CategoryIDColumn, LastUpdateColumn}
|
||||||
mutableColumns = postgres.ColumnList{LastUpdateColumn}
|
mutableColumns = postgres.ColumnList{LastUpdateColumn}
|
||||||
|
defaultColumns = postgres.ColumnList{LastUpdateColumn}
|
||||||
)
|
)
|
||||||
|
|
||||||
return filmCategoryTable{
|
return filmCategoryTable{
|
||||||
|
|
@ -77,5 +79,6 @@ func newFilmCategoryTableImpl(schemaName, tableName, alias string) filmCategoryT
|
||||||
|
|
||||||
AllColumns: allColumns,
|
AllColumns: allColumns,
|
||||||
MutableColumns: mutableColumns,
|
MutableColumns: mutableColumns,
|
||||||
|
DefaultColumns: defaultColumns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,14 @@ var Language = newLanguageTable("dvds", "language", "")
|
||||||
type languageTable struct {
|
type languageTable struct {
|
||||||
postgres.Table
|
postgres.Table
|
||||||
|
|
||||||
//Columns
|
// Columns
|
||||||
LanguageID postgres.ColumnInteger
|
LanguageID postgres.ColumnInteger
|
||||||
Name postgres.ColumnString
|
Name postgres.ColumnString
|
||||||
LastUpdate postgres.ColumnTimestamp
|
LastUpdate postgres.ColumnTimestamp
|
||||||
|
|
||||||
AllColumns postgres.ColumnList
|
AllColumns postgres.ColumnList
|
||||||
MutableColumns postgres.ColumnList
|
MutableColumns postgres.ColumnList
|
||||||
|
DefaultColumns postgres.ColumnList
|
||||||
}
|
}
|
||||||
|
|
||||||
type LanguageTable struct {
|
type LanguageTable struct {
|
||||||
|
|
@ -65,6 +66,7 @@ func newLanguageTableImpl(schemaName, tableName, alias string) languageTable {
|
||||||
LastUpdateColumn = postgres.TimestampColumn("last_update")
|
LastUpdateColumn = postgres.TimestampColumn("last_update")
|
||||||
allColumns = postgres.ColumnList{LanguageIDColumn, NameColumn, LastUpdateColumn}
|
allColumns = postgres.ColumnList{LanguageIDColumn, NameColumn, LastUpdateColumn}
|
||||||
mutableColumns = postgres.ColumnList{NameColumn, LastUpdateColumn}
|
mutableColumns = postgres.ColumnList{NameColumn, LastUpdateColumn}
|
||||||
|
defaultColumns = postgres.ColumnList{LanguageIDColumn, LastUpdateColumn}
|
||||||
)
|
)
|
||||||
|
|
||||||
return languageTable{
|
return languageTable{
|
||||||
|
|
@ -77,5 +79,6 @@ func newLanguageTableImpl(schemaName, tableName, alias string) languageTable {
|
||||||
|
|
||||||
AllColumns: allColumns,
|
AllColumns: allColumns,
|
||||||
MutableColumns: mutableColumns,
|
MutableColumns: mutableColumns,
|
||||||
|
DefaultColumns: defaultColumns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ var ActorInfo = newActorInfoTable("dvds", "actor_info", "")
|
||||||
type actorInfoTable struct {
|
type actorInfoTable struct {
|
||||||
postgres.Table
|
postgres.Table
|
||||||
|
|
||||||
//Columns
|
// Columns
|
||||||
ActorID postgres.ColumnInteger
|
ActorID postgres.ColumnInteger
|
||||||
FirstName postgres.ColumnString
|
FirstName postgres.ColumnString
|
||||||
LastName postgres.ColumnString
|
LastName postgres.ColumnString
|
||||||
|
|
@ -24,6 +24,7 @@ type actorInfoTable struct {
|
||||||
|
|
||||||
AllColumns postgres.ColumnList
|
AllColumns postgres.ColumnList
|
||||||
MutableColumns postgres.ColumnList
|
MutableColumns postgres.ColumnList
|
||||||
|
DefaultColumns postgres.ColumnList
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActorInfoTable struct {
|
type ActorInfoTable struct {
|
||||||
|
|
@ -67,6 +68,7 @@ func newActorInfoTableImpl(schemaName, tableName, alias string) actorInfoTable {
|
||||||
FilmInfoColumn = postgres.StringColumn("film_info")
|
FilmInfoColumn = postgres.StringColumn("film_info")
|
||||||
allColumns = postgres.ColumnList{ActorIDColumn, FirstNameColumn, LastNameColumn, FilmInfoColumn}
|
allColumns = postgres.ColumnList{ActorIDColumn, FirstNameColumn, LastNameColumn, FilmInfoColumn}
|
||||||
mutableColumns = postgres.ColumnList{ActorIDColumn, FirstNameColumn, LastNameColumn, FilmInfoColumn}
|
mutableColumns = postgres.ColumnList{ActorIDColumn, FirstNameColumn, LastNameColumn, FilmInfoColumn}
|
||||||
|
defaultColumns = postgres.ColumnList{}
|
||||||
)
|
)
|
||||||
|
|
||||||
return actorInfoTable{
|
return actorInfoTable{
|
||||||
|
|
@ -80,5 +82,6 @@ func newActorInfoTableImpl(schemaName, tableName, alias string) actorInfoTable {
|
||||||
|
|
||||||
AllColumns: allColumns,
|
AllColumns: allColumns,
|
||||||
MutableColumns: mutableColumns,
|
MutableColumns: mutableColumns,
|
||||||
|
DefaultColumns: defaultColumns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ var CustomerList = newCustomerListTable("dvds", "customer_list", "")
|
||||||
type customerListTable struct {
|
type customerListTable struct {
|
||||||
postgres.Table
|
postgres.Table
|
||||||
|
|
||||||
//Columns
|
// Columns
|
||||||
ID postgres.ColumnInteger
|
ID postgres.ColumnInteger
|
||||||
Name postgres.ColumnString
|
Name postgres.ColumnString
|
||||||
Address postgres.ColumnString
|
Address postgres.ColumnString
|
||||||
|
|
@ -29,6 +29,7 @@ type customerListTable struct {
|
||||||
|
|
||||||
AllColumns postgres.ColumnList
|
AllColumns postgres.ColumnList
|
||||||
MutableColumns postgres.ColumnList
|
MutableColumns postgres.ColumnList
|
||||||
|
DefaultColumns postgres.ColumnList
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomerListTable struct {
|
type CustomerListTable struct {
|
||||||
|
|
@ -77,6 +78,7 @@ func newCustomerListTableImpl(schemaName, tableName, alias string) customerListT
|
||||||
SidColumn = postgres.IntegerColumn("sid")
|
SidColumn = postgres.IntegerColumn("sid")
|
||||||
allColumns = postgres.ColumnList{IDColumn, NameColumn, AddressColumn, ZipCodeColumn, PhoneColumn, CityColumn, CountryColumn, NotesColumn, SidColumn}
|
allColumns = postgres.ColumnList{IDColumn, NameColumn, AddressColumn, ZipCodeColumn, PhoneColumn, CityColumn, CountryColumn, NotesColumn, SidColumn}
|
||||||
mutableColumns = 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{
|
return customerListTable{
|
||||||
|
|
@ -95,5 +97,6 @@ func newCustomerListTableImpl(schemaName, tableName, alias string) customerListT
|
||||||
|
|
||||||
AllColumns: allColumns,
|
AllColumns: allColumns,
|
||||||
MutableColumns: mutableColumns,
|
MutableColumns: mutableColumns,
|
||||||
|
DefaultColumns: defaultColumns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
|
|
||||||
# Quick start example
|
# Quick start example
|
||||||
|
|
||||||
This package contains sample usage for Jet framework.
|
This package contains sample usage for Jet framework.
|
||||||
|
|
||||||
Jet generated files of interest are in `./gen` folder.
|
Jet generated files of interest are in `./gen` folder.
|
||||||
|
|
||||||
`quick-start.go` - contains code explained at main [README.md](../../README.md#quick-start),
|
`quick-start.go` - contains code explained at main [README.md](../../README.md#quick-start),
|
||||||
with a difference of redirecting json output to files(`dest.json` and `dest2.json`) rather then to a
|
with a difference of redirecting json output to files(`dest.json` and `dest2.json`) rather then to a
|
||||||
standard output.
|
standard output.
|
||||||
|
|
||||||
`./gen`, `dest.json` and `dest2.json` - added into git for presentation purposes.
|
`./gen`, `dest.json` and `dest2.json` - added into git for presentation purposes.
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -35,9 +35,9 @@ func main() {
|
||||||
|
|
||||||
// Write query
|
// Write query
|
||||||
stmt := SELECT(
|
stmt := SELECT(
|
||||||
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate,
|
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate, // or just Actor.AllColumns
|
||||||
Film.AllColumns,
|
Film.AllColumns,
|
||||||
Language.AllColumns.Except(Language.LastUpdate),
|
Language.AllColumns.Except(Language.LastUpdate), // all language columns except last_update
|
||||||
Category.AllColumns,
|
Category.AllColumns,
|
||||||
).FROM(
|
).FROM(
|
||||||
Actor.
|
Actor.
|
||||||
|
|
@ -47,10 +47,13 @@ func main() {
|
||||||
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
|
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
|
||||||
INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
|
INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
|
||||||
).WHERE(
|
).WHERE(
|
||||||
Language.Name.EQ(Char(20)("English")).
|
AND(
|
||||||
AND(Category.Name.NOT_EQ(Text("Action"))).
|
Language.Name.EQ(Char(20)("English")), // string columns Language.Name and Category.Name can be compared only with string expression
|
||||||
AND(Film.Length.GT(Int(180))).
|
Category.Name.NOT_EQ(Text("Action")),
|
||||||
AND(Film.Rating.NOT_EQ(enum.MpaaRating.R)),
|
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(
|
).ORDER_BY(
|
||||||
Actor.ActorID.ASC(),
|
Actor.ActorID.ASC(),
|
||||||
Film.FilmID.ASC(),
|
Film.FilmID.ASC(),
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ const (
|
||||||
BaseType DataTypeKind = "base"
|
BaseType DataTypeKind = "base"
|
||||||
EnumType DataTypeKind = "enum"
|
EnumType DataTypeKind = "enum"
|
||||||
UserDefinedType DataTypeKind = "user-defined"
|
UserDefinedType DataTypeKind = "user-defined"
|
||||||
ArrayType DataTypeKind = "array"
|
|
||||||
RangeType DataTypeKind = "range"
|
RangeType DataTypeKind = "range"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -30,3 +29,7 @@ type DataType struct {
|
||||||
IsUnsigned bool
|
IsUnsigned bool
|
||||||
Dimensions int // The number of array dimensions
|
Dimensions int // The number of array dimensions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d DataType) IsArray() bool {
|
||||||
|
return d.Dimensions > 0
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,23 +66,25 @@ select
|
||||||
not attr.attnotnull as "column.isNullable",
|
not attr.attnotnull as "column.isNullable",
|
||||||
attr.attgenerated = 's' as "column.isGenerated",
|
attr.attgenerated = 's' as "column.isGenerated",
|
||||||
attr.atthasdef as "column.hasDefault",
|
attr.atthasdef as "column.hasDefault",
|
||||||
attr.attndims as "dataType.dimensions",
|
(case when tp.typcategory = 'A' then greatest(1, attr.attndims) --cockroach num dims fix
|
||||||
(case
|
else 0
|
||||||
when tp.typtype = 'b' AND tp.typcategory <> 'A' then 'base'
|
end) as "dataType.dimensions",
|
||||||
when tp.typtype = 'b' AND tp.typcategory = 'A' then 'array'
|
(case coalesce(elem.typtype, tp.typtype)
|
||||||
when tp.typtype = 'd' then 'base'
|
when 'b' then 'base'
|
||||||
when tp.typtype = 'e' then 'enum'
|
when 'd' then 'base'
|
||||||
when tp.typtype = 'r' then 'range'
|
when 'e' then 'enum'
|
||||||
end) as "dataType.Kind",
|
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)
|
(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
|
else tp.typname
|
||||||
end) as "dataType.Name",
|
end) as "dataType.Name",
|
||||||
false as "dataType.isUnsigned"
|
false as "dataType.isUnsigned"
|
||||||
from pg_catalog.pg_attribute as attr
|
from pg_catalog.pg_attribute as attr
|
||||||
join pg_catalog.pg_class as cls on cls.oid = attr.attrelid
|
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_namespace as ns on ns.oid = cls.relnamespace
|
||||||
join pg_catalog.pg_type as tp on tp.oid = attr.atttypid
|
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
|
where
|
||||||
ns.nspname = $1 and
|
ns.nspname = $1 and
|
||||||
cls.relname = $2 and
|
cls.relname = $2 and
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,17 @@ package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-jet/jet/v2/generator/metadata"
|
|
||||||
"github.com/go-jet/jet/v2/internal/utils/dbidentifier"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/jackc/pgtype"
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgtype"
|
||||||
|
|
||||||
|
"github.com/go-jet/jet/v2/generator/metadata"
|
||||||
|
"github.com/go-jet/jet/v2/internal/utils/dbidentifier"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Model is template for model files generation
|
// Model is template for model files generation
|
||||||
|
|
@ -246,10 +248,21 @@ func getType(columnMetadata metadata.Column) Type {
|
||||||
userDefinedType := getUserDefinedType(columnMetadata)
|
userDefinedType := getUserDefinedType(columnMetadata)
|
||||||
|
|
||||||
if userDefinedType != "" {
|
if userDefinedType != "" {
|
||||||
if columnMetadata.IsNullable {
|
var importPath string
|
||||||
return Type{Name: "*" + userDefinedType}
|
|
||||||
|
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))
|
return NewType(getGoType(columnMetadata))
|
||||||
|
|
@ -267,21 +280,44 @@ func getUserDefinedType(column metadata.Column) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGoType(column metadata.Column) interface{} {
|
func getGoType(column metadata.Column) interface{} {
|
||||||
defaultGoType := toGoType(column)
|
goType := toGoType(column)
|
||||||
|
|
||||||
if column.IsNullable {
|
if column.DataType.IsArray() {
|
||||||
return reflect.New(reflect.TypeOf(defaultGoType)).Interface()
|
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.
|
// toGoType returns model type for column info.
|
||||||
func toGoType(column metadata.Column) interface{} {
|
func toGoType(column metadata.Column) interface{} {
|
||||||
// We don't support multi-dimensional arrays
|
|
||||||
if column.DataType.Dimensions > 1 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
switch strings.ToLower(column.DataType.Name) {
|
switch strings.ToLower(column.DataType.Name) {
|
||||||
case "user-defined", "enum":
|
case "user-defined", "enum":
|
||||||
|
|
@ -348,14 +384,6 @@ func toGoType(column metadata.Column) interface{} {
|
||||||
return pgtype.Int8range{}
|
return pgtype.Int8range{}
|
||||||
case "numrange":
|
case "numrange":
|
||||||
return pgtype.Numrange{}
|
return pgtype.Numrange{}
|
||||||
case "bool[]":
|
|
||||||
return pq.BoolArray{}
|
|
||||||
case "integer[]", "int4[]":
|
|
||||||
return pq.Int32Array{}
|
|
||||||
case "bigint[]":
|
|
||||||
return pq.Int64Array{}
|
|
||||||
case "text[]", "jsonb[]", "json[]":
|
|
||||||
return pq.StringArray{}
|
|
||||||
default:
|
default:
|
||||||
fmt.Println("- [Model ] Unsupported sql column '" + column.Name + " " + column.DataType.Name + "', using string instead.")
|
fmt.Println("- [Model ] Unsupported sql column '" + column.Name + " " + column.DataType.Name + "', using string instead.")
|
||||||
return ""
|
return ""
|
||||||
|
|
|
||||||
|
|
@ -162,43 +162,33 @@ func DefaultTableSQLBuilderColumn(columnMetaData metadata.Column) TableSQLBuilde
|
||||||
|
|
||||||
// getSqlBuilderColumnType returns type of jet sql builder column
|
// getSqlBuilderColumnType returns type of jet sql builder column
|
||||||
func getSqlBuilderColumnType(columnMetaData metadata.Column) string {
|
func getSqlBuilderColumnType(columnMetaData metadata.Column) string {
|
||||||
if columnMetaData.DataType.Kind != metadata.BaseType &&
|
switch columnMetaData.DataType.Kind {
|
||||||
columnMetaData.DataType.Kind != metadata.RangeType &&
|
case metadata.EnumType, metadata.UserDefinedType:
|
||||||
columnMetaData.DataType.Kind != metadata.ArrayType {
|
if columnMetaData.DataType.IsArray() {
|
||||||
|
return "StringArray"
|
||||||
|
}
|
||||||
return "String"
|
return "String"
|
||||||
}
|
}
|
||||||
|
|
||||||
typeName := columnMetaData.DataType.Name
|
columnType := sqlToColumnType(columnMetaData)
|
||||||
columnName := columnMetaData.Name
|
|
||||||
|
|
||||||
if columnMetaData.DataType.Kind == metadata.ArrayType {
|
if columnMetaData.DataType.IsArray() {
|
||||||
if columnMetaData.DataType.Dimensions > 1 {
|
if columnMetaData.DataType.Dimensions > 1 {
|
||||||
fmt.Println("- [SQL Builder] Unsupported sql array with multiple dimensions column '" + columnName + " " + typeName + "', using StringColumn instead.")
|
fmt.Println("- [SQL Builder] Unsupported sql array with multiple dimensions column '" +
|
||||||
|
columnMetaData.Name + " " + columnMetaData.DataType.Name + "', using StringColumn instead.")
|
||||||
return "String"
|
return "String"
|
||||||
}
|
}
|
||||||
|
|
||||||
c := sqlToColumnType(strings.TrimSuffix(typeName, "[]"))
|
columnType = columnType + "Array"
|
||||||
|
|
||||||
// These are the supported array types
|
|
||||||
if slices.Index([]string{"Bool", "String", "Integer"}, c) == -1 {
|
|
||||||
fmt.Println("- [SQL Builder] Unsupported sql array column '" + columnName + " " + typeName + "', using StringColumn instead.")
|
|
||||||
return "String"
|
|
||||||
}
|
|
||||||
|
|
||||||
return c + "Array"
|
|
||||||
}
|
|
||||||
|
|
||||||
columnType := sqlToColumnType(typeName)
|
|
||||||
if columnType == "" {
|
|
||||||
fmt.Println("- [SQL Builder] Unsupported sql column '" + columnName + " " + typeName + "', using StringColumn instead.")
|
|
||||||
return "String"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return columnType
|
return columnType
|
||||||
}
|
}
|
||||||
|
|
||||||
func sqlToColumnType(typeName string) string {
|
// sqlToColumnType maps the type of a SQL column type to a go jet sql builder column. The second return value returns
|
||||||
switch strings.ToLower(typeName) {
|
// whether the given type is supported.
|
||||||
|
func sqlToColumnType(columnMetaData metadata.Column) string {
|
||||||
|
switch strings.ToLower(columnMetaData.DataType.Name) {
|
||||||
case "boolean", "bool":
|
case "boolean", "bool":
|
||||||
return "Bool"
|
return "Bool"
|
||||||
case "smallint", "integer", "bigint", "int2", "int4", "int8",
|
case "smallint", "integer", "bigint", "int2", "int4", "int8",
|
||||||
|
|
@ -243,7 +233,8 @@ func sqlToColumnType(typeName string) string {
|
||||||
case "numrange":
|
case "numrange":
|
||||||
return "NumericRange"
|
return "NumericRange"
|
||||||
default:
|
default:
|
||||||
return ""
|
fmt.Println("- [SQL Builder] Unsupported sql column '" + columnMetaData.Name + " " + columnMetaData.DataType.Name + "', using StringColumn instead.")
|
||||||
|
return "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,77 @@
|
||||||
package jet
|
package jet
|
||||||
|
|
||||||
// ArrayExpression interface
|
// Array interface
|
||||||
type ArrayExpression[E Expression] interface {
|
type Array[E Expression] interface {
|
||||||
Expression
|
Expression
|
||||||
|
|
||||||
EQ(rhs ArrayExpression[E]) BoolExpression
|
EQ(rhs Array[E]) BoolExpression
|
||||||
NOT_EQ(rhs ArrayExpression[E]) BoolExpression
|
NOT_EQ(rhs Array[E]) BoolExpression
|
||||||
LT(rhs ArrayExpression[E]) BoolExpression
|
LT(rhs Array[E]) BoolExpression
|
||||||
GT(rhs ArrayExpression[E]) BoolExpression
|
GT(rhs Array[E]) BoolExpression
|
||||||
LT_EQ(rhs ArrayExpression[E]) BoolExpression
|
LT_EQ(rhs Array[E]) BoolExpression
|
||||||
GT_EQ(rhs ArrayExpression[E]) BoolExpression
|
GT_EQ(rhs Array[E]) BoolExpression
|
||||||
|
|
||||||
CONTAINS(rhs ArrayExpression[E]) BoolExpression
|
CONTAINS(rhs Array[E]) BoolExpression
|
||||||
IS_CONTAINED_BY(rhs ArrayExpression[E]) BoolExpression
|
IS_CONTAINED_BY(rhs Array[E]) BoolExpression
|
||||||
OVERLAP(rhs ArrayExpression[E]) BoolExpression
|
OVERLAP(rhs Array[E]) BoolExpression
|
||||||
CONCAT(rhs ArrayExpression[E]) ArrayExpression[E]
|
CONCAT(rhs Array[E]) Array[E]
|
||||||
CONCAT_ELEMENT(E) ArrayExpression[E]
|
CONCAT_ELEMENT(E) Array[E]
|
||||||
|
|
||||||
AT(expression IntegerExpression) Expression
|
AT(expression IntegerExpression) E
|
||||||
}
|
}
|
||||||
|
|
||||||
type arrayInterfaceImpl[E Expression] struct {
|
type arrayInterfaceImpl[E Expression] struct {
|
||||||
parent ArrayExpression[E]
|
parent Array[E]
|
||||||
}
|
}
|
||||||
|
|
||||||
type BinaryBoolOp func(Expression, Expression) BoolExpression
|
type BinaryBoolOp func(Expression, Expression) BoolExpression
|
||||||
|
|
||||||
func (a arrayInterfaceImpl[E]) EQ(rhs ArrayExpression[E]) BoolExpression {
|
func (a arrayInterfaceImpl[E]) EQ(rhs Array[E]) BoolExpression {
|
||||||
return Eq(a.parent, rhs)
|
return Eq(a.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a arrayInterfaceImpl[E]) NOT_EQ(rhs ArrayExpression[E]) BoolExpression {
|
func (a arrayInterfaceImpl[E]) NOT_EQ(rhs Array[E]) BoolExpression {
|
||||||
return NotEq(a.parent, rhs)
|
return NotEq(a.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a arrayInterfaceImpl[E]) LT(rhs ArrayExpression[E]) BoolExpression {
|
func (a arrayInterfaceImpl[E]) LT(rhs Array[E]) BoolExpression {
|
||||||
return Lt(a.parent, rhs)
|
return Lt(a.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a arrayInterfaceImpl[E]) GT(rhs ArrayExpression[E]) BoolExpression {
|
func (a arrayInterfaceImpl[E]) GT(rhs Array[E]) BoolExpression {
|
||||||
return Gt(a.parent, rhs)
|
return Gt(a.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a arrayInterfaceImpl[E]) LT_EQ(rhs ArrayExpression[E]) BoolExpression {
|
func (a arrayInterfaceImpl[E]) LT_EQ(rhs Array[E]) BoolExpression {
|
||||||
return LtEq(a.parent, rhs)
|
return LtEq(a.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a arrayInterfaceImpl[E]) GT_EQ(rhs ArrayExpression[E]) BoolExpression {
|
func (a arrayInterfaceImpl[E]) GT_EQ(rhs Array[E]) BoolExpression {
|
||||||
return GtEq(a.parent, rhs)
|
return GtEq(a.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a arrayInterfaceImpl[E]) CONTAINS(rhs ArrayExpression[E]) BoolExpression {
|
func (a arrayInterfaceImpl[E]) CONTAINS(rhs Array[E]) BoolExpression {
|
||||||
return Contains(a.parent, rhs)
|
return Contains(a.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a arrayInterfaceImpl[E]) IS_CONTAINED_BY(rhs ArrayExpression[E]) BoolExpression {
|
func (a arrayInterfaceImpl[E]) IS_CONTAINED_BY(rhs Array[E]) BoolExpression {
|
||||||
return IsContainedBy(a.parent, rhs)
|
return IsContainedBy(a.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a arrayInterfaceImpl[E]) OVERLAP(rhs ArrayExpression[E]) BoolExpression {
|
func (a arrayInterfaceImpl[E]) OVERLAP(rhs Array[E]) BoolExpression {
|
||||||
return Overlap(a.parent, rhs)
|
return Overlap(a.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a arrayInterfaceImpl[E]) CONCAT(rhs ArrayExpression[E]) ArrayExpression[E] {
|
func (a arrayInterfaceImpl[E]) CONCAT(rhs Array[E]) Array[E] {
|
||||||
return ArrayExp[E](NewBinaryOperatorExpression(a.parent, rhs, "||"))
|
return ArrayExp[E](NewBinaryOperatorExpression(a.parent, rhs, "||"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a arrayInterfaceImpl[E]) CONCAT_ELEMENT(rhs E) ArrayExpression[E] {
|
func (a arrayInterfaceImpl[E]) CONCAT_ELEMENT(rhs E) Array[E] {
|
||||||
return ArrayExp[E](NewBinaryOperatorExpression(a.parent, rhs, "||"))
|
return ArrayExp[E](NewBinaryOperatorExpression(a.parent, rhs, "||"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a arrayInterfaceImpl[E]) AT(expression IntegerExpression) Expression {
|
func (a arrayInterfaceImpl[E]) AT(at IntegerExpression) E {
|
||||||
return arraySubscriptExpr(a.parent, expression)
|
return CastToArrayElemType[E](a.parent, CustomExpression(a.parent, Token("["), at, Token("]")))
|
||||||
}
|
}
|
||||||
|
|
||||||
type arrayExpressionWrapper[E Expression] struct {
|
type arrayExpressionWrapper[E Expression] struct {
|
||||||
|
|
@ -79,7 +79,7 @@ type arrayExpressionWrapper[E Expression] struct {
|
||||||
Expression
|
Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
func newArrayExpressionWrap[E Expression](expression Expression) ArrayExpression[E] {
|
func newArrayExpressionWrap[E Expression](expression Expression) Array[E] {
|
||||||
arrayExpressionWrapper := arrayExpressionWrapper[E]{Expression: expression}
|
arrayExpressionWrapper := arrayExpressionWrapper[E]{Expression: expression}
|
||||||
arrayExpressionWrapper.arrayInterfaceImpl.parent = &arrayExpressionWrapper
|
arrayExpressionWrapper.arrayInterfaceImpl.parent = &arrayExpressionWrapper
|
||||||
return &arrayExpressionWrapper
|
return &arrayExpressionWrapper
|
||||||
|
|
@ -88,6 +88,49 @@ func newArrayExpressionWrap[E Expression](expression Expression) ArrayExpression
|
||||||
// ArrayExp is array expression wrapper around arbitrary expression.
|
// ArrayExp is array expression wrapper around arbitrary expression.
|
||||||
// Allows go compiler to see any expression as array expression.
|
// Allows go compiler to see any expression as array expression.
|
||||||
// Does not add sql cast to generated sql builder output.
|
// Does not add sql cast to generated sql builder output.
|
||||||
func ArrayExp[E Expression](expression Expression) ArrayExpression[E] {
|
func ArrayExp[E Expression](expression Expression) Array[E] {
|
||||||
return newArrayExpressionWrap[E](expression)
|
return newArrayExpressionWrap[E](expression)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CastToArrayElemType casts exp to array element type
|
||||||
|
func CastToArrayElemType[E Expression](array Array[E], exp Expression) E {
|
||||||
|
var i Expression
|
||||||
|
switch array.(type) {
|
||||||
|
case Array[BoolExpression]:
|
||||||
|
i = BoolExp(exp)
|
||||||
|
case Array[StringExpression]:
|
||||||
|
i = StringExp(exp)
|
||||||
|
case Array[IntegerExpression]:
|
||||||
|
i = IntExp(exp)
|
||||||
|
case Array[FloatExpression]:
|
||||||
|
i = FloatExp(exp)
|
||||||
|
case Array[BlobExpression]:
|
||||||
|
i = BlobExp(exp)
|
||||||
|
case Array[DateExpression]:
|
||||||
|
i = DateExp(exp)
|
||||||
|
case Array[TimestampExpression]:
|
||||||
|
i = TimestampExp(exp)
|
||||||
|
case Array[TimestampzExpression]:
|
||||||
|
i = TimestampzExp(exp)
|
||||||
|
case Array[TimeExpression]:
|
||||||
|
i = TimeExp(exp)
|
||||||
|
case Array[TimezExpression]:
|
||||||
|
i = TimezExp(exp)
|
||||||
|
case Array[IntervalExpression]:
|
||||||
|
i = IntervalExp(exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.(E)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY constructor builds an array value using list of expressions.
|
||||||
|
func ARRAY[E Expression](elems ...E) Array[E] {
|
||||||
|
var args = make([]Serializer, len(elems))
|
||||||
|
for i, each := range elems {
|
||||||
|
args[i] = each
|
||||||
|
}
|
||||||
|
return ArrayExp[E](CustomExpression(Token("ARRAY["), ListSerializer{
|
||||||
|
Serializers: args,
|
||||||
|
Separator: ",",
|
||||||
|
}, Token("]")))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package jet
|
package jet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lib/pq"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -11,7 +10,6 @@ func TestArrayExpressionEQ(t *testing.T) {
|
||||||
|
|
||||||
func TestArrayExpressionNOT_EQ(t *testing.T) {
|
func TestArrayExpressionNOT_EQ(t *testing.T) {
|
||||||
assertClauseSerialize(t, table1ColStringArray.NOT_EQ(table2ColArray), "(table1.col_array_string != table2.col_array_string)")
|
assertClauseSerialize(t, table1ColStringArray.NOT_EQ(table2ColArray), "(table1.col_array_string != table2.col_array_string)")
|
||||||
assertClauseSerialize(t, table1ColStringArray.NOT_EQ(StringArray([]string{"x"})), "(table1.col_array_string != $1)", pq.StringArray{"x"})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArrayExpressionLT(t *testing.T) {
|
func TestArrayExpressionLT(t *testing.T) {
|
||||||
|
|
@ -32,12 +30,10 @@ func TestArrayExpressionGT_EQ(t *testing.T) {
|
||||||
|
|
||||||
func TestArrayExpressionCONTAINS(t *testing.T) {
|
func TestArrayExpressionCONTAINS(t *testing.T) {
|
||||||
assertClauseSerialize(t, table1ColStringArray.CONTAINS(table2ColArray), "(table1.col_array_string @> table2.col_array_string)")
|
assertClauseSerialize(t, table1ColStringArray.CONTAINS(table2ColArray), "(table1.col_array_string @> table2.col_array_string)")
|
||||||
assertClauseSerialize(t, table1ColStringArray.CONTAINS(StringArray([]string{"x"})), "(table1.col_array_string @> $1)", pq.StringArray{"x"})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArrayExpressionCONTAINED_BY(t *testing.T) {
|
func TestArrayExpressionCONTAINED_BY(t *testing.T) {
|
||||||
assertClauseSerialize(t, table1ColStringArray.IS_CONTAINED_BY(table2ColArray), "(table1.col_array_string <@ table2.col_array_string)")
|
assertClauseSerialize(t, table1ColStringArray.IS_CONTAINED_BY(table2ColArray), "(table1.col_array_string <@ table2.col_array_string)")
|
||||||
assertClauseSerialize(t, table1ColStringArray.IS_CONTAINED_BY(StringArray([]string{"x"})), "(table1.col_array_string <@ $1)", pq.StringArray{"x"})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArrayExpressionOVERLAP(t *testing.T) {
|
func TestArrayExpressionOVERLAP(t *testing.T) {
|
||||||
|
|
@ -46,14 +42,27 @@ func TestArrayExpressionOVERLAP(t *testing.T) {
|
||||||
|
|
||||||
func TestArrayExpressionCONCAT(t *testing.T) {
|
func TestArrayExpressionCONCAT(t *testing.T) {
|
||||||
assertClauseSerialize(t, table1ColStringArray.CONCAT(table2ColArray), "(table1.col_array_string || table2.col_array_string)")
|
assertClauseSerialize(t, table1ColStringArray.CONCAT(table2ColArray), "(table1.col_array_string || table2.col_array_string)")
|
||||||
assertClauseSerialize(t, table1ColStringArray.CONCAT(StringArray([]string{"x"})), "(table1.col_array_string || $1)", pq.StringArray{"x"})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArrayExpressionCONCAT_ELEMENT(t *testing.T) {
|
func TestArrayExpressionCONCAT_ELEMENT(t *testing.T) {
|
||||||
assertClauseSerialize(t, table1ColStringArray.CONCAT_ELEMENT(StringExp(table2ColArray.AT(Int(1)))), "(table1.col_array_string || (table2.col_array_string[$1]))", int64(1))
|
assertClauseSerialize(t, table1ColStringArray.CONCAT_ELEMENT(StringExp(table2ColArray.AT(Int(1)))), "(table1.col_array_string || table2.col_array_string[$1])", int64(1))
|
||||||
assertClauseSerialize(t, table1ColStringArray.CONCAT_ELEMENT(String("x")), "(table1.col_array_string || $1)", "x")
|
assertClauseSerialize(t, table1ColStringArray.CONCAT_ELEMENT(String("x")), "(table1.col_array_string || $1)", "x")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArrayExpressionAT(t *testing.T) {
|
func TestArrayExpressionAT(t *testing.T) {
|
||||||
assertClauseSerialize(t, table1ColStringArray.AT(Int(1)), "(table1.col_array_string[$1])", int64(1))
|
assertClauseSerialize(t, table1ColStringArray.AT(Int(1)), "table1.col_array_string[$1]", int64(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCastToArrayElemType(t *testing.T) {
|
||||||
|
var _ BoolExpression = CastToArrayElemType[BoolExpression](ARRAY[BoolExpression](), table1Col1)
|
||||||
|
var _ IntegerExpression = CastToArrayElemType[IntegerExpression](ARRAY[IntegerExpression](), table1Col1)
|
||||||
|
var _ FloatExpression = CastToArrayElemType[FloatExpression](ARRAY[FloatExpression](), table1Col1)
|
||||||
|
var _ StringExpression = CastToArrayElemType[StringExpression](ARRAY[StringExpression](), table1Col1)
|
||||||
|
var _ BlobExpression = CastToArrayElemType[BlobExpression](ARRAY[BlobExpression](), table1Col1)
|
||||||
|
var _ DateExpression = CastToArrayElemType[DateExpression](ARRAY[DateExpression](), table1Col1)
|
||||||
|
var _ TimestampExpression = CastToArrayElemType[TimestampExpression](ARRAY[TimestampExpression](), table1Col1)
|
||||||
|
var _ TimestampzExpression = CastToArrayElemType[TimestampzExpression](ARRAY[TimestampzExpression](), table1Col1)
|
||||||
|
var _ TimeExpression = CastToArrayElemType[TimeExpression](ARRAY[TimeExpression](), table1Col1)
|
||||||
|
var _ TimezExpression = CastToArrayElemType[TimezExpression](ARRAY[TimezExpression](), table1Col1)
|
||||||
|
var _ IntervalExpression = CastToArrayElemType[IntervalExpression](ARRAY[IntervalExpression](), table1Col1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,17 +134,21 @@ func IntegerColumn(name string) ColumnInteger {
|
||||||
//------------------------------------------------------//
|
//------------------------------------------------------//
|
||||||
|
|
||||||
type ColumnArray[E Expression] interface {
|
type ColumnArray[E Expression] interface {
|
||||||
ArrayExpression[E]
|
Array[E]
|
||||||
Column
|
Column
|
||||||
|
|
||||||
From(subQuery SelectTable) ColumnArray[E]
|
From(subQuery SelectTable) ColumnArray[E]
|
||||||
SET(stringExp ArrayExpression[E]) ColumnAssigment
|
SET(stringExp Array[E]) ColumnAssigment
|
||||||
}
|
}
|
||||||
|
|
||||||
type arrayColumnImpl[E Expression] struct {
|
type arrayColumnImpl[E Expression] struct {
|
||||||
arrayInterfaceImpl[E]
|
arrayInterfaceImpl[E]
|
||||||
|
|
||||||
ColumnExpressionImpl
|
*ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayColumnImpl[E]) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return a.From(subQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a arrayColumnImpl[E]) From(subQuery SelectTable) ColumnArray[E] {
|
func (a arrayColumnImpl[E]) From(subQuery SelectTable) ColumnArray[E] {
|
||||||
|
|
@ -155,10 +159,10 @@ func (a arrayColumnImpl[E]) From(subQuery SelectTable) ColumnArray[E] {
|
||||||
return newArrayColumn
|
return newArrayColumn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *arrayColumnImpl[E]) SET(stringExp ArrayExpression[E]) ColumnAssigment {
|
func (a *arrayColumnImpl[E]) SET(stringExp Array[E]) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: a,
|
column: a,
|
||||||
expression: stringExp,
|
toAssign: stringExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package jet
|
package jet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lib/pq"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -12,36 +11,30 @@ var subQuery = &selectTableImpl{
|
||||||
func TestNewArrayColumnString(t *testing.T) {
|
func TestNewArrayColumnString(t *testing.T) {
|
||||||
stringArrayColumn := ArrayColumn[StringExpression]("colArray").From(subQuery)
|
stringArrayColumn := ArrayColumn[StringExpression]("colArray").From(subQuery)
|
||||||
assertClauseSerialize(t, stringArrayColumn, `sub_query."colArray"`)
|
assertClauseSerialize(t, stringArrayColumn, `sub_query."colArray"`)
|
||||||
assertClauseSerialize(t, stringArrayColumn.EQ(StringArray([]string{"X"})), `(sub_query."colArray" = $1)`, pq.StringArray{"X"})
|
|
||||||
assertProjectionSerialize(t, stringArrayColumn, `sub_query."colArray" AS "colArray"`)
|
assertProjectionSerialize(t, stringArrayColumn, `sub_query."colArray" AS "colArray"`)
|
||||||
|
|
||||||
arrayColumn2 := table1ColStringArray.From(subQuery)
|
arrayColumn2 := table1ColStringArray.From(subQuery)
|
||||||
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_string"`)
|
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_string"`)
|
||||||
assertClauseSerialize(t, arrayColumn2.EQ(StringArray([]string{"X"})), `(sub_query."table1.col_array_string" = $1)`, pq.StringArray{"X"})
|
|
||||||
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_string" AS "table1.col_array_string"`)
|
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_string" AS "table1.col_array_string"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewArrayColumnBool(t *testing.T) {
|
func TestNewArrayColumnBool(t *testing.T) {
|
||||||
boolArrayColumn := ArrayColumn[BoolExpression]("colArrayBool").From(subQuery)
|
boolArrayColumn := ArrayColumn[BoolExpression]("colArrayBool").From(subQuery)
|
||||||
assertClauseSerialize(t, boolArrayColumn, `sub_query."colArrayBool"`)
|
assertClauseSerialize(t, boolArrayColumn, `sub_query."colArrayBool"`)
|
||||||
assertClauseSerialize(t, boolArrayColumn.EQ(BoolArray([]bool{true})), `(sub_query."colArrayBool" = $1)`, pq.BoolArray{true})
|
|
||||||
assertProjectionSerialize(t, boolArrayColumn, `sub_query."colArrayBool" AS "colArrayBool"`)
|
assertProjectionSerialize(t, boolArrayColumn, `sub_query."colArrayBool" AS "colArrayBool"`)
|
||||||
|
|
||||||
arrayColumn2 := table1ColBoolArray.From(subQuery)
|
arrayColumn2 := table1ColBoolArray.From(subQuery)
|
||||||
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_bool"`)
|
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_bool"`)
|
||||||
assertClauseSerialize(t, arrayColumn2.EQ(BoolArray([]bool{true})), `(sub_query."table1.col_array_bool" = $1)`, pq.BoolArray{true})
|
|
||||||
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_bool" AS "table1.col_array_bool"`)
|
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_bool" AS "table1.col_array_bool"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewArrayColumnInteger(t *testing.T) {
|
func TestNewArrayColumnInteger(t *testing.T) {
|
||||||
intArrayColumn := ArrayColumn[IntegerExpression]("colArrayInt").From(subQuery)
|
intArrayColumn := ArrayColumn[IntegerExpression]("colArrayInt").From(subQuery)
|
||||||
assertClauseSerialize(t, intArrayColumn, `sub_query."colArrayInt"`)
|
assertClauseSerialize(t, intArrayColumn, `sub_query."colArrayInt"`)
|
||||||
assertClauseSerialize(t, intArrayColumn.EQ(Int32Array([]int32{42})), `(sub_query."colArrayInt" = $1)`, pq.Int32Array{42})
|
|
||||||
assertProjectionSerialize(t, intArrayColumn, `sub_query."colArrayInt" AS "colArrayInt"`)
|
assertProjectionSerialize(t, intArrayColumn, `sub_query."colArrayInt" AS "colArrayInt"`)
|
||||||
|
|
||||||
arrayColumn2 := table1ColIntArray.From(subQuery)
|
arrayColumn2 := table1ColIntArray.From(subQuery)
|
||||||
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_int"`)
|
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_int"`)
|
||||||
assertClauseSerialize(t, arrayColumn2.EQ(Int32Array([]int32{42})), `(sub_query."table1.col_array_int" = $1)`, pq.Int32Array{42})
|
|
||||||
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_int" AS "table1.col_array_int"`)
|
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_int" AS "table1.col_array_int"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -341,42 +341,3 @@ func (s *complexExpression) serialize(statement StatementType, out *SQLBuilder,
|
||||||
func wrap(expressions ...Expression) Expression {
|
func wrap(expressions ...Expression) Expression {
|
||||||
return NewFunc("", expressions, nil)
|
return NewFunc("", expressions, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
type arraySubscriptExpression struct {
|
|
||||||
ExpressionInterfaceImpl
|
|
||||||
array Expression
|
|
||||||
subscript IntegerExpression
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a arraySubscriptExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
|
||||||
if !contains(options, NoWrap) {
|
|
||||||
out.WriteString("(")
|
|
||||||
}
|
|
||||||
a.array.serialize(statement, out, FallTrough(options)...) // FallTrough here because complexExpression is just a wrapper
|
|
||||||
out.WriteString("[")
|
|
||||||
a.subscript.serialize(statement, out, FallTrough(options)...) // FallTrough here because complexExpression is just a wrapper
|
|
||||||
out.WriteString("]")
|
|
||||||
if !contains(options, NoWrap) {
|
|
||||||
out.WriteString(")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func arraySubscriptExpr(array Expression, subscript IntegerExpression) Expression {
|
|
||||||
arraySubscriptExpression := &arraySubscriptExpression{array: array, subscript: subscript}
|
|
||||||
arraySubscriptExpression.ExpressionInterfaceImpl.Parent = arraySubscriptExpression
|
|
||||||
|
|
||||||
return arraySubscriptExpression
|
|
||||||
}
|
|
||||||
|
|
||||||
type skipParenthesisWrap struct {
|
|
||||||
Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
func skipWrap(expression Expression) Expression {
|
|
||||||
return &skipParenthesisWrap{expression}
|
|
||||||
}
|
|
||||||
|
|
||||||
// since the expression is a function parameter, there is no need to wrap it in parentheses
|
|
||||||
func (s *skipParenthesisWrap) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
|
||||||
s.Expression.serialize(statement, out, append(options, NoWrap)...)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package jet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lib/pq"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -161,66 +160,6 @@ func Decimal(value string) FloatExpression {
|
||||||
return &floatLiteral
|
return &floatLiteral
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------//
|
|
||||||
|
|
||||||
type boolArrayLiteral struct {
|
|
||||||
arrayInterfaceImpl[BoolExpression]
|
|
||||||
literalExpressionImpl
|
|
||||||
}
|
|
||||||
|
|
||||||
func BoolArray(values []bool) ArrayExpression[BoolExpression] {
|
|
||||||
l := boolArrayLiteral{}
|
|
||||||
l.literalExpressionImpl = *literal(pq.BoolArray(values))
|
|
||||||
l.arrayInterfaceImpl.parent = &l
|
|
||||||
|
|
||||||
return &l
|
|
||||||
}
|
|
||||||
|
|
||||||
type integerArrayLiteral struct {
|
|
||||||
arrayInterfaceImpl[IntegerExpression]
|
|
||||||
literalExpressionImpl
|
|
||||||
}
|
|
||||||
|
|
||||||
func Int64Array(values []int64) ArrayExpression[IntegerExpression] {
|
|
||||||
l := integerArrayLiteral{}
|
|
||||||
l.literalExpressionImpl = *literal(pq.Int64Array(values))
|
|
||||||
l.arrayInterfaceImpl.parent = &l
|
|
||||||
return &l
|
|
||||||
}
|
|
||||||
|
|
||||||
func Int32Array(values []int32) ArrayExpression[IntegerExpression] {
|
|
||||||
l := integerArrayLiteral{}
|
|
||||||
l.literalExpressionImpl = *literal(pq.Int32Array(values))
|
|
||||||
l.arrayInterfaceImpl.parent = &l
|
|
||||||
return &l
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringArrayLiteral struct {
|
|
||||||
arrayInterfaceImpl[StringExpression]
|
|
||||||
literalExpressionImpl
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringArray(values []string) ArrayExpression[StringExpression] {
|
|
||||||
l := stringArrayLiteral{}
|
|
||||||
l.literalExpressionImpl = *literal(pq.StringArray(values))
|
|
||||||
l.arrayInterfaceImpl.parent = &l
|
|
||||||
|
|
||||||
return &l
|
|
||||||
}
|
|
||||||
|
|
||||||
type unsafeArrayLiteral[E Expression] struct {
|
|
||||||
arrayInterfaceImpl[E]
|
|
||||||
literalExpressionImpl
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnsafeArray[E LiteralExpression](values []interface{}) ArrayExpression[E] {
|
|
||||||
l := unsafeArrayLiteral[E]{}
|
|
||||||
l.literalExpressionImpl = *literal(pq.Array(values))
|
|
||||||
l.arrayInterfaceImpl.parent = &l
|
|
||||||
|
|
||||||
return &l
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------//
|
// ---------------------------------------------------//
|
||||||
type stringLiteral struct {
|
type stringLiteral struct {
|
||||||
stringInterfaceImpl
|
stringInterfaceImpl
|
||||||
|
|
|
||||||
|
|
@ -22,15 +22,6 @@ func BIT_NOT(expr IntegerExpression) IntegerExpression {
|
||||||
return newPrefixIntegerOperatorExpression(expr, "~")
|
return newPrefixIntegerOperatorExpression(expr, "~")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------- Array operators -------------- //
|
|
||||||
func Any(lhs Expression, op BinaryBoolOp, rhs Expression) BoolExpression {
|
|
||||||
return op(lhs, Func("ANY", rhs))
|
|
||||||
}
|
|
||||||
|
|
||||||
func All(lhs Expression, op BinaryBoolOp, rhs Expression) BoolExpression {
|
|
||||||
return op(lhs, Func("ALL", rhs))
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------- Comparison operators ---------------//
|
//----------- Comparison operators ---------------//
|
||||||
|
|
||||||
// EXISTS checks for existence of the rows in subQuery
|
// EXISTS checks for existence of the rows in subQuery
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,15 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/go-jet/jet/v2/internal/3rdparty/pq"
|
||||||
|
"github.com/go-jet/jet/v2/internal/utils/is"
|
||||||
|
"github.com/google/uuid"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"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
|
// SQLBuilder generates output SQL
|
||||||
|
|
@ -249,8 +248,6 @@ func (s *SQLBuilder) argToString(value interface{}) string {
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
return stringQuote(bindVal)
|
return stringQuote(bindVal)
|
||||||
case []string:
|
|
||||||
return stringArrayQuote(bindVal)
|
|
||||||
case []byte:
|
case []byte:
|
||||||
return stringQuote(string(bindVal))
|
return stringQuote(string(bindVal))
|
||||||
case uuid.UUID:
|
case uuid.UUID:
|
||||||
|
|
@ -278,19 +275,6 @@ func (s *SQLBuilder) argToString(value interface{}) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringArrayQuote(val []string) string {
|
|
||||||
var sb strings.Builder
|
|
||||||
sb.WriteString(`'{`)
|
|
||||||
for i := 0; i < len(val); i++ {
|
|
||||||
if i > 0 {
|
|
||||||
sb.WriteString(`, `)
|
|
||||||
}
|
|
||||||
sb.WriteString(stringDoubleQuote(val[i]))
|
|
||||||
}
|
|
||||||
sb.WriteString(`}'`)
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func integerTypesToString(value interface{}) string {
|
func integerTypesToString(value interface{}) string {
|
||||||
switch bindVal := value.(type) {
|
switch bindVal := value.(type) {
|
||||||
case int:
|
case int:
|
||||||
|
|
@ -339,7 +323,3 @@ func shouldQuoteIdentifier(identifier string) bool {
|
||||||
func stringQuote(value string) string {
|
func stringQuote(value string) string {
|
||||||
return `'` + strings.Replace(value, "'", "''", -1) + `'`
|
return `'` + strings.Replace(value, "'", "''", -1) + `'`
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringDoubleQuote(value string) string {
|
|
||||||
return `"` + strings.Replace(value, `"`, `""`, -1) + `"`
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,6 @@ type StringExpression interface {
|
||||||
BETWEEN(min, max StringExpression) BoolExpression
|
BETWEEN(min, max StringExpression) BoolExpression
|
||||||
NOT_BETWEEN(min, max StringExpression) BoolExpression
|
NOT_BETWEEN(min, max StringExpression) BoolExpression
|
||||||
|
|
||||||
ANY_EQ(rhs ArrayExpression[StringExpression]) BoolExpression
|
|
||||||
ALL_EQ(rhs ArrayExpression[StringExpression]) BoolExpression
|
|
||||||
|
|
||||||
CONCAT(rhs Expression) StringExpression
|
CONCAT(rhs Expression) StringExpression
|
||||||
|
|
||||||
LIKE(pattern StringExpression) BoolExpression
|
LIKE(pattern StringExpression) BoolExpression
|
||||||
|
|
@ -75,14 +72,6 @@ func (s *stringInterfaceImpl) NOT_BETWEEN(min, max StringExpression) BoolExpress
|
||||||
return NewBetweenOperatorExpression(s.root, min, max, true)
|
return NewBetweenOperatorExpression(s.root, min, max, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *stringInterfaceImpl) ANY_EQ(rhs ArrayExpression[StringExpression]) BoolExpression {
|
|
||||||
return Any(i.parent, Eq, rhs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *stringInterfaceImpl) ALL_EQ(rhs ArrayExpression[StringExpression]) BoolExpression {
|
|
||||||
return All(i.parent, Eq, rhs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stringInterfaceImpl) CONCAT(rhs Expression) StringExpression {
|
func (s *stringInterfaceImpl) CONCAT(rhs Expression) StringExpression {
|
||||||
return newBinaryStringOperatorExpression(s.root, rhs, StringConcatOperator)
|
return newBinaryStringOperatorExpression(s.root, rhs, StringConcatOperator)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,14 +76,6 @@ func TestStringNOT_REGEXP_LIKE(t *testing.T) {
|
||||||
assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 NOT REGEXP $1)", "JOHN")
|
assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 NOT REGEXP $1)", "JOHN")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringANY_EQ(t *testing.T) {
|
|
||||||
assertClauseSerialize(t, table2ColStr.ANY_EQ(table1ColStringArray), "(table2.col_str = ANY(table1.col_array_string))")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringALL_EQ(t *testing.T) {
|
|
||||||
assertClauseSerialize(t, table2ColStr.ALL_EQ(table1ColStringArray), "(table2.col_str = ALL(table1.col_array_string))")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringExp(t *testing.T) {
|
func TestStringExp(t *testing.T) {
|
||||||
assertClauseSerialize(t, StringExp(table2ColFloat), "table2.col_float")
|
assertClauseSerialize(t, StringExp(table2ColFloat), "table2.col_float")
|
||||||
assertClauseSerialize(t, StringExp(table2ColFloat).NOT_LIKE(String("abc")), "(table2.col_float NOT LIKE $1)", "abc")
|
assertClauseSerialize(t, StringExp(table2ColFloat).NOT_LIKE(String("abc")), "(table2.col_float NOT LIKE $1)", "abc")
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,16 @@ func AssertFileNamesEqual(t *testing.T, dirPath string, fileNames ...string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopy create deep copy of src
|
||||||
|
func DeepCopy[T any](t require.TestingT, src T) T {
|
||||||
|
var dst T
|
||||||
|
data, err := json.Marshal(src)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = json.Unmarshal(data, &dst)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
// AssertDeepEqual checks if actual and expected objects are deeply equal.
|
// AssertDeepEqual checks if actual and expected objects are deeply equal.
|
||||||
func AssertDeepEqual(t require.TestingT, actual, expected interface{}, option ...cmp.Option) {
|
func AssertDeepEqual(t require.TestingT, actual, expected interface{}, option ...cmp.Option) {
|
||||||
if !assert.True(t, cmp.Equal(actual, expected, 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"))
|
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 {
|
func (b *cast) AS_DECIMAL() FloatExpression {
|
||||||
return FloatExp(b.AS("decimal"))
|
return FloatExp(b.AS("decimal"))
|
||||||
}
|
}
|
||||||
|
|
@ -130,3 +130,68 @@ func (b *cast) AS_TIMESTAMPZ() TimestampzExpression {
|
||||||
func (b *cast) AS_INTERVAL() IntervalExpression {
|
func (b *cast) AS_INTERVAL() IntervalExpression {
|
||||||
return IntervalExp(b.AS("interval"))
|
return IntervalExp(b.AS("interval"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AS_UUID casts expression AS uuid type
|
||||||
|
func (b *cast) AS_UUID() StringExpression {
|
||||||
|
return StringExp(b.AS("uuid"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS_BOOL_ARRAY casts expression as boolean array type
|
||||||
|
func (b *cast) AS_BOOL_ARRAY() Array[BoolExpression] {
|
||||||
|
return ArrayExp[BoolExpression](b.AS("boolean[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS_INTEGER_ARRAY casts expression as integer array type
|
||||||
|
func (b *cast) AS_INTEGER_ARRAY() Array[IntegerExpression] {
|
||||||
|
return ArrayExp[IntegerExpression](b.AS("integer[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS_BIGINT_ARRAY casts expression as bigint array type
|
||||||
|
func (b *cast) AS_BIGINT_ARRAY() Array[IntegerExpression] {
|
||||||
|
return ArrayExp[IntegerExpression](b.AS("bigint[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS_REAL_ARRAY casts expression as real array
|
||||||
|
func (b *cast) AS_REAL_ARRAY() Array[FloatExpression] {
|
||||||
|
return ArrayExp[FloatExpression](b.AS("real[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS_DOUBLE_ARRAY casts expression as double precision array
|
||||||
|
func (b *cast) AS_DOUBLE_ARRAY() Array[FloatExpression] {
|
||||||
|
return ArrayExp[FloatExpression](b.AS("double precision[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS_TEXT_ARRAY casts expression as text array
|
||||||
|
func (b *cast) AS_TEXT_ARRAY() Array[StringExpression] {
|
||||||
|
return ArrayExp[StringExpression](b.AS("text[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS_BYTEA_ARRAY casts expression as bytea array
|
||||||
|
func (b *cast) AS_BYTEA_ARRAY() Array[ByteaExpression] {
|
||||||
|
return ArrayExp[ByteaExpression](b.AS("bytea[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS_DATE_ARRAY casts expression as date array
|
||||||
|
func (b *cast) AS_DATE_ARRAY() Array[DateExpression] {
|
||||||
|
return ArrayExp[DateExpression](b.AS("date[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS_TIMESTAMP_ARRAY casts expression as timestamp array
|
||||||
|
func (b *cast) AS_TIMESTAMP_ARRAY() Array[TimestampExpression] {
|
||||||
|
return ArrayExp[TimestampExpression](b.AS("timestamp without time zone[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS_TIMESTAMPZ_ARRAY casts expression as timestamp with time zone array
|
||||||
|
func (b *cast) AS_TIMESTAMPZ_ARRAY() Array[TimestampzExpression] {
|
||||||
|
return ArrayExp[TimestampzExpression](b.AS("timestamp with time zone[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS_TIME_ARRAY casts expression as time array
|
||||||
|
func (b *cast) AS_TIME_ARRAY() Array[TimeExpression] {
|
||||||
|
return ArrayExp[TimeExpression](b.AS("time without time zone[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS_TIMEZ_ARRAY casts expression as time with timezone array
|
||||||
|
func (b *cast) AS_TIMEZ_ARRAY() Array[TimezExpression] {
|
||||||
|
return ArrayExp[TimezExpression](b.AS("time with time zone[]"))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,21 +112,3 @@ type ColumnInt8Range jet.ColumnRange[jet.Int8Expression]
|
||||||
|
|
||||||
// Int8RangeColumn creates named range with range column
|
// Int8RangeColumn creates named range with range column
|
||||||
var Int8RangeColumn = jet.RangeColumn[jet.Int8Expression]
|
var Int8RangeColumn = jet.RangeColumn[jet.Int8Expression]
|
||||||
|
|
||||||
// ColumnStringArray is interface of column
|
|
||||||
type ColumnStringArray jet.ColumnArray[jet.StringExpression]
|
|
||||||
|
|
||||||
// StringArrayColumn creates named string array column
|
|
||||||
var StringArrayColumn = jet.ArrayColumn[jet.StringExpression]
|
|
||||||
|
|
||||||
// ColumnIntegerArray is interface of column
|
|
||||||
type ColumnIntegerArray jet.ColumnArray[jet.IntegerExpression]
|
|
||||||
|
|
||||||
// IntegerArrayColumn creates named integer array column
|
|
||||||
var IntegerArrayColumn = jet.ArrayColumn[jet.IntegerExpression]
|
|
||||||
|
|
||||||
// ColumnBoolArray is interface of column
|
|
||||||
type ColumnBoolArray jet.ColumnArray[jet.BoolExpression]
|
|
||||||
|
|
||||||
// BoolArrayColumn creates named bool array column
|
|
||||||
var BoolArrayColumn = jet.ArrayColumn[jet.BoolExpression]
|
|
||||||
|
|
|
||||||
|
|
@ -2,33 +2,24 @@ package postgres
|
||||||
|
|
||||||
import "github.com/go-jet/jet/v2/internal/jet"
|
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.
|
// Can be Bool, Int, Float, String, Date, Time, Timez, Timestamp or Timestampz expressions.
|
||||||
type Expression = jet.Expression
|
type Expression = jet.Expression
|
||||||
|
|
||||||
// BoolExpression interface
|
// BoolExpression interface
|
||||||
type BoolExpression = jet.BoolExpression
|
type BoolExpression = jet.BoolExpression
|
||||||
|
|
||||||
// BoolArrayExpression interface
|
|
||||||
type BoolArrayExpression = jet.ArrayExpression[BoolExpression]
|
|
||||||
|
|
||||||
// StringExpression interface
|
// StringExpression interface
|
||||||
type StringExpression = jet.StringExpression
|
type StringExpression = jet.StringExpression
|
||||||
|
|
||||||
type ByteaExpression = jet.BlobExpression
|
type ByteaExpression = jet.BlobExpression
|
||||||
|
|
||||||
// StringArrayExpression interface
|
|
||||||
type StringArrayExpression = jet.ArrayExpression[StringExpression]
|
|
||||||
|
|
||||||
// NumericExpression interface
|
// NumericExpression interface
|
||||||
type NumericExpression = jet.NumericExpression
|
type NumericExpression = jet.NumericExpression
|
||||||
|
|
||||||
// IntegerExpression interface
|
// IntegerExpression interface
|
||||||
type IntegerExpression = jet.IntegerExpression
|
type IntegerExpression = jet.IntegerExpression
|
||||||
|
|
||||||
// IntegerArrayExpression interface
|
|
||||||
type IntegerArrayExpression = jet.ArrayExpression[IntegerExpression]
|
|
||||||
|
|
||||||
// FloatExpression is interface
|
// FloatExpression is interface
|
||||||
type FloatExpression = jet.FloatExpression
|
type FloatExpression = jet.FloatExpression
|
||||||
|
|
||||||
|
|
@ -185,3 +176,13 @@ var NewEnumValue = jet.NewEnumValue
|
||||||
|
|
||||||
// BinaryOperator can be used to use custom or unsupported operators that take two operands.
|
// BinaryOperator can be used to use custom or unsupported operators that take two operands.
|
||||||
var BinaryOperator = jet.BinaryOperator
|
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
|
// TO_HEX converts number to its equivalent hexadecimal representation
|
||||||
var TO_HEX = jet.TO_HEX
|
var TO_HEX = jet.TO_HEX
|
||||||
|
|
||||||
//----------Data Type Formatting Functions ----------------------//
|
//---------- Range Functions ----------------------//
|
||||||
|
|
||||||
// LOWER_BOUND returns range expressions lower bound
|
// LOWER_BOUND returns range expressions lower bound
|
||||||
func LOWER_BOUND[T Expression](expression jet.Range[T]) T {
|
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)
|
return jet.UPPER_BOUND[T](expression)
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------Data Type Formatting Functions ----------------------//
|
// ---------- Array Functions ----------------------//
|
||||||
|
|
||||||
|
// ANY should be used in combination with a boolean operator. The result of ANY is "true" if any true result is obtained
|
||||||
|
func ANY[E Expression](arr Array[E]) E {
|
||||||
|
return jet.CastToArrayElemType(arr, Func("ANY", arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALL should be used in combination with a boolean operator. The result of ALL is “true” if all comparisons yield true
|
||||||
|
func ALL[E Expression](arr Array[E]) E {
|
||||||
|
return jet.CastToArrayElemType(arr, Func("ALL", arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_APPEND appends an element to the end of an array
|
||||||
|
func ARRAY_APPEND[E Expression](arr Array[E], elem E) Array[E] {
|
||||||
|
return ArrayExp[E](Func("ARRAY_APPEND", arr, elem))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_CAT concatenates two arrays
|
||||||
|
func ARRAY_CAT[E Expression](arr1, arr2 Array[E]) Array[E] {
|
||||||
|
return ArrayExp[E](Func("ARRAY_CAT", arr1, arr2))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_DIMS returns a text representation of the array's dimensions.
|
||||||
|
func ARRAY_DIMS[E Expression](arr Array[E]) StringExpression {
|
||||||
|
return StringExp(Func("ARRAY_DIMS", arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_LENGTH returns the length of the requested array dimension.
|
||||||
|
// Produces NULL instead of 0 for empty or missing array dimensions.
|
||||||
|
func ARRAY_LENGTH[E Expression](arr Array[E], elem IntegerExpression) IntegerExpression {
|
||||||
|
return IntExp(Func("ARRAY_LENGTH", arr, elem))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_LOWER returns the lower bound of the requested array dimension.
|
||||||
|
func ARRAY_LOWER[E Expression](arr Array[E]) IntegerExpression {
|
||||||
|
return IntExp(Func("ARRAY_LOWER", arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_NDIMS returns the number of dimensions of the array.
|
||||||
|
func ARRAY_NDIMS[E Expression](arr Array[E]) IntegerExpression {
|
||||||
|
return IntExp(Func("ARRAY_NDIMS", arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_POSITION returns the subscript of the first occurrence of the second argument in the array, or NULL if it's not present.
|
||||||
|
// If the third argument is given, the search begins at that subscript.
|
||||||
|
// The array must be one-dimensional.
|
||||||
|
// Comparisons are done using IS NOT DISTINCT FROM semantics, so it is possible to search for NULL.
|
||||||
|
func ARRAY_POSITION[E Expression](arr Array[E], elem E, beginAt ...IntegerExpression) IntegerExpression {
|
||||||
|
return IntExp(Func("ARRAY_POSITION", optionalAppend([]Expression{arr, elem}, beginAt)...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_POSITIONS returns an array of the subscripts of all occurrences of the second argument in the array given as first argument.
|
||||||
|
// The array must be one-dimensional.
|
||||||
|
// Comparisons are done using IS NOT DISTINCT FROM semantics, so it is possible to search for NULL.
|
||||||
|
// NULL is returned only if the array is NULL; if the value is not found in the array, an empty array is returned.
|
||||||
|
func ARRAY_POSITIONS[E Expression](arr Array[E], elem E) Array[IntegerExpression] {
|
||||||
|
return ArrayExp[IntegerExpression](Func("ARRAY_POSITIONS", arr, elem))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_PREPEND prepends an element to the beginning of an array
|
||||||
|
func ARRAY_PREPEND[E Expression](el E, arr Array[E]) Array[E] {
|
||||||
|
return ArrayExp[E](Func("ARRAY_PREPEND", el, arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_REMOVE removes all elements equal to the given value from the array. The array must be one-dimensional.
|
||||||
|
// Comparisons are done using IS NOT DISTINCT FROM semantics, so it is possible to remove NULLs.
|
||||||
|
func ARRAY_REMOVE[E Expression](arr Array[E], elem Expression) IntegerExpression {
|
||||||
|
return IntExp(Func("ARRAY_REMOVE", arr, elem))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_REPLACE replaces each array element equal to the second argument with the third argument.
|
||||||
|
func ARRAY_REPLACE[E Expression](arr Array[E], existing E, new E) Array[E] {
|
||||||
|
return ArrayExp[E](Func("ARRAY_REPLACE", arr, existing, new))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_REVERSE reverses the first dimension of the array.
|
||||||
|
func ARRAY_REVERSE[E Expression](arr Array[E]) Array[E] {
|
||||||
|
return ArrayExp[E](Func("ARRAY_REVERSE", arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_SAMPLE returns an array of n items randomly selected from array.
|
||||||
|
// n may not exceed the length of array's first dimension.
|
||||||
|
// If array is multi-dimensional, an “item” is a slice having a given first subscript.
|
||||||
|
func ARRAY_SAMPLE[E Expression](arr Array[E], n IntegerExpression) Array[E] {
|
||||||
|
return ArrayExp[E](Func("ARRAY_SAMPLE", arr, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_SHUFFLE randomly shuffles the first dimension of the array.
|
||||||
|
func ARRAY_SHUFFLE[E Expression](arr Array[E]) Array[E] {
|
||||||
|
return ArrayExp[E](Func("ARRAY_SHUFFLE", arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_SORT sorts the first dimension of the array.
|
||||||
|
// The sort order is determined by the default sort ordering of the array's element type; however, if the element type is collatable, the collation to use can be specified by adding a COLLATE clause to the array argument.
|
||||||
|
//
|
||||||
|
// If descending is true then sort in descending order, otherwise ascending order.
|
||||||
|
// If omitted, the default is ascending order.
|
||||||
|
// If nulls_first is true then nulls appear before non-null values, otherwise nulls appear after non-null values.
|
||||||
|
// If omitted, nulls_first is taken to have the same value as descending.
|
||||||
|
func ARRAY_SORT[E Expression](arr Array[E], desc BoolExpression, nullFirst ...BoolExpression) Array[E] {
|
||||||
|
return ArrayExp[E](Func("ARRAY_SORT", optionalAppend([]Expression{arr, desc}, nullFirst)...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_TO_STRING Converts each array element to its text representation, and concatenates those separated by the delimiter string.
|
||||||
|
// If null_string is given and is not NULL, then NULL array entries are represented by that string; otherwise, they are omitted.
|
||||||
|
func ARRAY_TO_STRING[T Expression](arr Array[T], delim StringExpression) StringExpression {
|
||||||
|
return StringExp(Func("ARRAY_TO_STRING", arr, delim))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY_UPPER returns the upper bound of the requested array dimension.
|
||||||
|
func ARRAY_UPPER[E Expression](arr Array[E], dim IntegerExpression) IntegerExpression {
|
||||||
|
return IntExp(Func("ARRAY_UPPER", arr, dim))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CARDINALITY returns the total number of elements in the array, or 0 if the array is empty.
|
||||||
|
func CARDINALITY[E Expression](arr Array[E]) IntegerExpression {
|
||||||
|
return IntExp(Func("CARDINALITY", arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TRIM_ARRAY trims an array by removing the last n elements. If the array is multidimensional, only the first dimension is trimmed.
|
||||||
|
func TRIM_ARRAY[E Expression](arr Array[E], n IntegerExpression) Array[E] {
|
||||||
|
return ArrayExp[E](Func("TRIM_ARRAY", arr, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARRAY constructor is an expression that builds an array value using values for its member elements.
|
||||||
|
func ARRAY[T Expression](elems ...T) Array[T] {
|
||||||
|
return jet.ARRAY[T](elems...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func optionalAppend[O Expression](elem []Expression, optional []O) []Expression {
|
||||||
|
if len(optional) == 0 {
|
||||||
|
return elem
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(elem, optional[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------- Data Type Formatting Functions ----------------------//
|
||||||
|
|
||||||
// TO_CHAR converts expression to string with format
|
// TO_CHAR converts expression to string with format
|
||||||
var TO_CHAR = jet.TO_CHAR
|
var TO_CHAR = jet.TO_CHAR
|
||||||
|
|
|
||||||
|
|
@ -175,30 +175,27 @@ RETURNING table1.col1 AS "table1.col1",
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInsert_ON_CONFLICT_ON_CONSTRAINT(t *testing.T) {
|
func TestInsert_ON_CONFLICT_ON_CONSTRAINT(t *testing.T) {
|
||||||
stmt := table1.INSERT(table1Col1, table1ColBool, table1ColStringArray).
|
stmt := table1.INSERT(table1Col1, table1ColBool).
|
||||||
VALUES("one", "two", "three").
|
VALUES("one", "two").
|
||||||
VALUES("1", "2", "3").
|
VALUES("1", "2").
|
||||||
ON_CONFLICT().ON_CONSTRAINT("idk_primary_key").DO_UPDATE(
|
ON_CONFLICT().ON_CONSTRAINT("idk_primary_key").
|
||||||
SET(table1ColBool.SET(Bool(false)),
|
DO_UPDATE(
|
||||||
table2ColInt.SET(Int(1)),
|
SET(table1ColBool.SET(Bool(false)),
|
||||||
table1ColStringArray.SET(StringArray([]string{"one"})),
|
table2ColInt.SET(Int(1)),
|
||||||
ColumnList{table1Col1, table1ColBool, table1ColStringArray}.SET(jet.ROW(Int(2), String("two"), StringArray([]string{"two"}))),
|
ColumnList{table1Col1, table1ColBool}.SET(ROW(Int(2), String("two"))),
|
||||||
).WHERE(table1Col1.GT(Int(2))),
|
).WHERE(table1Col1.GT(Int(2)))).
|
||||||
).
|
RETURNING(table1Col1, table1ColBool)
|
||||||
RETURNING(table1Col1, table1ColBool, table1ColStringArray)
|
|
||||||
|
|
||||||
assertDebugStatementSql(t, stmt, `
|
assertDebugStatementSql(t, stmt, `
|
||||||
INSERT INTO db.table1 (col1, col_bool, col_string_array)
|
INSERT INTO db.table1 (col1, col_bool)
|
||||||
VALUES ('one', 'two', 'three'),
|
VALUES ('one', 'two'),
|
||||||
('1', '2', '3')
|
('1', '2')
|
||||||
ON CONFLICT ON CONSTRAINT idk_primary_key DO UPDATE
|
ON CONFLICT ON CONSTRAINT idk_primary_key DO UPDATE
|
||||||
SET col_bool = FALSE::boolean,
|
SET col_bool = FALSE::boolean,
|
||||||
col_int = 1,
|
col_int = 1,
|
||||||
col_string_array = '{"one"}',
|
(col1, col_bool) = ROW(2, 'two'::text)
|
||||||
(col1, col_bool, col_string_array) = ROW(2, 'two'::text, '{"two"}')
|
|
||||||
WHERE table1.col1 > 2
|
WHERE table1.col1 > 2
|
||||||
RETURNING table1.col1 AS "table1.col1",
|
RETURNING table1.col1 AS "table1.col1",
|
||||||
table1.col_bool AS "table1.col_bool",
|
table1.col_bool AS "table1.col_bool";
|
||||||
table1.col_string_array AS "table1.col_string_array";
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/lib/pq"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-jet/jet/v2/internal/jet"
|
"github.com/go-jet/jet/v2/internal/jet"
|
||||||
|
|
@ -11,11 +13,6 @@ func Bool(value bool) BoolExpression {
|
||||||
return CAST(jet.Bool(value)).AS_BOOL()
|
return CAST(jet.Bool(value)).AS_BOOL()
|
||||||
}
|
}
|
||||||
|
|
||||||
// BoolArray creates new bool array literal expression
|
|
||||||
func BoolArray(elements []bool) BoolArrayExpression {
|
|
||||||
return jet.BoolArray(elements)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int is constructor for 64 bit signed integer expressions literals.
|
// Int is constructor for 64 bit signed integer expressions literals.
|
||||||
var Int = jet.Int
|
var Int = jet.Int
|
||||||
|
|
||||||
|
|
@ -73,8 +70,11 @@ func Double(value float64) FloatExpression {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decimal creates new float literal expression
|
// 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
|
// String is a parameter constructor for the PostgreSQL text type. Using the `Text` constructor is
|
||||||
// generally preferable.
|
// generally preferable.
|
||||||
//
|
//
|
||||||
|
|
@ -85,11 +85,6 @@ func String(value string) StringExpression {
|
||||||
return CAST(jet.String(value)).AS_TEXT()
|
return CAST(jet.String(value)).AS_TEXT()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringArray creates new string array literal expression
|
|
||||||
func StringArray(elements []string) StringArrayExpression {
|
|
||||||
return jet.StringArray(elements)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text is a parameter constructor for the PostgreSQL text type. This constructor also adds an
|
// Text is a parameter constructor for the PostgreSQL text type. This constructor also adds an
|
||||||
// explicit placeholder type cast to text in the generated query, such as `$3::text`.
|
// explicit placeholder type cast to text in the generated query, such as `$3::text`.
|
||||||
// Example usage:
|
// Example usage:
|
||||||
|
|
@ -134,7 +129,9 @@ func Json(value interface{}) StringExpression {
|
||||||
|
|
||||||
// UUID is a helper function to create string literal expression from uuid object
|
// UUID is a helper function to create string literal expression from uuid object
|
||||||
// value can be any uuid type with a String method
|
// value can be any uuid type with a String method
|
||||||
var UUID = jet.UUID
|
func UUID(value fmt.Stringer) StringExpression {
|
||||||
|
return CAST(jet.Literal(value.String())).AS_UUID()
|
||||||
|
}
|
||||||
|
|
||||||
// Bytea creates new bytea literal expression
|
// Bytea creates new bytea literal expression
|
||||||
func Bytea(value interface{}) ByteaExpression {
|
func Bytea(value interface{}) ByteaExpression {
|
||||||
|
|
@ -195,3 +192,63 @@ func Timestampz(year int, month time.Month, day, hour, minute, second int, milli
|
||||||
func TimestampzT(t time.Time) TimestampzExpression {
|
func TimestampzT(t time.Time) TimestampzExpression {
|
||||||
return CAST(jet.TimestampzT(t)).AS_TIMESTAMPZ()
|
return CAST(jet.TimestampzT(t)).AS_TIMESTAMPZ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BoolArray creates new bool array literal expression from list of values
|
||||||
|
func BoolArray(values ...bool) Array[BoolExpression] {
|
||||||
|
return CAST(jet.Literal(pq.BoolArray(values))).AS_BOOL_ARRAY()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32Array creates new integer array literal expression from list of values
|
||||||
|
func Int32Array(values ...int32) Array[IntegerExpression] {
|
||||||
|
return CAST(jet.Literal(pq.Int32Array(values))).AS_INTEGER_ARRAY()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Array creates new bigint array literal expression from list of values
|
||||||
|
func Int64Array(values ...int64) Array[IntegerExpression] {
|
||||||
|
return CAST(jet.Literal(pq.Int64Array(values))).AS_BIGINT_ARRAY()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32Array creates new real array literal expression from list of values
|
||||||
|
func Float32Array(values ...float32) Array[FloatExpression] {
|
||||||
|
return CAST(jet.Literal(pq.Float32Array(values))).AS_REAL_ARRAY()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Array creates new double precision array literal expression from list of values
|
||||||
|
func Float64Array(values ...float64) Array[FloatExpression] {
|
||||||
|
return CAST(jet.Literal(pq.Float64Array(values))).AS_DOUBLE_ARRAY()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringArray creates new string array literal expression from list of values
|
||||||
|
func StringArray(values ...string) Array[StringExpression] {
|
||||||
|
return CAST(jet.Literal(pq.StringArray(values))).AS_TEXT_ARRAY()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteaArray creates new bytea array literal expression from list of values
|
||||||
|
func ByteaArray(values ...[]byte) Array[ByteaExpression] {
|
||||||
|
return CAST(jet.Literal(pq.ByteaArray(values))).AS_BYTEA_ARRAY()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateArray creates new date array literal expression from list of values
|
||||||
|
func DateArray(values ...time.Time) Array[DateExpression] {
|
||||||
|
return CAST(jet.Literal(pq.Array(values))).AS_DATE_ARRAY()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimestampArray creates new timestamp array literal expression from list of values
|
||||||
|
func TimestampArray(values ...time.Time) Array[TimestampExpression] {
|
||||||
|
return CAST(jet.Literal(pq.Array(values))).AS_TIMESTAMP_ARRAY()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimestampzArray creates new timestampt with timezone array literal expression from list of values
|
||||||
|
func TimestampzArray(values ...time.Time) Array[TimestampzExpression] {
|
||||||
|
return CAST(jet.Literal(pq.Array(values))).AS_TIMESTAMPZ_ARRAY()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeArray creates new time array literal expression from list of values
|
||||||
|
func TimeArray(values ...time.Time) Array[TimeExpression] {
|
||||||
|
return CAST(jet.Literal(pq.Array(values))).AS_TIME_ARRAY()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimezArray creates new time with timezone array literal expression from list of values
|
||||||
|
func TimezArray(values ...time.Time) Array[TimezExpression] {
|
||||||
|
return CAST(jet.Literal(pq.Array(values))).AS_TIMEZ_ARRAY()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"github.com/lib/pq"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
|
||||||
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/go-jet/jet/v2/qrm"
|
"github.com/go-jet/jet/v2/qrm"
|
||||||
|
"github.com/lib/pq"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -46,8 +44,7 @@ func TestAllTypesSelectJson(t *testing.T) {
|
||||||
AllTypesAllColumns.Except(
|
AllTypesAllColumns.Except(
|
||||||
AllTypes.JSON, AllTypes.JSONPtr,
|
AllTypes.JSON, AllTypes.JSONPtr,
|
||||||
AllTypes.Jsonb, AllTypes.JsonbPtr,
|
AllTypes.Jsonb, AllTypes.JsonbPtr,
|
||||||
AllTypes.TextArray, AllTypes.TextArrayPtr,
|
AllTypes.JsonbArray,
|
||||||
AllTypes.JsonbArray, AllTypes.IntegerArray, AllTypes.IntegerArrayPtr,
|
|
||||||
AllTypes.TextMultiDimArray, AllTypes.TextMultiDimArrayPtr,
|
AllTypes.TextMultiDimArray, AllTypes.TextMultiDimArrayPtr,
|
||||||
),
|
),
|
||||||
// unsupported at the moment, casting to text allows these columns to be assigned to string fields
|
// unsupported at the moment, casting to text allows these columns to be assigned to string fields
|
||||||
|
|
@ -55,11 +52,7 @@ func TestAllTypesSelectJson(t *testing.T) {
|
||||||
CAST(AllTypes.JSON).AS_TEXT().AS("JSON"),
|
CAST(AllTypes.JSON).AS_TEXT().AS("JSON"),
|
||||||
CAST(AllTypes.JsonbPtr).AS_TEXT().AS("jsonbPtr"),
|
CAST(AllTypes.JsonbPtr).AS_TEXT().AS("jsonbPtr"),
|
||||||
CAST(AllTypes.Jsonb).AS_TEXT().AS("Jsonb"),
|
CAST(AllTypes.Jsonb).AS_TEXT().AS("Jsonb"),
|
||||||
CAST(AllTypes.TextArrayPtr).AS_TEXT().AS("TextArrayPtr"),
|
CAST(AllTypes.JsonbArray).AS_TEXT_ARRAY().AS("JsonbArray"),
|
||||||
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.TextMultiDimArray).AS_TEXT().AS("TextMultiDimArray"),
|
CAST(AllTypes.TextMultiDimArray).AS_TEXT().AS("TextMultiDimArray"),
|
||||||
CAST(AllTypes.TextMultiDimArrayPtr).AS_TEXT().AS("TextMultiDimArrayPtr"),
|
CAST(AllTypes.TextMultiDimArrayPtr).AS_TEXT().AS("TextMultiDimArrayPtr"),
|
||||||
).FROM(AllTypes)
|
).FROM(AllTypes)
|
||||||
|
|
@ -117,17 +110,17 @@ FROM (
|
||||||
all_types.uuid AS "uuid",
|
all_types.uuid AS "uuid",
|
||||||
all_types.xml_ptr AS "xmlPtr",
|
all_types.xml_ptr AS "xmlPtr",
|
||||||
all_types.xml AS "xml",
|
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_ptr AS "moodPtr",
|
||||||
all_types.mood AS "mood",
|
all_types.mood AS "mood",
|
||||||
all_types.json_ptr::text AS "jsonPtr",
|
all_types.json_ptr::text AS "jsonPtr",
|
||||||
all_types.json::text AS "JSON",
|
all_types.json::text AS "JSON",
|
||||||
all_types.jsonb_ptr::text AS "jsonbPtr",
|
all_types.jsonb_ptr::text AS "jsonbPtr",
|
||||||
all_types.jsonb::text AS "Jsonb",
|
all_types.jsonb::text AS "Jsonb",
|
||||||
all_types.text_array_ptr::text AS "TextArrayPtr",
|
all_types.jsonb_array::text[] AS "JsonbArray",
|
||||||
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.text_multi_dim_array::text AS "TextMultiDimArray",
|
all_types.text_multi_dim_array::text AS "TextMultiDimArray",
|
||||||
all_types.text_multi_dim_array_ptr::text AS "TextMultiDimArrayPtr"
|
all_types.text_multi_dim_array_ptr::text AS "TextMultiDimArrayPtr"
|
||||||
FROM test_sample.all_types
|
FROM test_sample.all_types
|
||||||
|
|
@ -259,7 +252,7 @@ func TestUUIDType(t *testing.T) {
|
||||||
SELECT all_types.uuid AS "all_types.uuid",
|
SELECT all_types.uuid AS "all_types.uuid",
|
||||||
all_types.uuid_ptr AS "all_types.uuid_ptr"
|
all_types.uuid_ptr AS "all_types.uuid_ptr"
|
||||||
FROM test_sample.all_types
|
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")
|
`, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")
|
||||||
|
|
||||||
result := model.AllTypes{}
|
result := model.AllTypes{}
|
||||||
|
|
@ -875,10 +868,7 @@ FROM (
|
||||||
|
|
||||||
err := stmtJson.QueryContext(ctx, db, &destSelectJson)
|
err := stmtJson.QueryContext(ctx, db, &destSelectJson)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testutils.PrintJson(destSelectJson)
|
|
||||||
|
|
||||||
require.Equal(t, dest, destSelectJson)
|
require.Equal(t, dest, destSelectJson)
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1583,8 +1573,6 @@ func TestInterval(t *testing.T) {
|
||||||
AllTypes.IntervalPtr.DIV(Float(22.222)).EQ(AllTypes.IntervalPtr),
|
AllTypes.IntervalPtr.DIV(Float(22.222)).EQ(AllTypes.IntervalPtr),
|
||||||
).FROM(AllTypes)
|
).FROM(AllTypes)
|
||||||
|
|
||||||
fmt.Println(stmt.Sql())
|
|
||||||
|
|
||||||
testutils.AssertDebugStatementSql(t, stmt, `
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
SELECT INTERVAL '1 YEAR',
|
SELECT INTERVAL '1 YEAR',
|
||||||
INTERVAL '1 MONTH',
|
INTERVAL '1 MONTH',
|
||||||
|
|
@ -2250,7 +2238,7 @@ var allTypesRow0 = model.AllTypes{
|
||||||
TextArrayPtr: &pq.StringArray{"breakfast", "consulting"},
|
TextArrayPtr: &pq.StringArray{"breakfast", "consulting"},
|
||||||
TextArray: pq.StringArray{"breakfast", "consulting"},
|
TextArray: pq.StringArray{"breakfast", "consulting"},
|
||||||
JsonbArray: pq.StringArray{`{"a": 1, "b": 2}`, `{"a": 3, "b": 4}`},
|
JsonbArray: pq.StringArray{`{"a": 1, "b": 2}`, `{"a": 3, "b": 4}`},
|
||||||
TextMultiDimArrayPtr: testutils.StringPtr("{{meeting,lunch},{training,presentation}}"),
|
TextMultiDimArrayPtr: ptr.Of("{{meeting,lunch},{training,presentation}}"),
|
||||||
TextMultiDimArray: "{{meeting,lunch},{training,presentation}}",
|
TextMultiDimArray: "{{meeting,lunch},{training,presentation}}",
|
||||||
MoodPtr: &moodSad,
|
MoodPtr: &moodSad,
|
||||||
Mood: model.Mood_Happy,
|
Mood: model.Mood_Happy,
|
||||||
|
|
|
||||||
719
tests/postgres/array_test.go
Normal file
719
tests/postgres/array_test.go
Normal file
|
|
@ -0,0 +1,719 @@
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-jet/jet/v2/internal/testutils"
|
||||||
|
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
||||||
|
. "github.com/go-jet/jet/v2/postgres"
|
||||||
|
"github.com/go-jet/jet/v2/qrm"
|
||||||
|
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/enum"
|
||||||
|
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/model"
|
||||||
|
. "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/table"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArraySelect(t *testing.T) {
|
||||||
|
stmt := SELECT(
|
||||||
|
SampleArrays.AllColumns,
|
||||||
|
).FROM(
|
||||||
|
SampleArrays,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT sample_arrays.id AS "sample_arrays.id",
|
||||||
|
sample_arrays.bool_array AS "sample_arrays.bool_array",
|
||||||
|
sample_arrays.int2_array_ptr AS "sample_arrays.int2_array_ptr",
|
||||||
|
sample_arrays.int4_array AS "sample_arrays.int4_array",
|
||||||
|
sample_arrays.int8_array AS "sample_arrays.int8_array",
|
||||||
|
sample_arrays.numeric_array AS "sample_arrays.numeric_array",
|
||||||
|
sample_arrays.decimal_array AS "sample_arrays.decimal_array",
|
||||||
|
sample_arrays.real_array AS "sample_arrays.real_array",
|
||||||
|
sample_arrays.double_array AS "sample_arrays.double_array",
|
||||||
|
sample_arrays.text_array AS "sample_arrays.text_array",
|
||||||
|
sample_arrays.varchar_array AS "sample_arrays.varchar_array",
|
||||||
|
sample_arrays.char_array AS "sample_arrays.char_array",
|
||||||
|
sample_arrays.bytea_array AS "sample_arrays.bytea_array",
|
||||||
|
sample_arrays.date_array AS "sample_arrays.date_array",
|
||||||
|
sample_arrays.timestamp_array AS "sample_arrays.timestamp_array",
|
||||||
|
sample_arrays.timestamptz_array AS "sample_arrays.timestamptz_array",
|
||||||
|
sample_arrays.time_array AS "sample_arrays.time_array",
|
||||||
|
sample_arrays.timetz_array AS "sample_arrays.timetz_array",
|
||||||
|
sample_arrays.interval_array AS "sample_arrays.interval_array",
|
||||||
|
sample_arrays.uuid_array AS "sample_arrays.uuid_array",
|
||||||
|
sample_arrays.mood_enum_array AS "sample_arrays.mood_enum_array"
|
||||||
|
FROM test_sample.sample_arrays;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest []model.SampleArrays
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, dest, 1)
|
||||||
|
require.Equal(t, dest[0], sampleArrayRow0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayOperations(t *testing.T) {
|
||||||
|
|
||||||
|
boolArray := BoolArray(true, false, false, true)
|
||||||
|
int32Array := Int32Array(-1, 2, -3, 4, 5)
|
||||||
|
int64Array := Int64Array(10, -11, 12, -13, 14, 1515551)
|
||||||
|
float32Array := Float32Array(1.01, -2.02, 3.03, 4.04)
|
||||||
|
float64Array := Float64Array(10.001, 20.002, -3.003, 400050.04)
|
||||||
|
stringArray := StringArray("temp", "text", "array")
|
||||||
|
byteaArray := ByteaArray([]byte("temporal"), []byte("byte array"), []byte("array"))
|
||||||
|
dateArray := DateArray(*testutils.Date("2022-04-06"), *testutils.Date("2018-05-06"))
|
||||||
|
dateArray2 := ARRAY(Date(2022, 2, 3), Date(2023, 2, 2))
|
||||||
|
timestampArray := TimestampArray(*testutils.TimestampWithoutTimeZone("1999-01-08 04:05:06", 0))
|
||||||
|
timestampzArray := TimestampzArray(time.Date(2022, 1, 1, 8, 30, 50, 0, time.UTC))
|
||||||
|
timestampzArray2 := ARRAY(Timestampz(2023, 2, 3, 10, 29, 34, 1000, "UTC"))
|
||||||
|
timeArray := TimeArray(*testutils.TimeWithoutTimeZone("14:20:45"), *testutils.TimeWithoutTimeZone("8:10:15"))
|
||||||
|
timezArray := TimezArray(*testutils.TimeWithTimeZone("04:05:06 -0800"), *testutils.TimeWithTimeZone("01:02:03 -0600"))
|
||||||
|
timeArray2 := ARRAY(Time(10, 20, 30), Time(5, 45, 45))
|
||||||
|
timezArray2 := ARRAY(Timez(10, 20, 30, 0, "UTC"), Timez(5, 45, 45, 0, "UTC"))
|
||||||
|
|
||||||
|
timestampz := Timestampz(1950, 2, 3, 10, 30, 40, 0, "UTC")
|
||||||
|
|
||||||
|
query := SELECT(
|
||||||
|
// array constructors
|
||||||
|
boolArray.AS("bool_array"),
|
||||||
|
int32Array.AS("int32_array"),
|
||||||
|
int64Array.AS("int64_array"),
|
||||||
|
float32Array.AS("float32_array"),
|
||||||
|
float64Array.AS("float64_array"),
|
||||||
|
stringArray.AS("string_array"),
|
||||||
|
byteaArray.AS("bytea_array"),
|
||||||
|
dateArray.AS("date_array"),
|
||||||
|
timestampArray.AS("timestamp_array"),
|
||||||
|
timestampzArray.AS("timestampz_array"),
|
||||||
|
timeArray.AS("time_array"),
|
||||||
|
timezArray.AS("timez_array"),
|
||||||
|
|
||||||
|
// operators
|
||||||
|
SampleArrays.BoolArray.EQ(boolArray).AS("bool_eq"),
|
||||||
|
SampleArrays.TextArray.EQ(SampleArrays.TextArray).AS("text_eq"),
|
||||||
|
SampleArrays.TextArray.NOT_EQ(stringArray).AS("text_neq"),
|
||||||
|
SampleArrays.Int4Array.LT(int32Array).IS_TRUE().AS("int4_lt"),
|
||||||
|
SampleArrays.Int8Array.LT_EQ(int64Array).IS_FALSE().AS("int8_lteq"),
|
||||||
|
SampleArrays.RealArray.GT(float32Array).AS("decimal_gt"),
|
||||||
|
SampleArrays.DoubleArray.GT_EQ(float64Array).AS("numeric_gt_eq"),
|
||||||
|
SampleArrays.ByteaArray.CONTAINS(byteaArray).AS("bytea_contains"),
|
||||||
|
byteaArray.IS_CONTAINED_BY(SampleArrays.ByteaArray).AS("bytea_contained_by"),
|
||||||
|
SampleArrays.DateArray.OVERLAP(dateArray2).AS("date_overlaps"),
|
||||||
|
SampleArrays.TimestampArray.CONCAT(timestampArray).AS("timestamp_concat"),
|
||||||
|
timestampzArray2.CONCAT_ELEMENT(timestampz).AS("timestampz_concat_elem"),
|
||||||
|
SampleArrays.TimeArray.AT(Int32(1)).AS("time_at"),
|
||||||
|
|
||||||
|
Int32(22).EQ(ANY(SampleArrays.Int4Array)).AS("int32_eq_any"),
|
||||||
|
Double(7.89).NOT_EQ(ANY(SampleArrays.DoubleArray)).AS("double_neq_any"),
|
||||||
|
String("temp").EQ(ALL(stringArray)).AS("string_eq_all"),
|
||||||
|
|
||||||
|
// functions
|
||||||
|
ARRAY_APPEND(SampleArrays.TextArray, String("after")).AS("append"),
|
||||||
|
ARRAY_CAT(SampleArrays.TimeArray, timeArray2).AS("cat"),
|
||||||
|
ARRAY_LENGTH(timezArray2, Int32(1)).AS("length"),
|
||||||
|
// ARRAY_LOWER(SampleArrays.UUIDArray).AS("lower"), //newer postgres versions
|
||||||
|
ARRAY_POSITIONS(stringArray, String("text")).AS("position"),
|
||||||
|
ARRAY_PREPEND(String("before"), SampleArrays.TextArray).AS("prepend"),
|
||||||
|
ARRAY_REMOVE(boolArray, Bool(true)).AS("remove"),
|
||||||
|
ARRAY_REPLACE(SampleArrays.VarcharArray, String("hello"), String("hi")).AS("replace"),
|
||||||
|
//ARRAY_REVERSE(SampleArrays.TextArray).AS("reverse"),
|
||||||
|
//ARRAY_SAMPLE(SampleArrays.TimestampArray, Int(2)).AS("sample"),
|
||||||
|
//ARRAY_SHUFFLE(SampleArrays.BoolArray).AS("shuffle"),
|
||||||
|
//ARRAY_SORT(SampleArrays.Int8Array, Bool(true)),
|
||||||
|
ARRAY_TO_STRING(SampleArrays.MoodEnumArray, String(", ")).AS("to_string"),
|
||||||
|
ARRAY_UPPER(SampleArrays.Int8Array, Int(1)).AS("upper"),
|
||||||
|
CARDINALITY(SampleArrays.DoubleArray).AS("cardinality"),
|
||||||
|
|
||||||
|
// unsupported by cockroachdb
|
||||||
|
|
||||||
|
//ARRAY_DIMS(SampleArrays.IntervalArray).AS("dims"),
|
||||||
|
//ARRAY_NDIMS(SampleArrays.TextArray).AS("ndims"),
|
||||||
|
//TRIM_ARRAY(SampleArrays.DateArray, Int(1)).AS("trim"),
|
||||||
|
|
||||||
|
).FROM(
|
||||||
|
SampleArrays,
|
||||||
|
).WHERE(
|
||||||
|
SampleArrays.BoolArray.CONTAINS(BoolArray(true)),
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, query, `
|
||||||
|
SELECT $1::boolean[] AS "bool_array",
|
||||||
|
$2::integer[] AS "int32_array",
|
||||||
|
$3::bigint[] AS "int64_array",
|
||||||
|
$4::real[] AS "float32_array",
|
||||||
|
$5::double precision[] AS "float64_array",
|
||||||
|
$6::text[] AS "string_array",
|
||||||
|
$7::bytea[] AS "bytea_array",
|
||||||
|
$8::date[] AS "date_array",
|
||||||
|
$9::timestamp without time zone[] AS "timestamp_array",
|
||||||
|
$10::timestamp with time zone[] AS "timestampz_array",
|
||||||
|
$11::time without time zone[] AS "time_array",
|
||||||
|
$12::time with time zone[] AS "timez_array",
|
||||||
|
(sample_arrays.bool_array = $13::boolean[]) AS "bool_eq",
|
||||||
|
(sample_arrays.text_array = sample_arrays.text_array) AS "text_eq",
|
||||||
|
(sample_arrays.text_array != $14::text[]) AS "text_neq",
|
||||||
|
(sample_arrays.int4_array < $15::integer[]) IS TRUE AS "int4_lt",
|
||||||
|
(sample_arrays.int8_array <= $16::bigint[]) IS FALSE AS "int8_lteq",
|
||||||
|
(sample_arrays.real_array > $17::real[]) AS "decimal_gt",
|
||||||
|
(sample_arrays.double_array >= $18::double precision[]) AS "numeric_gt_eq",
|
||||||
|
(sample_arrays.bytea_array @> $19::bytea[]) AS "bytea_contains",
|
||||||
|
($20::bytea[] <@ sample_arrays.bytea_array) AS "bytea_contained_by",
|
||||||
|
(sample_arrays.date_array && ARRAY[$21::date,$22::date]) AS "date_overlaps",
|
||||||
|
(sample_arrays.timestamp_array || $23::timestamp without time zone[]) AS "timestamp_concat",
|
||||||
|
(ARRAY[$24::timestamp with time zone] || $25::timestamp with time zone) AS "timestampz_concat_elem",
|
||||||
|
sample_arrays.time_array[$26::integer] AS "time_at",
|
||||||
|
($27::integer = ANY(sample_arrays.int4_array)) AS "int32_eq_any",
|
||||||
|
($28::double precision != ANY(sample_arrays.double_array)) AS "double_neq_any",
|
||||||
|
($29::text = ALL($30::text[])) AS "string_eq_all",
|
||||||
|
ARRAY_APPEND(sample_arrays.text_array, $31::text) AS "append",
|
||||||
|
ARRAY_CAT(sample_arrays.time_array, ARRAY[$32::time without time zone,$33::time without time zone]) AS "cat",
|
||||||
|
ARRAY_LENGTH(ARRAY[$34::time with time zone,$35::time with time zone], $36::integer) AS "length",
|
||||||
|
ARRAY_POSITIONS($37::text[], $38::text) AS "position",
|
||||||
|
ARRAY_PREPEND($39::text, sample_arrays.text_array) AS "prepend",
|
||||||
|
ARRAY_REMOVE($40::boolean[], $41::boolean) AS "remove",
|
||||||
|
ARRAY_REPLACE(sample_arrays.varchar_array, $42::text, $43::text) AS "replace",
|
||||||
|
ARRAY_TO_STRING(sample_arrays.mood_enum_array, $44::text) AS "to_string",
|
||||||
|
ARRAY_UPPER(sample_arrays.int8_array, $45) AS "upper",
|
||||||
|
CARDINALITY(sample_arrays.double_array) AS "cardinality"
|
||||||
|
FROM test_sample.sample_arrays
|
||||||
|
WHERE sample_arrays.bool_array @> $46::boolean[];
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest struct {
|
||||||
|
// array constructors
|
||||||
|
BoolArray pq.BoolArray
|
||||||
|
Int32Array pq.Int32Array
|
||||||
|
Int64Array pq.Int64Array
|
||||||
|
Float32Array pq.Float32Array
|
||||||
|
Float64Array pq.Float64Array
|
||||||
|
StringArray pq.StringArray
|
||||||
|
ByteaArray pq.ByteaArray
|
||||||
|
DateArray pq.StringArray
|
||||||
|
TimestampArray pq.StringArray
|
||||||
|
TimestampzArray pq.StringArray
|
||||||
|
TimeArray pq.StringArray
|
||||||
|
TimezArray pq.StringArray
|
||||||
|
|
||||||
|
// array operators
|
||||||
|
TextEq bool
|
||||||
|
BoolEq bool
|
||||||
|
TextNeq bool
|
||||||
|
Int4Lt bool
|
||||||
|
Int8Lteq bool
|
||||||
|
DecimalGt bool
|
||||||
|
NumericGtEq bool
|
||||||
|
ByteaContains bool
|
||||||
|
ByteaContainedBy bool
|
||||||
|
DateOverlaps bool
|
||||||
|
TimestampConcat pq.StringArray
|
||||||
|
TimestampzConcatElem pq.StringArray
|
||||||
|
TimeAt time.Time
|
||||||
|
|
||||||
|
Int32EqAny bool
|
||||||
|
DoubleNeqAny bool
|
||||||
|
StringEqAll bool
|
||||||
|
|
||||||
|
// functions
|
||||||
|
Append pq.StringArray
|
||||||
|
Cat pq.StringArray
|
||||||
|
Dims string
|
||||||
|
Length int32
|
||||||
|
Lower int32
|
||||||
|
NDims int32
|
||||||
|
Position pq.Int32Array
|
||||||
|
Prepend pq.StringArray
|
||||||
|
Remove pq.BoolArray
|
||||||
|
Replace pq.StringArray
|
||||||
|
ToString string
|
||||||
|
Upper int32
|
||||||
|
Cardinality int32
|
||||||
|
Trim pq.StringArray
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Query(db, &dest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testutils.AssertJSON(t, dest, `
|
||||||
|
{
|
||||||
|
"BoolArray": [
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"Int32Array": [
|
||||||
|
-1,
|
||||||
|
2,
|
||||||
|
-3,
|
||||||
|
4,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"Int64Array": [
|
||||||
|
10,
|
||||||
|
-11,
|
||||||
|
12,
|
||||||
|
-13,
|
||||||
|
14,
|
||||||
|
1515551
|
||||||
|
],
|
||||||
|
"Float32Array": [
|
||||||
|
1.01,
|
||||||
|
-2.02,
|
||||||
|
3.03,
|
||||||
|
4.04
|
||||||
|
],
|
||||||
|
"Float64Array": [
|
||||||
|
10.001,
|
||||||
|
20.002,
|
||||||
|
-3.003,
|
||||||
|
400050.04
|
||||||
|
],
|
||||||
|
"StringArray": [
|
||||||
|
"temp",
|
||||||
|
"text",
|
||||||
|
"array"
|
||||||
|
],
|
||||||
|
"ByteaArray": [
|
||||||
|
"dGVtcG9yYWw=",
|
||||||
|
"Ynl0ZSBhcnJheQ==",
|
||||||
|
"YXJyYXk="
|
||||||
|
],
|
||||||
|
"DateArray": [
|
||||||
|
"2022-04-06",
|
||||||
|
"2018-05-06"
|
||||||
|
],
|
||||||
|
"TimestampArray": [
|
||||||
|
"1999-01-08 04:05:06"
|
||||||
|
],
|
||||||
|
"TimestampzArray": [
|
||||||
|
"2022-01-01 08:30:50+00"
|
||||||
|
],
|
||||||
|
"TimeArray": [
|
||||||
|
"14:20:45",
|
||||||
|
"08:10:15"
|
||||||
|
],
|
||||||
|
"TimezArray": [
|
||||||
|
"04:05:06-08",
|
||||||
|
"01:02:03-06"
|
||||||
|
],
|
||||||
|
"TextEq": true,
|
||||||
|
"BoolEq": false,
|
||||||
|
"TextNeq": true,
|
||||||
|
"Int4Lt": false,
|
||||||
|
"Int8Lteq": true,
|
||||||
|
"DecimalGt": true,
|
||||||
|
"NumericGtEq": true,
|
||||||
|
"ByteaContains": false,
|
||||||
|
"ByteaContainedBy": false,
|
||||||
|
"DateOverlaps": false,
|
||||||
|
"TimestampConcat": [
|
||||||
|
"2025-01-01 10:00:00",
|
||||||
|
"2025-02-01 10:00:00",
|
||||||
|
"1999-01-08 04:05:06"
|
||||||
|
],
|
||||||
|
"TimestampzConcatElem": [
|
||||||
|
"2023-02-03 10:29:34.000001+00",
|
||||||
|
"1950-02-03 10:30:40+00"
|
||||||
|
],
|
||||||
|
"TimeAt": "0000-01-01T12:00:00Z",
|
||||||
|
"Int32EqAny": false,
|
||||||
|
"DoubleNeqAny": true,
|
||||||
|
"StringEqAll": false,
|
||||||
|
"Append": [
|
||||||
|
"alpha",
|
||||||
|
"beta",
|
||||||
|
"gama",
|
||||||
|
"after"
|
||||||
|
],
|
||||||
|
"Cat": [
|
||||||
|
"12:00:00",
|
||||||
|
"13:00:00",
|
||||||
|
"10:20:30",
|
||||||
|
"05:45:45"
|
||||||
|
],
|
||||||
|
"Dims": "",
|
||||||
|
"Length": 2,
|
||||||
|
"Lower": 0,
|
||||||
|
"NDims": 0,
|
||||||
|
"Position": [
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"Prepend": [
|
||||||
|
"before",
|
||||||
|
"alpha",
|
||||||
|
"beta",
|
||||||
|
"gama"
|
||||||
|
],
|
||||||
|
"Remove": [
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"Replace": [
|
||||||
|
"hi",
|
||||||
|
"world"
|
||||||
|
],
|
||||||
|
"ToString": "happy, ok",
|
||||||
|
"Upper": 4,
|
||||||
|
"Cardinality": 3,
|
||||||
|
"Trim": null
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
requireLogged(t, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArraySelectColumnsFromSubQuery(t *testing.T) {
|
||||||
|
|
||||||
|
subQuery := SELECT(
|
||||||
|
SampleArrays.AllColumns,
|
||||||
|
Int64Array(10, -11, 12, -13, 14, 1515551).AS("int64_array"),
|
||||||
|
).FROM(
|
||||||
|
SampleArrays,
|
||||||
|
).AsTable("sub_query")
|
||||||
|
|
||||||
|
int64Array := IntegerArrayColumn("int64_array").From(subQuery)
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
subQuery.AllColumns().Except(int64Array),
|
||||||
|
int64Array,
|
||||||
|
).FROM(
|
||||||
|
subQuery,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT sub_query."sample_arrays.id" AS "sample_arrays.id",
|
||||||
|
sub_query."sample_arrays.bool_array" AS "sample_arrays.bool_array",
|
||||||
|
sub_query."sample_arrays.int2_array_ptr" AS "sample_arrays.int2_array_ptr",
|
||||||
|
sub_query."sample_arrays.int4_array" AS "sample_arrays.int4_array",
|
||||||
|
sub_query."sample_arrays.int8_array" AS "sample_arrays.int8_array",
|
||||||
|
sub_query."sample_arrays.numeric_array" AS "sample_arrays.numeric_array",
|
||||||
|
sub_query."sample_arrays.decimal_array" AS "sample_arrays.decimal_array",
|
||||||
|
sub_query."sample_arrays.real_array" AS "sample_arrays.real_array",
|
||||||
|
sub_query."sample_arrays.double_array" AS "sample_arrays.double_array",
|
||||||
|
sub_query."sample_arrays.text_array" AS "sample_arrays.text_array",
|
||||||
|
sub_query."sample_arrays.varchar_array" AS "sample_arrays.varchar_array",
|
||||||
|
sub_query."sample_arrays.char_array" AS "sample_arrays.char_array",
|
||||||
|
sub_query."sample_arrays.bytea_array" AS "sample_arrays.bytea_array",
|
||||||
|
sub_query."sample_arrays.date_array" AS "sample_arrays.date_array",
|
||||||
|
sub_query."sample_arrays.timestamp_array" AS "sample_arrays.timestamp_array",
|
||||||
|
sub_query."sample_arrays.timestamptz_array" AS "sample_arrays.timestamptz_array",
|
||||||
|
sub_query."sample_arrays.time_array" AS "sample_arrays.time_array",
|
||||||
|
sub_query."sample_arrays.timetz_array" AS "sample_arrays.timetz_array",
|
||||||
|
sub_query."sample_arrays.interval_array" AS "sample_arrays.interval_array",
|
||||||
|
sub_query."sample_arrays.uuid_array" AS "sample_arrays.uuid_array",
|
||||||
|
sub_query."sample_arrays.mood_enum_array" AS "sample_arrays.mood_enum_array",
|
||||||
|
sub_query.int64_array AS "int64_array"
|
||||||
|
FROM (
|
||||||
|
SELECT sample_arrays.id AS "sample_arrays.id",
|
||||||
|
sample_arrays.bool_array AS "sample_arrays.bool_array",
|
||||||
|
sample_arrays.int2_array_ptr AS "sample_arrays.int2_array_ptr",
|
||||||
|
sample_arrays.int4_array AS "sample_arrays.int4_array",
|
||||||
|
sample_arrays.int8_array AS "sample_arrays.int8_array",
|
||||||
|
sample_arrays.numeric_array AS "sample_arrays.numeric_array",
|
||||||
|
sample_arrays.decimal_array AS "sample_arrays.decimal_array",
|
||||||
|
sample_arrays.real_array AS "sample_arrays.real_array",
|
||||||
|
sample_arrays.double_array AS "sample_arrays.double_array",
|
||||||
|
sample_arrays.text_array AS "sample_arrays.text_array",
|
||||||
|
sample_arrays.varchar_array AS "sample_arrays.varchar_array",
|
||||||
|
sample_arrays.char_array AS "sample_arrays.char_array",
|
||||||
|
sample_arrays.bytea_array AS "sample_arrays.bytea_array",
|
||||||
|
sample_arrays.date_array AS "sample_arrays.date_array",
|
||||||
|
sample_arrays.timestamp_array AS "sample_arrays.timestamp_array",
|
||||||
|
sample_arrays.timestamptz_array AS "sample_arrays.timestamptz_array",
|
||||||
|
sample_arrays.time_array AS "sample_arrays.time_array",
|
||||||
|
sample_arrays.timetz_array AS "sample_arrays.timetz_array",
|
||||||
|
sample_arrays.interval_array AS "sample_arrays.interval_array",
|
||||||
|
sample_arrays.uuid_array AS "sample_arrays.uuid_array",
|
||||||
|
sample_arrays.mood_enum_array AS "sample_arrays.mood_enum_array",
|
||||||
|
'{10,-11,12,-13,14,1515551}'::bigint[] AS "int64_array"
|
||||||
|
FROM test_sample.sample_arrays
|
||||||
|
) AS sub_query;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest struct {
|
||||||
|
model.SampleArrays
|
||||||
|
|
||||||
|
Int64Array pq.Int64Array
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.AssertDeepEqual(t, dest.SampleArrays, sampleArrayRow0)
|
||||||
|
testutils.AssertDeepEqual(t, dest.Int64Array, pq.Int64Array{10, -11, 12, -13, 14, 1515551})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayTableInsert(t *testing.T) {
|
||||||
|
|
||||||
|
sampleArrayRow3 := testutils.DeepCopy(t, sampleArrayRow0)
|
||||||
|
sampleArrayRow3.ID = 3
|
||||||
|
|
||||||
|
insertQuery := SampleArrays.INSERT(SampleArrays.AllColumns).
|
||||||
|
VALUES(
|
||||||
|
Int64(2),
|
||||||
|
ARRAY(Bool(false), Bool(false), Bool(true)),
|
||||||
|
ARRAY(Int16(4), Int16(5), Int(6)),
|
||||||
|
Int32Array(40, 50, 60),
|
||||||
|
Int64Array(400, 500, 600),
|
||||||
|
ARRAY(Decimal("1.11"), Decimal("2.22")),
|
||||||
|
ARRAY(Decimal("1.11"), Decimal("2.22")),
|
||||||
|
Float32Array(4.4, 5.5, 6.6, 7.7),
|
||||||
|
Float64Array(40.04, 50.05, 60.06, 70.07),
|
||||||
|
StringArray("john", "doe"),
|
||||||
|
ARRAY(VarChar(10)("Andy"), VarChar(10)("Bob")),
|
||||||
|
ARRAY(Char(1)("q"), Char(1)("w"), Char(1)("e")),
|
||||||
|
ByteaArray([]byte("title"), []byte("name")),
|
||||||
|
ARRAY(Date(2010, 2, 3), Date(2025, 4, 5)),
|
||||||
|
ARRAY(Timestamp(2025, 2, 3, 0, 10, 20, 0)),
|
||||||
|
ARRAY(Timestampz(2025, 2, 3, 0, 10, 20, 0, "UTC")),
|
||||||
|
ARRAY(Time(12, 15, 45), Time(2, 30, 40)),
|
||||||
|
ARRAY(Timez(12, 15, 45, 0, "UTC"), Timez(2, 30, 40, 0, "UTC")),
|
||||||
|
ARRAY(INTERVAL(1, DAY, 3, MINUTE), INTERVAL(2, YEAR, 30, DAY)),
|
||||||
|
ARRAY(UUID(uuid.MustParse("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"))),
|
||||||
|
CAST(ARRAY(enum.Mood.Happy, enum.Mood.Sad)).AS("test_sample.mood[]"),
|
||||||
|
).
|
||||||
|
MODEL(
|
||||||
|
sampleArrayRow3,
|
||||||
|
).
|
||||||
|
RETURNING(SampleArrays.AllColumns)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, insertQuery, `
|
||||||
|
INSERT INTO test_sample.sample_arrays (id, bool_array, int2_array_ptr, int4_array, int8_array, numeric_array, decimal_array, real_array, double_array, text_array, varchar_array, char_array, bytea_array, date_array, timestamp_array, timestamptz_array, time_array, timetz_array, interval_array, uuid_array, mood_enum_array)
|
||||||
|
VALUES ($1::bigint, ARRAY[$2::boolean,$3::boolean,$4::boolean], ARRAY[$5::smallint,$6::smallint,$7], $8::integer[], $9::bigint[], ARRAY[$10::decimal,$11::decimal], ARRAY[$12::decimal,$13::decimal], $14::real[], $15::double precision[], $16::text[], ARRAY[$17::varchar(10),$18::varchar(10)], ARRAY[$19::char(1),$20::char(1),$21::char(1)], $22::bytea[], ARRAY[$23::date,$24::date], ARRAY[$25::timestamp without time zone], ARRAY[$26::timestamp with time zone], ARRAY[$27::time without time zone,$28::time without time zone], ARRAY[$29::time with time zone,$30::time with time zone], ARRAY[INTERVAL '1 DAY 3 MINUTE',INTERVAL '2 YEAR 30 DAY'], ARRAY[$31::uuid], ARRAY['happy','sad']::test_sample.mood[]),
|
||||||
|
($32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $50, $51, $52)
|
||||||
|
RETURNING sample_arrays.id AS "sample_arrays.id",
|
||||||
|
sample_arrays.bool_array AS "sample_arrays.bool_array",
|
||||||
|
sample_arrays.int2_array_ptr AS "sample_arrays.int2_array_ptr",
|
||||||
|
sample_arrays.int4_array AS "sample_arrays.int4_array",
|
||||||
|
sample_arrays.int8_array AS "sample_arrays.int8_array",
|
||||||
|
sample_arrays.numeric_array AS "sample_arrays.numeric_array",
|
||||||
|
sample_arrays.decimal_array AS "sample_arrays.decimal_array",
|
||||||
|
sample_arrays.real_array AS "sample_arrays.real_array",
|
||||||
|
sample_arrays.double_array AS "sample_arrays.double_array",
|
||||||
|
sample_arrays.text_array AS "sample_arrays.text_array",
|
||||||
|
sample_arrays.varchar_array AS "sample_arrays.varchar_array",
|
||||||
|
sample_arrays.char_array AS "sample_arrays.char_array",
|
||||||
|
sample_arrays.bytea_array AS "sample_arrays.bytea_array",
|
||||||
|
sample_arrays.date_array AS "sample_arrays.date_array",
|
||||||
|
sample_arrays.timestamp_array AS "sample_arrays.timestamp_array",
|
||||||
|
sample_arrays.timestamptz_array AS "sample_arrays.timestamptz_array",
|
||||||
|
sample_arrays.time_array AS "sample_arrays.time_array",
|
||||||
|
sample_arrays.timetz_array AS "sample_arrays.timetz_array",
|
||||||
|
sample_arrays.interval_array AS "sample_arrays.interval_array",
|
||||||
|
sample_arrays.uuid_array AS "sample_arrays.uuid_array",
|
||||||
|
sample_arrays.mood_enum_array AS "sample_arrays.mood_enum_array";
|
||||||
|
`)
|
||||||
|
|
||||||
|
testutils.ExecuteInTxAndRollback(t, db, func(tx qrm.DB) {
|
||||||
|
var dest []model.SampleArrays
|
||||||
|
err := insertQuery.Query(tx, &dest)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, dest, 2)
|
||||||
|
|
||||||
|
testutils.AssertDeepEqual(t, sampleArrayRow3, dest[1])
|
||||||
|
testutils.AssertJSON(t, dest[0], `
|
||||||
|
{
|
||||||
|
"ID": 2,
|
||||||
|
"BoolArray": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"Int2ArrayPtr": [
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
"6"
|
||||||
|
],
|
||||||
|
"Int4Array": [
|
||||||
|
40,
|
||||||
|
50,
|
||||||
|
60
|
||||||
|
],
|
||||||
|
"Int8Array": [
|
||||||
|
400,
|
||||||
|
500,
|
||||||
|
600
|
||||||
|
],
|
||||||
|
"NumericArray": [
|
||||||
|
1.11,
|
||||||
|
2.22
|
||||||
|
],
|
||||||
|
"DecimalArray": [
|
||||||
|
1.11,
|
||||||
|
2.22
|
||||||
|
],
|
||||||
|
"RealArray": [
|
||||||
|
4.4,
|
||||||
|
5.5,
|
||||||
|
6.6,
|
||||||
|
7.7
|
||||||
|
],
|
||||||
|
"DoubleArray": [
|
||||||
|
40.04,
|
||||||
|
50.05,
|
||||||
|
60.06,
|
||||||
|
70.07
|
||||||
|
],
|
||||||
|
"TextArray": [
|
||||||
|
"john",
|
||||||
|
"doe"
|
||||||
|
],
|
||||||
|
"VarcharArray": [
|
||||||
|
"Andy",
|
||||||
|
"Bob"
|
||||||
|
],
|
||||||
|
"CharArray": [
|
||||||
|
"q",
|
||||||
|
"w",
|
||||||
|
"e"
|
||||||
|
],
|
||||||
|
"ByteaArray": [
|
||||||
|
"dGl0bGU=",
|
||||||
|
"bmFtZQ=="
|
||||||
|
],
|
||||||
|
"DateArray": [
|
||||||
|
"2010-02-03",
|
||||||
|
"2025-04-05"
|
||||||
|
],
|
||||||
|
"TimestampArray": [
|
||||||
|
"2025-02-03 00:10:20"
|
||||||
|
],
|
||||||
|
"TimestamptzArray": [
|
||||||
|
"2025-02-03 00:10:20+00"
|
||||||
|
],
|
||||||
|
"TimeArray": [
|
||||||
|
"12:15:45",
|
||||||
|
"02:30:40"
|
||||||
|
],
|
||||||
|
"TimetzArray": [
|
||||||
|
"12:15:45+00",
|
||||||
|
"02:30:40+00"
|
||||||
|
],
|
||||||
|
"IntervalArray": [
|
||||||
|
"1 day 00:03:00",
|
||||||
|
"2 years 30 days"
|
||||||
|
],
|
||||||
|
"UUIDArray": [
|
||||||
|
"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
|
||||||
|
],
|
||||||
|
"MoodEnumArray": [
|
||||||
|
"happy",
|
||||||
|
"sad"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayTableUpdate(t *testing.T) {
|
||||||
|
|
||||||
|
t.Run("using model", func(t *testing.T) {
|
||||||
|
|
||||||
|
sampleArrayRow1Clone := testutils.DeepCopy(t, sampleArrayRow0)
|
||||||
|
sampleArrayRow1Clone.RealArray = pq.Float32Array{100.11, 200.22, 300.33}
|
||||||
|
|
||||||
|
stmt := SampleArrays.UPDATE(SampleArrays.MutableColumns).
|
||||||
|
MODEL(sampleArrayRow1Clone).
|
||||||
|
WHERE(String("alpha").EQ(ANY(SampleArrays.TextArray))).
|
||||||
|
RETURNING(SampleArrays.AllColumns)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
UPDATE test_sample.sample_arrays
|
||||||
|
SET (bool_array, int2_array_ptr, int4_array, int8_array, numeric_array, decimal_array, real_array, double_array, text_array, varchar_array, char_array, bytea_array, date_array, timestamp_array, timestamptz_array, time_array, timetz_array, interval_array, uuid_array, mood_enum_array) = ('{t,f,t}', '{"1","2","3","4"}', '{10,20,30,40}', '{100,200,300,400}', '{1.8881,2.8882,3.8883,4.8884}', '{1.0001,2.0002,3.0003,4.0004}', '{100.11,200.22,300.33}', '{11.11,22.22,33.33}', '{"alpha","beta","gama"}', '{"hello","world"}', '{"a","b","c"}', '{"\\x01020304","\\x11223344"}', '{"2024-11-01","2025-02-28"}', '{"2025-01-01 10:00:00","2025-02-01 10:00:00"}', '{"2025-01-01 09:00:00+00","2025-02-01 09:00:00+00"}', '{"12:00:00","13:00:00"}', '{"12:00:00+01","13:00:00+02"}', '{"1 day","02:00:00"}', '{"550e8400-e29b-41d4-a716-446655440000"}', '{"happy","ok"}')
|
||||||
|
WHERE 'alpha'::text = ANY(sample_arrays.text_array)
|
||||||
|
RETURNING sample_arrays.id AS "sample_arrays.id",
|
||||||
|
sample_arrays.bool_array AS "sample_arrays.bool_array",
|
||||||
|
sample_arrays.int2_array_ptr AS "sample_arrays.int2_array_ptr",
|
||||||
|
sample_arrays.int4_array AS "sample_arrays.int4_array",
|
||||||
|
sample_arrays.int8_array AS "sample_arrays.int8_array",
|
||||||
|
sample_arrays.numeric_array AS "sample_arrays.numeric_array",
|
||||||
|
sample_arrays.decimal_array AS "sample_arrays.decimal_array",
|
||||||
|
sample_arrays.real_array AS "sample_arrays.real_array",
|
||||||
|
sample_arrays.double_array AS "sample_arrays.double_array",
|
||||||
|
sample_arrays.text_array AS "sample_arrays.text_array",
|
||||||
|
sample_arrays.varchar_array AS "sample_arrays.varchar_array",
|
||||||
|
sample_arrays.char_array AS "sample_arrays.char_array",
|
||||||
|
sample_arrays.bytea_array AS "sample_arrays.bytea_array",
|
||||||
|
sample_arrays.date_array AS "sample_arrays.date_array",
|
||||||
|
sample_arrays.timestamp_array AS "sample_arrays.timestamp_array",
|
||||||
|
sample_arrays.timestamptz_array AS "sample_arrays.timestamptz_array",
|
||||||
|
sample_arrays.time_array AS "sample_arrays.time_array",
|
||||||
|
sample_arrays.timetz_array AS "sample_arrays.timetz_array",
|
||||||
|
sample_arrays.interval_array AS "sample_arrays.interval_array",
|
||||||
|
sample_arrays.uuid_array AS "sample_arrays.uuid_array",
|
||||||
|
sample_arrays.mood_enum_array AS "sample_arrays.mood_enum_array";
|
||||||
|
`)
|
||||||
|
|
||||||
|
testutils.ExecuteInTxAndRollback(t, db, func(tx qrm.DB) {
|
||||||
|
var dest []model.SampleArrays
|
||||||
|
|
||||||
|
err := stmt.Query(tx, &dest)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, dest, 1)
|
||||||
|
testutils.AssertDeepEqual(t, sampleArrayRow1Clone, dest[0])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("update using SET", func(t *testing.T) {
|
||||||
|
stmt := SampleArrays.UPDATE().
|
||||||
|
SET(
|
||||||
|
SampleArrays.Int4Array.SET(ARRAY(Int32(-10), Int32(11))),
|
||||||
|
SampleArrays.Int8Array.SET(ARRAY(Int64(-1200), Int64(7800))),
|
||||||
|
).
|
||||||
|
WHERE(String("alpha").EQ(ANY(SampleArrays.TextArray))).
|
||||||
|
RETURNING(
|
||||||
|
SampleArrays.Int4Array,
|
||||||
|
SampleArrays.Int8Array,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
|
UPDATE test_sample.sample_arrays
|
||||||
|
SET int4_array = ARRAY[$1::integer,$2::integer],
|
||||||
|
int8_array = ARRAY[$3::bigint,$4::bigint]
|
||||||
|
WHERE $5::text = ANY(sample_arrays.text_array)
|
||||||
|
RETURNING sample_arrays.int4_array AS "sample_arrays.int4_array",
|
||||||
|
sample_arrays.int8_array AS "sample_arrays.int8_array";
|
||||||
|
`)
|
||||||
|
|
||||||
|
testutils.ExecuteInTxAndRollback(t, db, func(tx qrm.DB) {
|
||||||
|
var dest []model.SampleArrays
|
||||||
|
|
||||||
|
err := stmt.Query(tx, &dest)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, dest, 1)
|
||||||
|
testutils.AssertDeepEqual(t, dest[0].Int4Array, pq.Int32Array{-10, 11})
|
||||||
|
testutils.AssertDeepEqual(t, dest[0].Int8Array, pq.Int64Array{-1200, 7800})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var sampleArrayRow0 = model.SampleArrays{
|
||||||
|
ID: 1,
|
||||||
|
BoolArray: ptr.Of(pq.BoolArray{true, false, true}),
|
||||||
|
Int2ArrayPtr: ptr.Of(pq.StringArray{"1", "2", "3", "4"}),
|
||||||
|
Int4Array: pq.Int32Array{10, 20, 30, 40},
|
||||||
|
Int8Array: pq.Int64Array{100, 200, 300, 400},
|
||||||
|
NumericArray: pq.Float64Array{1.8881, 2.8882, 3.8883, 4.8884},
|
||||||
|
DecimalArray: pq.Float64Array{1.0001, 2.0002, 3.0003, 4.0004},
|
||||||
|
RealArray: pq.Float32Array{1.0099999904632568, 2.0199999809265137, 3.0299999713897705, 4.039999961853027},
|
||||||
|
DoubleArray: pq.Float64Array{11.11, 22.22, 33.33},
|
||||||
|
TextArray: pq.StringArray{"alpha", "beta", "gama"},
|
||||||
|
CharArray: pq.StringArray{"a", "b", "c"},
|
||||||
|
VarcharArray: pq.StringArray{"hello", "world"},
|
||||||
|
ByteaArray: pq.ByteaArray{{0x01, 0x02, 0x03, 0x04}, {0x11, 0x22, 0x33, 0x44}},
|
||||||
|
DateArray: pq.StringArray{"2024-11-01", "2025-02-28"},
|
||||||
|
TimestampArray: &pq.StringArray{"2025-01-01 10:00:00", "2025-02-01 10:00:00"},
|
||||||
|
TimestamptzArray: pq.StringArray{"2025-01-01 09:00:00+00", "2025-02-01 09:00:00+00"},
|
||||||
|
TimeArray: pq.StringArray{"12:00:00", "13:00:00"},
|
||||||
|
TimetzArray: pq.StringArray{"12:00:00+01", "13:00:00+02"},
|
||||||
|
IntervalArray: pq.StringArray{"1 day", "02:00:00"},
|
||||||
|
UUIDArray: pq.StringArray{"550e8400-e29b-41d4-a716-446655440000"},
|
||||||
|
MoodEnumArray: pq.StringArray{"happy", "ok"},
|
||||||
|
}
|
||||||
|
|
@ -450,7 +450,7 @@ func TestGeneratorTemplate_Model_ChangeFieldTypes(t *testing.T) {
|
||||||
require.Contains(t, data, `"github.com/google/uuid"`)
|
require.Contains(t, data, `"github.com/google/uuid"`)
|
||||||
require.Contains(t, data, "Description sql.NullString")
|
require.Contains(t, data, "Description sql.NullString")
|
||||||
require.Contains(t, data, "ReleaseYear sql.NullInt32")
|
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]")
|
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, want, got)
|
||||||
require.Equal(t, metadata.ArrayType, specialFeatures.DataType.Kind)
|
require.Equal(t, 1, specialFeatures.DataType.Dimensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGeneratorSpecialCharacters(t *testing.T) {
|
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",
|
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",
|
"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+"/all_types.go", allTypesModelContent)
|
||||||
testutils.AssertFileContent(t, modelDir+"/link.go", linkModelContent)
|
testutils.AssertFileContent(t, modelDir+"/link.go", linkModelContent)
|
||||||
|
|
||||||
testutils.AssertFileNamesEqual(t, tableDir, "all_types.go", "employee.go", "link.go",
|
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",
|
"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+"/all_types.go", allTypesTableContent)
|
||||||
testutils.AssertFileContent(t, tableDir+"/sample_ranges.go", sampleRangeTableContent)
|
testutils.AssertFileContent(t, tableDir+"/sample_ranges.go", sampleRangeTableContent)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,8 @@ func TestRangeTableSelect(t *testing.T) {
|
||||||
table.SampleRanges.TimestampzRange.DIFFERENCE(tstzRange).AS("sample.tstz_diff"),
|
table.SampleRanges.TimestampzRange.DIFFERENCE(tstzRange).AS("sample.tstz_diff"),
|
||||||
table.SampleRanges.Int4Range.UPPER_BOUND().ADD(Int(5)).AS("sample.int4_upper"),
|
table.SampleRanges.Int4Range.UPPER_BOUND().ADD(Int(5)).AS("sample.int4_upper"),
|
||||||
table.SampleRanges.Int8Range.LOWER_BOUND().SUB(Int(12)).AS("sample.int8_lower"),
|
table.SampleRanges.Int8Range.LOWER_BOUND().SUB(Int(12)).AS("sample.int8_lower"),
|
||||||
LOWER_BOUND[DateExpression](table.SampleRanges.DateRange),
|
LOWER_BOUND(table.SampleRanges.DateRange),
|
||||||
UPPER_BOUND[NumericExpression](table.SampleRanges.NumRange).AS("sample.num_upper"),
|
UPPER_BOUND(table.SampleRanges.NumRange).AS("sample.num_upper"),
|
||||||
table.SampleRanges.TimestampRange.UPPER_BOUND(),
|
table.SampleRanges.TimestampRange.UPPER_BOUND(),
|
||||||
table.SampleRanges.DateRange.IS_EMPTY().AS("sample.date_empty"),
|
table.SampleRanges.DateRange.IS_EMPTY().AS("sample.date_empty"),
|
||||||
table.SampleRanges.TimestampRange.LOWER_INC().AS("sample.ts_low_inc"),
|
table.SampleRanges.TimestampRange.LOWER_INC().AS("sample.ts_low_inc"),
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/lib/pq"
|
|
||||||
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
||||||
|
"github.com/lib/pq"
|
||||||
"github.com/volatiletech/null/v8"
|
"github.com/volatiletech/null/v8"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -1007,7 +1007,6 @@ func TestScanIntoCustomBaseTypes(t *testing.T) {
|
||||||
type MyFloat32 float32
|
type MyFloat32 float32
|
||||||
type MyFloat64 float64
|
type MyFloat64 float64
|
||||||
type MyString string
|
type MyString string
|
||||||
type MyStringArray pq.StringArray
|
|
||||||
type MyTime = time.Time
|
type MyTime = time.Time
|
||||||
|
|
||||||
type film struct {
|
type film struct {
|
||||||
|
|
@ -1022,13 +1021,12 @@ func TestScanIntoCustomBaseTypes(t *testing.T) {
|
||||||
ReplacementCost MyFloat64
|
ReplacementCost MyFloat64
|
||||||
Rating *model.MpaaRating
|
Rating *model.MpaaRating
|
||||||
LastUpdate MyTime
|
LastUpdate MyTime
|
||||||
SpecialFeatures MyStringArray
|
SpecialFeatures pq.StringArray
|
||||||
Fulltext MyString
|
Fulltext MyString
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll skip special features, because it's a slice and it does not implement sql.Scanner
|
|
||||||
stmt := SELECT(
|
stmt := SELECT(
|
||||||
Film.AllColumns.Except(Film.SpecialFeatures),
|
Film.AllColumns,
|
||||||
).FROM(
|
).FROM(
|
||||||
Film,
|
Film,
|
||||||
).ORDER_BY(
|
).ORDER_BY(
|
||||||
|
|
|
||||||
|
|
@ -358,8 +358,7 @@ func TestSelectQuickStartJSON(t *testing.T) {
|
||||||
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate,
|
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate,
|
||||||
|
|
||||||
SELECT_JSON_ARR(
|
SELECT_JSON_ARR(
|
||||||
Film.AllColumns.Except(Film.SpecialFeatures),
|
Film.AllColumns,
|
||||||
CAST(Film.SpecialFeatures).AS_TEXT().AS("SpecialFeatures"),
|
|
||||||
|
|
||||||
SELECT_JSON_OBJ(
|
SELECT_JSON_OBJ(
|
||||||
Language.AllColumns,
|
Language.AllColumns,
|
||||||
|
|
@ -385,7 +384,11 @@ func TestSelectQuickStartJSON(t *testing.T) {
|
||||||
Film.
|
Film.
|
||||||
INNER_JOIN(FilmActor, FilmActor.FilmID.EQ(Film.FilmID)),
|
INNER_JOIN(FilmActor, FilmActor.FilmID.EQ(Film.FilmID)),
|
||||||
).WHERE(
|
).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(
|
).ORDER_BY(
|
||||||
Film.FilmID.ASC(),
|
Film.FilmID.ASC(),
|
||||||
).AS("Films"),
|
).AS("Films"),
|
||||||
|
|
@ -416,8 +419,8 @@ FROM (
|
||||||
film.replacement_cost AS "replacementCost",
|
film.replacement_cost AS "replacementCost",
|
||||||
film.rating AS "rating",
|
film.rating AS "rating",
|
||||||
to_char(film.last_update, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "lastUpdate",
|
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.fulltext AS "fulltext",
|
||||||
film.special_features::text AS "SpecialFeatures",
|
|
||||||
(
|
(
|
||||||
SELECT row_to_json(language_records) AS "language_json"
|
SELECT row_to_json(language_records) AS "language_json"
|
||||||
FROM (
|
FROM (
|
||||||
|
|
@ -441,7 +444,11 @@ FROM (
|
||||||
) AS "Categories"
|
) AS "Categories"
|
||||||
FROM dvds.film
|
FROM dvds.film
|
||||||
INNER JOIN dvds.film_actor ON (film_actor.film_id = film.film_id)
|
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
|
ORDER BY film.film_id ASC
|
||||||
) AS films_records
|
) AS films_records
|
||||||
) AS "Films"
|
) AS "Films"
|
||||||
|
|
@ -469,8 +476,8 @@ FROM (
|
||||||
return // char[n] columns whitespaces are trimmed when returned as json in cockroachdb
|
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.SaveJSONFile(dest, "./testdata/results/postgres/quick-start-json-dest.json")
|
||||||
//testutils.AssertJSONFile(t, 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) {
|
func TestSelectJsonInReturning(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -2710,7 +2710,32 @@ FOR UPDATE OF "myFilm";
|
||||||
|
|
||||||
func TestSelectQuickStart(t *testing.T) {
|
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",
|
SELECT actor.actor_id AS "actor.actor_id",
|
||||||
actor.first_name AS "actor.first_name",
|
actor.first_name AS "actor.first_name",
|
||||||
actor.last_name AS "actor.last_name",
|
actor.last_name AS "actor.last_name",
|
||||||
|
|
@ -2730,7 +2755,6 @@ SELECT actor.actor_id AS "actor.actor_id",
|
||||||
film.fulltext AS "film.fulltext",
|
film.fulltext AS "film.fulltext",
|
||||||
language.language_id AS "language.language_id",
|
language.language_id AS "language.language_id",
|
||||||
language.name AS "language.name",
|
language.name AS "language.name",
|
||||||
language.last_update AS "language.last_update",
|
|
||||||
category.category_id AS "category.category_id",
|
category.category_id AS "category.category_id",
|
||||||
category.name AS "category.name",
|
category.name AS "category.name",
|
||||||
category.last_update AS "category.last_update"
|
category.last_update AS "category.last_update"
|
||||||
|
|
@ -2740,33 +2764,54 @@ FROM dvds.actor
|
||||||
INNER JOIN dvds.language ON (language.language_id = film.language_id)
|
INNER JOIN dvds.language ON (language.language_id = film.language_id)
|
||||||
INNER JOIN dvds.film_category ON (film_category.film_id = film.film_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)
|
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;
|
ORDER BY actor.actor_id ASC, film.film_id ASC;
|
||||||
`
|
`, "English", "Action", int32(180), "Trailers")
|
||||||
|
|
||||||
stmt := SELECT(
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate, // list of all actor columns (equivalent to Actor.AllColumns)
|
SELECT actor.actor_id AS "actor.actor_id",
|
||||||
Film.AllColumns, // list of all film columns (equivalent to Film.FilmID, Film.Title, ...)
|
actor.first_name AS "actor.first_name",
|
||||||
Language.AllColumns,
|
actor.last_name AS "actor.last_name",
|
||||||
Category.AllColumns,
|
actor.last_update AS "actor.last_update",
|
||||||
).FROM(
|
film.film_id AS "film.film_id",
|
||||||
Actor.
|
film.title AS "film.title",
|
||||||
INNER_JOIN(FilmActor, Actor.ActorID.EQ(FilmActor.ActorID)). // INNER JOIN Actor with FilmActor on condition Actor.ActorID = FilmActor.ActorID
|
film.description AS "film.description",
|
||||||
INNER_JOIN(Film, Film.FilmID.EQ(FilmActor.FilmID)). // then with Film, Language, FilmCategory and Category.
|
film.release_year AS "film.release_year",
|
||||||
INNER_JOIN(Language, Language.LanguageID.EQ(Film.LanguageID)).
|
film.language_id AS "film.language_id",
|
||||||
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
|
film.rental_duration AS "film.rental_duration",
|
||||||
INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
|
film.rental_rate AS "film.rental_rate",
|
||||||
).WHERE(
|
film.length AS "film.length",
|
||||||
Language.Name.EQ(Char(20)("English")). // note that every column has type.
|
film.replacement_cost AS "film.replacement_cost",
|
||||||
AND(Category.Name.NOT_EQ(Text("Action"))). // String column Language.Name and Category.Name can be compared only with string expression
|
film.rating AS "film.rating",
|
||||||
AND(Film.Length.GT(Int32(180))). // Film.Length is integer column and can be compared only with integer expression
|
film.last_update AS "film.last_update",
|
||||||
AND(Film.Rating.NOT_EQ(enum.MpaaRating.R)),
|
film.special_features AS "film.special_features",
|
||||||
).ORDER_BY(
|
film.fulltext AS "film.fulltext",
|
||||||
Actor.ActorID.ASC(),
|
language.language_id AS "language.language_id",
|
||||||
Film.FilmID.ASC(),
|
language.name AS "language.name",
|
||||||
)
|
category.category_id AS "category.category_id",
|
||||||
|
category.name AS "category.name",
|
||||||
testutils.AssertDebugStatementSql(t, stmt, expectedSQL, "English", "Action", int32(180))
|
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 {
|
var dest []struct {
|
||||||
model.Actor
|
model.Actor
|
||||||
|
|
@ -2798,7 +2843,7 @@ ORDER BY actor.actor_id ASC, film.film_id ASC;
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
//testutils.SaveJSONFile(dest, "./testdata/results/postgres/quick-start-dest2.json")
|
//testutils.SaveJSONFile(dest2, "./testdata/results/postgres/quick-start-dest2.json")
|
||||||
testutils.AssertJSONFile(t, dest2, "./testdata/results/postgres/quick-start-dest2.json")
|
testutils.AssertJSONFile(t, dest2, "./testdata/results/postgres/quick-start-dest2.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2806,7 +2851,11 @@ func TestSelectQuickStartWithSubQueries(t *testing.T) {
|
||||||
|
|
||||||
filmLogerThan180 := Film.
|
filmLogerThan180 := Film.
|
||||||
SELECT(Film.AllColumns).
|
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")
|
AsTable("films")
|
||||||
|
|
||||||
filmID := Film.FilmID.From(filmLogerThan180)
|
filmID := Film.FilmID.From(filmLogerThan180)
|
||||||
|
|
@ -2822,7 +2871,7 @@ func TestSelectQuickStartWithSubQueries(t *testing.T) {
|
||||||
stmt := SELECT(
|
stmt := SELECT(
|
||||||
Actor.AllColumns,
|
Actor.AllColumns,
|
||||||
filmLogerThan180.AllColumns(),
|
filmLogerThan180.AllColumns(),
|
||||||
Language.AllColumns,
|
Language.AllColumns.Except(Language.LastUpdate),
|
||||||
categoriesNotAction.AllColumns(),
|
categoriesNotAction.AllColumns(),
|
||||||
).FROM(
|
).FROM(
|
||||||
Actor.
|
Actor.
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,9 @@ ORDER BY film_values.title;
|
||||||
"ReplacementCost": 15.99,
|
"ReplacementCost": 15.99,
|
||||||
"Rating": "R",
|
"Rating": "R",
|
||||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
"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"
|
"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",
|
"Title": "Airport Pollock",
|
||||||
|
|
@ -194,7 +196,9 @@ ORDER BY film_values.title;
|
||||||
"ReplacementCost": 12.99,
|
"ReplacementCost": 12.99,
|
||||||
"Rating": "PG-13",
|
"Rating": "PG-13",
|
||||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
"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"
|
"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",
|
"Title": "Bright Encounters",
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 94f3504ef8245b8f76ca391f068705ba1375c03b
|
Subproject commit 0387785d9e9ceacba2247d477181436f27bf2068
|
||||||
Loading…
Add table
Add a link
Reference in a new issue