Package structure refactor.
This commit is contained in:
parent
3d8e872336
commit
23fd973699
125 changed files with 2401 additions and 1818 deletions
547
internal/jet/README.md
Normal file
547
internal/jet/README.md
Normal file
|
|
@ -0,0 +1,547 @@
|
|||
# Jet
|
||||
|
||||
[](https://circleci.com/gh/go-jet/jet/tree/develop)
|
||||
[](https://codecov.io/gh/go-jet/jet)
|
||||
[](https://goreportcard.com/report/github.com/go-jet/jet)
|
||||
[](http://godoc.org/github.com/go-jet/jet)
|
||||
[](https://github.com/go-jet/jet/releases)
|
||||
|
||||
|
||||
Jet is a framework for writing type-safe SQL queries for PostgreSQL in Go, with ability to easily
|
||||
convert database query result to desired arbitrary structure.
|
||||
_*Support for additional databases will be added in future jet releases._
|
||||
|
||||
|
||||
## Contents
|
||||
- [Features](#features)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Generate sql builder and model files](#generate-sql-builder-and-model-files)
|
||||
- [Lets write some SQL queries in Go](#lets-write-some-sql-queries-in-go)
|
||||
- [Execute query and store result](#execute-query-and-store-result)
|
||||
- [Benefits](#benefits)
|
||||
- [Dependencies](#dependencies)
|
||||
- [Versioning](#versioning)
|
||||
- [License](#license)
|
||||
|
||||
## Features
|
||||
1) Auto-generated type-safe SQL Builder
|
||||
- Types - boolean, integers(smallint, integer, bigint), floats(real, numeric, decimal, double precision),
|
||||
strings(text, character, character varying), date, time(z), timestamp(z) and enums.
|
||||
- Statements:
|
||||
* SELECT (DISTINCT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET, FOR, UNION, INTERSECT, EXCEPT, sub-queries)
|
||||
* INSERT (VALUES, query, RETURNING),
|
||||
* UPDATE (SET, WHERE, RETURNING),
|
||||
* DELETE (WHERE, RETURNING),
|
||||
* LOCK (IN, NOWAIT)
|
||||
2) Auto-generated Data Model types - Go types mapped to database type (table or enum), used to store
|
||||
result of database queries. Can be combined to create desired query result destination.
|
||||
3) Query execution with result mapping to arbitrary destination structure.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To install Jet package, you need to install Go and set your Go workspace first.
|
||||
|
||||
[Go](https://golang.org/) **version 1.8+ is required**
|
||||
|
||||
### Installation
|
||||
|
||||
Use the bellow command to install jet
|
||||
```sh
|
||||
$ go get -u github.com/go-jet/jet
|
||||
```
|
||||
|
||||
Install jet generator to GOPATH bin folder. This will allow generating jet files from the command line.
|
||||
|
||||
```sh
|
||||
go install github.com/go-jet/jet/cmd/jet
|
||||
```
|
||||
|
||||
Make sure GOPATH bin folder is added to the PATH environment variable.
|
||||
|
||||
### Quick Start
|
||||
For this quick start example we will use sample _dvd rental_ database. Full database dump can be found in [./tests/init/data/dvds.sql](./tests/init/data/dvds.sql).
|
||||
Schema diagram of interest for example can be found [here](./examples/quick-start/diagram.png).
|
||||
|
||||
#### Generate SQL Builder and Model files
|
||||
To generate jet SQL Builder and Data Model files from postgres database we need to call `jet` generator with postgres
|
||||
connection parameters and root destination folder path for generated files.\
|
||||
Assuming we are running local postgres database, with user `jetuser`, user password `jetpass`, database `jetdb` and
|
||||
schema `dvds` we will use this command:
|
||||
```sh
|
||||
jet -host=localhost -port=5432 -user=jetuser -password=jetpass -dbname=jetdb -schema=dvds -path=./gen
|
||||
```
|
||||
```sh
|
||||
Connecting to postgres database: host=localhost port=5432 user=jetuser password=jetpass dbname=jetdb sslmode=disable
|
||||
Retrieving schema information...
|
||||
FOUND 15 table(s), 1 enum(s)
|
||||
Destination directory: ./gen/jetdb/dvds
|
||||
Cleaning up schema destination directory...
|
||||
Generating table sql builder files...
|
||||
Generating table model files...
|
||||
Generating enum sql builder files...
|
||||
Generating enum model files...
|
||||
Done
|
||||
```
|
||||
_*User has to have a permission to read information schema tables_
|
||||
|
||||
As command output suggest, Jet will:
|
||||
- connect to postgres database and retrieve information about the _tables_ and _enums_ of `dvds` schema
|
||||
- delete everything in schema destination folder - `./gen/jetdb/dvds`,
|
||||
- and finally generate SQL Builder and Model files for each schema table and enum.
|
||||
|
||||
|
||||
Generated files folder structure will look like this:
|
||||
```sh
|
||||
|-- gen # -path
|
||||
| `-- jetdb # database name
|
||||
| `-- dvds # schema name
|
||||
| |-- enum # sql builder folder for enums
|
||||
| | |-- mpaa_rating.go
|
||||
| |-- table # sql builder folder for tables
|
||||
| |-- actor.go
|
||||
| |-- address.go
|
||||
| |-- category.go
|
||||
| ...
|
||||
| |-- model # model files for each table and enum
|
||||
| | |-- actor.go
|
||||
| | |-- address.go
|
||||
| | |-- mpaa_rating.go
|
||||
| | ...
|
||||
```
|
||||
Types from `table` and `enum` are used to write type safe SQL in Go, and `model` types can be combined to store
|
||||
results of the SQL queries.
|
||||
|
||||
#### Lets write some SQL queries in Go
|
||||
|
||||
First we need to import jet and generated files from previous step:
|
||||
```go
|
||||
import (
|
||||
// dot import so that Go code would resemble as much as native SQL
|
||||
// dot import is not mandatory
|
||||
. "github.com/go-jet/jet"
|
||||
. "github.com/go-jet/jet/examples/quick-start/gen/jetdb/dvds/table"
|
||||
|
||||
"github.com/go-jet/jet/examples/quick-start/gen/jetdb/dvds/model"
|
||||
)
|
||||
```
|
||||
Lets say we want to retrieve the list of all _actors_ that acted in _films_ longer than 180 minutes, _film language_ is 'English'
|
||||
and _film category_ is not 'Action'.
|
||||
```go
|
||||
stmt := SELECT(
|
||||
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate, // or just Actor.AllColumns
|
||||
Film.AllColumns,
|
||||
Language.AllColumns,
|
||||
Category.AllColumns,
|
||||
).FROM(
|
||||
Actor.
|
||||
INNER_JOIN(FilmActor, Actor.ActorID.EQ(FilmActor.ActorID)).
|
||||
INNER_JOIN(Film, Film.FilmID.EQ(FilmActor.FilmID)).
|
||||
INNER_JOIN(Language, Language.LanguageID.EQ(Film.LanguageID)).
|
||||
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
|
||||
INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
|
||||
).WHERE(
|
||||
Language.Name.EQ(String("English")).
|
||||
AND(Category.Name.NOT_EQ(String("Action"))).
|
||||
AND(Film.Length.GT(Int(180))),
|
||||
).ORDER_BY(
|
||||
Actor.ActorID.ASC(),
|
||||
Film.FilmID.ASC(),
|
||||
)
|
||||
```
|
||||
With package(dot) import above statements looks almost the same as native SQL. Note that every column has a type. String column `Language.Name` and `Category.Name` can be compared only with
|
||||
string columns and expressions. `Actor.ActorID`, `FilmActor.ActorID`, `Film.Length` are integer columns
|
||||
and can be compared only with integer columns and expressions.
|
||||
|
||||
__How to get parametrized SQL query from statement?__
|
||||
```go
|
||||
query, args, err := stmt.Sql()
|
||||
```
|
||||
query - parametrized query\
|
||||
args - parameters for the query
|
||||
|
||||
<details>
|
||||
<summary>Click to see `query` and `args`</summary>
|
||||
|
||||
```sql
|
||||
SELECT actor.actor_id AS "actor.actor_id",
|
||||
actor.first_name AS "actor.first_name",
|
||||
actor.last_name AS "actor.last_name",
|
||||
actor.last_update AS "actor.last_update",
|
||||
film.film_id AS "film.film_id",
|
||||
film.title AS "film.title",
|
||||
film.description AS "film.description",
|
||||
film.release_year AS "film.release_year",
|
||||
film.language_id AS "film.language_id",
|
||||
film.rental_duration AS "film.rental_duration",
|
||||
film.rental_rate AS "film.rental_rate",
|
||||
film.length AS "film.length",
|
||||
film.replacement_cost AS "film.replacement_cost",
|
||||
film.rating AS "film.rating",
|
||||
film.last_update AS "film.last_update",
|
||||
film.special_features AS "film.special_features",
|
||||
film.fulltext AS "film.fulltext",
|
||||
language.language_id AS "language.language_id",
|
||||
language.name AS "language.name",
|
||||
language.last_update AS "language.last_update",
|
||||
category.category_id AS "category.category_id",
|
||||
category.name AS "category.name",
|
||||
category.last_update AS "category.last_update"
|
||||
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 = $1) AND (category.name != $2)) AND (film.length > $3)
|
||||
ORDER BY actor.actor_id ASC, film.film_id ASC;
|
||||
```
|
||||
```sh
|
||||
[English Action 180]
|
||||
```
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
__How to get debug SQL from statement?__
|
||||
```go
|
||||
debugSql, err := stmt.DebugSql()
|
||||
```
|
||||
debugSql - query string that can be copy pasted to sql editor and executed. It's not intended to be used in production.
|
||||
|
||||
<details>
|
||||
<summary>Click to see debug sql</summary>
|
||||
|
||||
```sql
|
||||
SELECT actor.actor_id AS "actor.actor_id",
|
||||
actor.first_name AS "actor.first_name",
|
||||
actor.last_name AS "actor.last_name",
|
||||
actor.last_update AS "actor.last_update",
|
||||
film.film_id AS "film.film_id",
|
||||
film.title AS "film.title",
|
||||
film.description AS "film.description",
|
||||
film.release_year AS "film.release_year",
|
||||
film.language_id AS "film.language_id",
|
||||
film.rental_duration AS "film.rental_duration",
|
||||
film.rental_rate AS "film.rental_rate",
|
||||
film.length AS "film.length",
|
||||
film.replacement_cost AS "film.replacement_cost",
|
||||
film.rating AS "film.rating",
|
||||
film.last_update AS "film.last_update",
|
||||
film.special_features AS "film.special_features",
|
||||
film.fulltext AS "film.fulltext",
|
||||
language.language_id AS "language.language_id",
|
||||
language.name AS "language.name",
|
||||
language.last_update AS "language.last_update",
|
||||
category.category_id AS "category.category_id",
|
||||
category.name AS "category.name",
|
||||
category.last_update AS "category.last_update"
|
||||
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') AND (category.name != 'Action')) AND (film.length > 180)
|
||||
ORDER BY actor.actor_id ASC, film.film_id ASC;
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
#### Execute query and store result
|
||||
|
||||
Well formed SQL is just a first half the job. Lets see how can we make some sense of result set returned executing
|
||||
above statement. Usually this is the most complex and tedious work, but with Jet it is the easiest.
|
||||
|
||||
First we have to create desired structure to store query result set.
|
||||
This is done be combining autogenerated model types or it can be done manually(see [wiki](https://github.com/go-jet/jet/wiki/Scan-to-arbitrary-destination) for more information).
|
||||
|
||||
Let's say this is our desired structure:
|
||||
```go
|
||||
var dest []struct {
|
||||
model.Actor
|
||||
|
||||
Films []struct {
|
||||
model.Film
|
||||
|
||||
Language model.Language
|
||||
Categories []model.Category
|
||||
}
|
||||
}
|
||||
```
|
||||
Because one actor can act in multiple films, `Films` field is a slice, and because each film belongs to one language
|
||||
`Langauge` field is just a single model struct.
|
||||
_*There is no limitation of how big or nested destination structure can be._
|
||||
|
||||
Now lets execute a above statement on open database connection db and store result into `dest`.
|
||||
|
||||
```go
|
||||
err := stmt.Query(db, &dest)
|
||||
handleError(err)
|
||||
```
|
||||
|
||||
__And thats it.__
|
||||
|
||||
`dest` now contains the list of all actors(with list of films acted, where each film has information about language and list of belonging categories) that acted in films longer than 180 minutes, film language is 'English'
|
||||
and film category is not 'Action'.
|
||||
|
||||
Lets print `dest` as a json to see:
|
||||
```go
|
||||
jsonText, _ := json.MarshalIndent(dest, "", "\t")
|
||||
fmt.Println(string(jsonText))
|
||||
```
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
"ActorID": 1,
|
||||
"FirstName": "Penelope",
|
||||
"LastName": "Guiness",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z",
|
||||
"Films": [
|
||||
{
|
||||
"FilmID": 499,
|
||||
"Title": "King Evolution",
|
||||
"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 3,
|
||||
"RentalRate": 4.99,
|
||||
"Length": 184,
|
||||
"ReplacementCost": 24.99,
|
||||
"Rating": "NC-17",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"SpecialFeatures": "{Trailers,\"Deleted Scenes\",\"Behind the Scenes\"}",
|
||||
"Fulltext": "'action':5 'action-pack':4 'baloon':21 'boy':10 'chase':16 'evolut':2 'king':1 'lumberjack':13 'madman':18 'must':15 'pack':6 'tale':7",
|
||||
"Language": {
|
||||
"LanguageID": 1,
|
||||
"Name": "English ",
|
||||
"LastUpdate": "2006-02-15T10:02:19Z"
|
||||
},
|
||||
"Categories": [
|
||||
{
|
||||
"CategoryID": 8,
|
||||
"Name": "Family",
|
||||
"LastUpdate": "2006-02-15T09:46:27Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ActorID": 3,
|
||||
"FirstName": "Ed",
|
||||
"LastName": "Chase",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z",
|
||||
"Films": [
|
||||
{
|
||||
"FilmID": 996,
|
||||
"Title": "Young Language",
|
||||
"Description": "A Unbelieveable Yarn of a Boat And a Database Administrator who must Meet a Boy in The First Manned Space Station",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 6,
|
||||
"RentalRate": 0.99,
|
||||
"Length": 183,
|
||||
"ReplacementCost": 9.99,
|
||||
"Rating": "G",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"SpecialFeatures": "{Trailers,\"Behind the Scenes\"}",
|
||||
"Fulltext": "'administr':12 'boat':8 'boy':17 'databas':11 'first':20 'languag':2 'man':21 'meet':15 'must':14 'space':22 'station':23 'unbeliev':4 'yarn':5 'young':1",
|
||||
"Language": {
|
||||
"LanguageID": 1,
|
||||
"Name": "English ",
|
||||
"LastUpdate": "2006-02-15T10:02:19Z"
|
||||
},
|
||||
"Categories": [
|
||||
{
|
||||
"CategoryID": 6,
|
||||
"Name": "Documentary",
|
||||
"LastUpdate": "2006-02-15T09:46:27Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
//...(125 more items)
|
||||
]
|
||||
```
|
||||
|
||||
What if, we also want to have list of films per category and actors per category, where films are longer than 180 minutes, film language is 'English'
|
||||
and film category is not 'Action'.
|
||||
In that case we can reuse above statement `stmt`, and just change our destination:
|
||||
|
||||
```go
|
||||
var dest2 []struct {
|
||||
model.Category
|
||||
|
||||
Films []model.Film
|
||||
Actors []model.Actor
|
||||
}
|
||||
|
||||
err = stmt.Query(db, &dest2)
|
||||
handleError(err)
|
||||
```
|
||||
<details>
|
||||
<summary>Click to see `dest2` json</summary>
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
"CategoryID": 8,
|
||||
"Name": "Family",
|
||||
"LastUpdate": "2006-02-15T09:46:27Z",
|
||||
"Films": [
|
||||
{
|
||||
"FilmID": 499,
|
||||
"Title": "King Evolution",
|
||||
"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 3,
|
||||
"RentalRate": 4.99,
|
||||
"Length": 184,
|
||||
"ReplacementCost": 24.99,
|
||||
"Rating": "NC-17",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"SpecialFeatures": "{Trailers,\"Deleted Scenes\",\"Behind the Scenes\"}",
|
||||
"Fulltext": "'action':5 'action-pack':4 'baloon':21 'boy':10 'chase':16 'evolut':2 'king':1 'lumberjack':13 'madman':18 'must':15 'pack':6 'tale':7"
|
||||
},
|
||||
{
|
||||
"FilmID": 50,
|
||||
"Title": "Baked Cleopatra",
|
||||
"Description": "A Stunning Drama of a Forensic Psychologist And a Husband who must Overcome a Waitress in A Monastery",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 3,
|
||||
"RentalRate": 2.99,
|
||||
"Length": 182,
|
||||
"ReplacementCost": 20.99,
|
||||
"Rating": "G",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"SpecialFeatures": "{Commentaries,\"Behind the Scenes\"}",
|
||||
"Fulltext": "'bake':1 'cleopatra':2 'drama':5 'forens':8 'husband':12 'monasteri':20 'must':14 'overcom':15 'psychologist':9 'stun':4 'waitress':17"
|
||||
}
|
||||
],
|
||||
"Actors": [
|
||||
{
|
||||
"ActorID": 1,
|
||||
"FirstName": "Penelope",
|
||||
"LastName": "Guiness",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 20,
|
||||
"FirstName": "Lucille",
|
||||
"LastName": "Tracy",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 36,
|
||||
"FirstName": "Burt",
|
||||
"LastName": "Dukakis",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 70,
|
||||
"FirstName": "Michelle",
|
||||
"LastName": "Mcconaughey",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 118,
|
||||
"FirstName": "Cuba",
|
||||
"LastName": "Allen",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 187,
|
||||
"FirstName": "Renee",
|
||||
"LastName": "Ball",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 198,
|
||||
"FirstName": "Mary",
|
||||
"LastName": "Keitel",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
//...
|
||||
]
|
||||
```
|
||||
</details>
|
||||
|
||||
Complete code example can be found at [./examples/quick-start/quick-start.go](./examples/quick-start/quick-start.go)
|
||||
|
||||
|
||||
This example represent probably the most common use case. Detail info about additional features and use cases can be
|
||||
found at project [wiki](https://github.com/go-jet/jet/wiki) page.
|
||||
|
||||
## Benefits
|
||||
|
||||
What are the benefits of writing SQL in Go using Jet? The biggest benefit is speed.
|
||||
Speed is improved in 3 major areas:
|
||||
|
||||
##### Speed of development
|
||||
|
||||
Writing SQL queries is much easier directly from Go, because programmer has the help of SQL code completion and SQL type safety directly in Go.
|
||||
Writing code is much faster and code is more robust. Automatic scan to arbitrary structure removes a lot of headache and
|
||||
boilerplate code needed to structure database query result.
|
||||
|
||||
##### Speed of execution
|
||||
|
||||
Common web and database server usually are not on the same physical machine, and there is some latency between them.
|
||||
Latency can vary from 5ms to 50+ms. In majority of cases query executed on database is simple query lasting no more than 1ms.
|
||||
In those cases web server handler execution time is directly proportional to latency between server and database.
|
||||
This is not such a big problem if handler calls database couple of times, but what if web server is using ORM to retrieve data from database.
|
||||
ORM sometimes can access the database once for every object needed. Now lets say latency is 30ms and there are 100
|
||||
different objects required from the database. This handler will last 3 seconds !!!.
|
||||
|
||||
With Jet, handler time lost on latency between server and database is constant. Because we can write complex query and
|
||||
return result in one database call. Handler execution will be only proportional to the number of rows returned from database.
|
||||
ORM example replaced with jet will take just 30ms + 'result scan time' = 31ms (rough estimate).
|
||||
|
||||
With Jet you can even join the whole database and store the whole structured result in in one query call.
|
||||
This is exactly what is being done in one of the tests: [TestJoinEverything](/tests/postgres/chinook_db_test.go#L40).
|
||||
The whole test database is joined and query result(~10,000 rows) is stored in a structured variable in less than 0.7s.
|
||||
|
||||
##### How quickly bugs are found
|
||||
|
||||
The most expensive bugs are the one on the production and the least expensive are those found during development.
|
||||
With automatically generated type safe SQL not only queries are written faster but bugs are found sooner.
|
||||
Lets return to quick start example, and take closer look at a line:
|
||||
```go
|
||||
AND(Film.Length.GT(Int(180))),
|
||||
```
|
||||
Lets say someone changes column `length` to `duration` from `film` table. The next go build will fail at that line and
|
||||
the bug will be caught at compile time.
|
||||
|
||||
Lets say someone changes the type of `length` column to some non integer type. Build will also fail at the same line
|
||||
because integer columns and expressions can be only compered to other integer columns and expressions.
|
||||
|
||||
Without Jet these bugs will have to be either caught by some test or by manual testing.
|
||||
|
||||
## Dependencies
|
||||
At the moment Jet dependence only of:
|
||||
- `github.com/google/uuid` _(Used for debug purposes and in data model files)_
|
||||
- `github.com/lib/pq` _(Used by Jet to read information about database schema types)_
|
||||
|
||||
To run the tests, additional dependencies are required:
|
||||
- `github.com/pkg/profile`
|
||||
- `gotest.tools/assert`
|
||||
|
||||
|
||||
## Versioning
|
||||
|
||||
[SemVer](http://semver.org/) is used for versioning. For the versions available, see the [releases](https://github.com/go-jet/jet/releases).
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2019 Goran Bjelanovic
|
||||
Licensed under the Apache License, Version 2.0.
|
||||
34
internal/jet/alias.go
Normal file
34
internal/jet/alias.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package jet
|
||||
|
||||
type alias struct {
|
||||
expression Expression
|
||||
alias string
|
||||
}
|
||||
|
||||
func newAlias(expression Expression, aliasName string) Projection {
|
||||
return &alias{
|
||||
expression: expression,
|
||||
alias: aliasName,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *alias) fromImpl(subQuery SelectTable) Projection {
|
||||
column := newColumn(a.alias, "", nil)
|
||||
column.parent = &column
|
||||
column.subQuery = subQuery
|
||||
|
||||
return &column
|
||||
}
|
||||
|
||||
func (a *alias) serializeForProjection(statement StatementType, out *SqlBuilder) error {
|
||||
err := a.expression.serialize(statement, out)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.WriteString("AS")
|
||||
out.writeAlias(a.alias)
|
||||
|
||||
return nil
|
||||
}
|
||||
159
internal/jet/bool_expression.go
Normal file
159
internal/jet/bool_expression.go
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
package jet
|
||||
|
||||
//BoolExpression interface
|
||||
type BoolExpression interface {
|
||||
Expression
|
||||
|
||||
// Check if this expression is equal to rhs
|
||||
EQ(rhs BoolExpression) BoolExpression
|
||||
// Check if this expression is not equal to rhs
|
||||
NOT_EQ(rhs BoolExpression) BoolExpression
|
||||
// Check if this expression is distinct to rhs
|
||||
IS_DISTINCT_FROM(rhs BoolExpression) BoolExpression
|
||||
// Check if this expression is not distinct to rhs
|
||||
IS_NOT_DISTINCT_FROM(rhs BoolExpression) BoolExpression
|
||||
|
||||
// Check if this expression is true
|
||||
IS_TRUE() BoolExpression
|
||||
// Check if this expression is not true
|
||||
IS_NOT_TRUE() BoolExpression
|
||||
// Check if this expression is false
|
||||
IS_FALSE() BoolExpression
|
||||
// Check if this expression is not false
|
||||
IS_NOT_FALSE() BoolExpression
|
||||
// Check if this expression is unknown
|
||||
IS_UNKNOWN() BoolExpression
|
||||
// Check if this expression is not unknown
|
||||
IS_NOT_UNKNOWN() BoolExpression
|
||||
|
||||
// expression AND operator rhs
|
||||
AND(rhs BoolExpression) BoolExpression
|
||||
// expression OR operator rhs
|
||||
OR(rhs BoolExpression) BoolExpression
|
||||
}
|
||||
|
||||
type boolInterfaceImpl struct {
|
||||
parent BoolExpression
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) EQ(expression BoolExpression) BoolExpression {
|
||||
return eq(b.parent, expression)
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) NOT_EQ(expression BoolExpression) BoolExpression {
|
||||
return notEq(b.parent, expression)
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_DISTINCT_FROM(rhs BoolExpression) BoolExpression {
|
||||
return isDistinctFrom(b.parent, rhs)
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs BoolExpression) BoolExpression {
|
||||
return isNotDistinctFrom(b.parent, rhs)
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) AND(expression BoolExpression) BoolExpression {
|
||||
return newBinaryBoolOperator(b.parent, expression, "AND")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) OR(expression BoolExpression) BoolExpression {
|
||||
return newBinaryBoolOperator(b.parent, expression, "OR")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_TRUE() BoolExpression {
|
||||
return newPostifxBoolExpression(b.parent, "IS TRUE")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_NOT_TRUE() BoolExpression {
|
||||
return newPostifxBoolExpression(b.parent, "IS NOT TRUE")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_FALSE() BoolExpression {
|
||||
return newPostifxBoolExpression(b.parent, "IS FALSE")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_NOT_FALSE() BoolExpression {
|
||||
return newPostifxBoolExpression(b.parent, "IS NOT FALSE")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_UNKNOWN() BoolExpression {
|
||||
return newPostifxBoolExpression(b.parent, "IS UNKNOWN")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_NOT_UNKNOWN() BoolExpression {
|
||||
return newPostifxBoolExpression(b.parent, "IS NOT UNKNOWN")
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
type binaryBoolExpression struct {
|
||||
expressionInterfaceImpl
|
||||
boolInterfaceImpl
|
||||
|
||||
binaryOpExpression
|
||||
}
|
||||
|
||||
func newBinaryBoolOperator(lhs, rhs Expression, operator string) BoolExpression {
|
||||
binaryBoolExpression := binaryBoolExpression{}
|
||||
|
||||
binaryBoolExpression.binaryOpExpression = newBinaryExpression(lhs, rhs, operator)
|
||||
binaryBoolExpression.expressionInterfaceImpl.parent = &binaryBoolExpression
|
||||
binaryBoolExpression.boolInterfaceImpl.parent = &binaryBoolExpression
|
||||
|
||||
return &binaryBoolExpression
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
type prefixBoolExpression struct {
|
||||
expressionInterfaceImpl
|
||||
boolInterfaceImpl
|
||||
|
||||
prefixOpExpression
|
||||
}
|
||||
|
||||
func newPrefixBoolOperator(expression Expression, operator string) BoolExpression {
|
||||
exp := prefixBoolExpression{}
|
||||
exp.prefixOpExpression = newPrefixExpression(expression, operator)
|
||||
|
||||
exp.expressionInterfaceImpl.parent = &exp
|
||||
exp.boolInterfaceImpl.parent = &exp
|
||||
|
||||
return &exp
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
type postfixBoolOpExpression struct {
|
||||
expressionInterfaceImpl
|
||||
boolInterfaceImpl
|
||||
|
||||
postfixOpExpression
|
||||
}
|
||||
|
||||
func newPostifxBoolExpression(expression Expression, operator string) BoolExpression {
|
||||
exp := postfixBoolOpExpression{}
|
||||
exp.postfixOpExpression = newPostfixOpExpression(expression, operator)
|
||||
|
||||
exp.expressionInterfaceImpl.parent = &exp
|
||||
exp.boolInterfaceImpl.parent = &exp
|
||||
|
||||
return &exp
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type boolExpressionWrapper struct {
|
||||
boolInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newBoolExpressionWrap(expression Expression) BoolExpression {
|
||||
boolExpressionWrap := boolExpressionWrapper{Expression: expression}
|
||||
boolExpressionWrap.boolInterfaceImpl.parent = &boolExpressionWrap
|
||||
return &boolExpressionWrap
|
||||
}
|
||||
|
||||
// BoolExp is bool expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as bool expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func BoolExp(expression Expression) BoolExpression {
|
||||
return newBoolExpressionWrap(expression)
|
||||
}
|
||||
90
internal/jet/bool_expression_test.go
Normal file
90
internal/jet/bool_expression_test.go
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBoolExpressionEQ(t *testing.T) {
|
||||
assertClauseSerializeErr(t, table1ColBool.EQ(nil), "jet: nil rhs")
|
||||
assertClauseSerialize(t, table1ColBool.EQ(table2ColBool), "(table1.col_bool = table2.col_bool)")
|
||||
}
|
||||
|
||||
func TestBoolExpressionNOT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.NOT_EQ(table2ColBool), "(table1.col_bool != table2.col_bool)")
|
||||
assertClauseSerialize(t, table1ColBool.NOT_EQ(Bool(true)), "(table1.col_bool != TRUE)")
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(table2ColBool), "(table1.col_bool IS DISTINCT FROM table2.col_bool)")
|
||||
assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(Bool(false)), "(table1.col_bool IS DISTINCT FROM FALSE)")
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(table2ColBool), "(table1.col_bool IS NOT DISTINCT FROM table2.col_bool)")
|
||||
assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(Bool(false)), "(table1.col_bool IS NOT DISTINCT FROM FALSE)")
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_TRUE(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_TRUE(), "table1.col_bool IS TRUE")
|
||||
assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE(),
|
||||
`(2 = table1.col_int) IS TRUE`)
|
||||
assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE().AND(Int(4).EQ(table2ColInt)),
|
||||
`((2 = table1.col_int) IS TRUE AND (4 = table2.col_int))`)
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_NOT_TRUE(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_NOT_TRUE(), "table1.col_bool IS NOT TRUE")
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_FALSE(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_FALSE(), "table1.col_bool IS FALSE")
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_NOT_FALSE(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_NOT_FALSE(), "table1.col_bool IS NOT FALSE")
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_UNKNOWN(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_UNKNOWN(), "table1.col_bool IS UNKNOWN")
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_NOT_UNKNOWN(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_NOT_UNKNOWN(), "table1.col_bool IS NOT UNKNOWN")
|
||||
}
|
||||
|
||||
func TestBinaryBoolExpression(t *testing.T) {
|
||||
boolExpression := Int(2).EQ(Int(3))
|
||||
|
||||
assertClauseSerialize(t, boolExpression, "(2 = 3)")
|
||||
|
||||
assertProjectionSerialize(t, boolExpression, "2 = 3")
|
||||
assertProjectionSerialize(t, boolExpression.AS("alias_eq_expression"),
|
||||
`(2 = 3) AS "alias_eq_expression"`)
|
||||
assertClauseSerialize(t, boolExpression.AND(Int(4).EQ(Int(5))),
|
||||
"((2 = 3) AND (4 = 5))")
|
||||
assertClauseSerialize(t, boolExpression.OR(Int(4).EQ(Int(5))),
|
||||
"((2 = 3) OR (4 = 5))")
|
||||
}
|
||||
|
||||
func TestBoolLiteral(t *testing.T) {
|
||||
assertClauseSerialize(t, Bool(true), "TRUE", true)
|
||||
assertClauseSerialize(t, Bool(false), "FALSE", false)
|
||||
}
|
||||
|
||||
func TestExists(t *testing.T) {
|
||||
assertClauseSerialize(t, EXISTS(
|
||||
table2.
|
||||
SELECT(Int(1)).
|
||||
WHERE(table1Col1.EQ(table2Col3)),
|
||||
),
|
||||
`(EXISTS (
|
||||
SELECT 1
|
||||
FROM db.table2
|
||||
WHERE table1.col1 = table2.col3
|
||||
))`)
|
||||
}
|
||||
|
||||
func TestBoolExp(t *testing.T) {
|
||||
assertClauseSerialize(t, BoolExp(String("true")), "'true'")
|
||||
assertClauseSerialize(t, BoolExp(String("true")).IS_TRUE(), "'true' IS TRUE")
|
||||
}
|
||||
93
internal/jet/cast.go
Normal file
93
internal/jet/cast.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package jet
|
||||
|
||||
import "strconv"
|
||||
|
||||
type Cast interface {
|
||||
AS(castType string) Expression
|
||||
|
||||
AS_CHAR(lenght ...int) StringExpression
|
||||
// Cast expression AS date type
|
||||
AS_DATE() DateExpression
|
||||
// Cast expression AS numeric type, using precision and optionally scale
|
||||
AS_DECIMAL() FloatExpression
|
||||
// Cast expression AS time type
|
||||
AS_TIME() TimeExpression
|
||||
}
|
||||
|
||||
type CastImpl struct {
|
||||
expression Expression
|
||||
}
|
||||
|
||||
func NewCastImpl(expression Expression) CastImpl {
|
||||
castImpl := CastImpl{
|
||||
expression: expression,
|
||||
}
|
||||
|
||||
return castImpl
|
||||
}
|
||||
|
||||
func (b *CastImpl) AS(castType string) Expression {
|
||||
castExp := &castExpression{
|
||||
expression: b.expression,
|
||||
cast: string(castType),
|
||||
}
|
||||
|
||||
castExp.expressionInterfaceImpl.parent = castExp
|
||||
|
||||
return castExp
|
||||
}
|
||||
|
||||
func (b *CastImpl) AS_CHAR(lenght ...int) StringExpression {
|
||||
if len(lenght) > 0 {
|
||||
return StringExp(b.AS("CHAR(" + strconv.Itoa(lenght[0]) + ")"))
|
||||
}
|
||||
|
||||
return StringExp(b.AS("CHAR"))
|
||||
}
|
||||
|
||||
// Cast expression AS date type
|
||||
func (b *CastImpl) AS_DATE() DateExpression {
|
||||
return DateExp(b.AS("DATE"))
|
||||
}
|
||||
|
||||
// Cast expression AS date type
|
||||
func (b *CastImpl) AS_DECIMAL() FloatExpression {
|
||||
return FloatExp(b.AS("DECIMAL"))
|
||||
}
|
||||
|
||||
// Cast expression AS date type
|
||||
func (b *CastImpl) AS_TIME() TimeExpression {
|
||||
return TimeExp(b.AS("TIME"))
|
||||
}
|
||||
|
||||
type castExpression struct {
|
||||
expressionInterfaceImpl
|
||||
|
||||
expression Expression
|
||||
cast string
|
||||
}
|
||||
|
||||
func (b *castExpression) accept(visitor visitor) {
|
||||
b.expression.accept(visitor)
|
||||
}
|
||||
|
||||
func (b *castExpression) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
|
||||
expression := b.expression
|
||||
castType := b.cast
|
||||
|
||||
if castOverride := out.Dialect.CastOverride(); castOverride != nil {
|
||||
return castOverride(expression, castType)(statement, out, options...)
|
||||
}
|
||||
|
||||
out.WriteString("CAST(")
|
||||
err := expression.serialize(statement, out, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.WriteString("AS")
|
||||
out.WriteString(castType + ")")
|
||||
|
||||
return err
|
||||
}
|
||||
7
internal/jet/cast_test.go
Normal file
7
internal/jet/cast_test.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package jet
|
||||
|
||||
//func TestCastAS(t *testing.T) {
|
||||
// AssertClauseSerialize(t, NewCastImpl(Int(1)).AS("boolean"), "CAST(? AS boolean)", int64(1))
|
||||
// AssertClauseSerialize(t, NewCastImpl(table2Col3).AS("real"), "CAST(table2.col3 AS real)")
|
||||
// AssertClauseSerialize(t, NewCastImpl(table2Col3.ADD(table2Col3)).AS("integer"), "CAST((table2.col3 + table2.col3) AS integer)")
|
||||
//}
|
||||
270
internal/jet/clause.go
Normal file
270
internal/jet/clause.go
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/go-jet/jet/internal/utils"
|
||||
"github.com/google/uuid"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SerializeOption int
|
||||
|
||||
const (
|
||||
noWrap SerializeOption = iota
|
||||
)
|
||||
|
||||
type Clause interface {
|
||||
serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error
|
||||
}
|
||||
|
||||
func Serialize(exp Clause, statementType StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
return exp.serialize(statementType, out, options...)
|
||||
}
|
||||
|
||||
func contains(options []SerializeOption, option SerializeOption) bool {
|
||||
for _, opt := range options {
|
||||
if opt == option {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type SqlBuilder struct {
|
||||
Dialect Dialect
|
||||
Buff bytes.Buffer
|
||||
Args []interface{}
|
||||
|
||||
lastChar byte
|
||||
ident int
|
||||
}
|
||||
|
||||
func (s *SqlBuilder) DebugSQL() string {
|
||||
return queryStringToDebugString(s.Buff.String(), s.Args, s.Dialect)
|
||||
}
|
||||
|
||||
type StatementType string
|
||||
|
||||
const (
|
||||
SelectStatementType StatementType = "SELECT"
|
||||
InsertStatementType StatementType = "INSERT"
|
||||
UpdateStatementType StatementType = "UPDATE"
|
||||
DeleteStatementType StatementType = "DELETE"
|
||||
SetStatementType StatementType = "SET"
|
||||
LockStatementType StatementType = "LOCK"
|
||||
)
|
||||
|
||||
const defaultIdent = 5
|
||||
|
||||
func (q *SqlBuilder) increaseIdent() {
|
||||
q.ident += defaultIdent
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) decreaseIdent() {
|
||||
if q.ident < defaultIdent {
|
||||
q.ident = 0
|
||||
}
|
||||
|
||||
q.ident -= defaultIdent
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) writeProjections(statement StatementType, projections []Projection) error {
|
||||
q.increaseIdent()
|
||||
err := SerializeProjectionList(statement, projections, q)
|
||||
q.decreaseIdent()
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) writeFrom(statement StatementType, table ReadableTable) error {
|
||||
q.newLine()
|
||||
q.WriteString("FROM")
|
||||
|
||||
q.increaseIdent()
|
||||
err := table.serialize(statement, q)
|
||||
q.decreaseIdent()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) writeWhere(statement StatementType, where Expression) error {
|
||||
q.newLine()
|
||||
q.WriteString("WHERE")
|
||||
|
||||
q.increaseIdent()
|
||||
err := where.serialize(statement, q, noWrap)
|
||||
q.decreaseIdent()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) writeGroupBy(statement StatementType, groupBy []groupByClause) error {
|
||||
q.newLine()
|
||||
q.WriteString("GROUP BY")
|
||||
|
||||
q.increaseIdent()
|
||||
err := serializeGroupByClauseList(statement, groupBy, q)
|
||||
q.decreaseIdent()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) writeOrderBy(statement StatementType, orderBy []orderByClause) error {
|
||||
q.newLine()
|
||||
q.WriteString("ORDER BY")
|
||||
|
||||
q.increaseIdent()
|
||||
err := serializeOrderByClauseList(statement, orderBy, q)
|
||||
q.decreaseIdent()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) writeHaving(statement StatementType, having Expression) error {
|
||||
q.newLine()
|
||||
q.WriteString("HAVING")
|
||||
|
||||
q.increaseIdent()
|
||||
err := having.serialize(statement, q, noWrap)
|
||||
q.decreaseIdent()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) writeReturning(statement StatementType, returning []Projection) error {
|
||||
if len(returning) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !q.Dialect.SupportsReturning() {
|
||||
panic("jet: " + q.Dialect.Name() + " dialect does not support RETURNING.")
|
||||
}
|
||||
|
||||
q.newLine()
|
||||
q.WriteString("RETURNING")
|
||||
q.increaseIdent()
|
||||
|
||||
return q.writeProjections(statement, returning)
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) newLine() {
|
||||
q.write([]byte{'\n'})
|
||||
q.write(bytes.Repeat([]byte{' '}, q.ident))
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) write(data []byte) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !isPreSeparator(q.lastChar) && !isPostSeparator(data[0]) && q.Buff.Len() > 0 {
|
||||
q.Buff.WriteByte(' ')
|
||||
}
|
||||
|
||||
q.Buff.Write(data)
|
||||
q.lastChar = data[len(data)-1]
|
||||
}
|
||||
|
||||
func isPreSeparator(b byte) bool {
|
||||
return b == ' ' || b == '.' || b == ',' || b == '(' || b == '\n' || b == ':'
|
||||
}
|
||||
|
||||
func isPostSeparator(b byte) bool {
|
||||
return b == ' ' || b == '.' || b == ',' || b == ')' || b == '\n' || b == ':'
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) writeAlias(str string) {
|
||||
aliasQuoteChar := string(q.Dialect.AliasQuoteChar())
|
||||
q.WriteString(aliasQuoteChar + str + aliasQuoteChar)
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) WriteString(str string) {
|
||||
q.write([]byte(str))
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) writeIdentifier(name string, alwaysQuote ...bool) {
|
||||
quoteWrap := name != strings.ToLower(name) || strings.ContainsAny(name, ". -")
|
||||
|
||||
if quoteWrap || len(alwaysQuote) > 0 {
|
||||
identQuoteChar := string(q.Dialect.IdentifierQuoteChar())
|
||||
q.WriteString(identQuoteChar + name + identQuoteChar)
|
||||
} else {
|
||||
q.WriteString(name)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) writeByte(b byte) {
|
||||
q.write([]byte{b})
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) finalize() (string, []interface{}) {
|
||||
return q.Buff.String() + ";\n", q.Args
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) insertConstantArgument(arg interface{}) {
|
||||
q.WriteString(argToString(arg))
|
||||
}
|
||||
|
||||
func (q *SqlBuilder) insertParametrizedArgument(arg interface{}) {
|
||||
q.Args = append(q.Args, arg)
|
||||
argPlaceholder := q.Dialect.ArgumentPlaceholder()(len(q.Args))
|
||||
|
||||
q.WriteString(argPlaceholder)
|
||||
}
|
||||
|
||||
func argToString(value interface{}) string {
|
||||
if utils.IsNil(value) {
|
||||
return "NULL"
|
||||
}
|
||||
|
||||
switch bindVal := value.(type) {
|
||||
case bool:
|
||||
if bindVal {
|
||||
return "TRUE"
|
||||
}
|
||||
return "FALSE"
|
||||
case int8:
|
||||
return strconv.FormatInt(int64(bindVal), 10)
|
||||
case int:
|
||||
return strconv.FormatInt(int64(bindVal), 10)
|
||||
case int16:
|
||||
return strconv.FormatInt(int64(bindVal), 10)
|
||||
case int32:
|
||||
return strconv.FormatInt(int64(bindVal), 10)
|
||||
case int64:
|
||||
return strconv.FormatInt(int64(bindVal), 10)
|
||||
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(bindVal), 10)
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(bindVal), 10)
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(bindVal), 10)
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(bindVal), 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(uint64(bindVal), 10)
|
||||
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(bindVal), 'f', -1, 64)
|
||||
case float64:
|
||||
return strconv.FormatFloat(float64(bindVal), 'f', -1, 64)
|
||||
|
||||
case string:
|
||||
return stringQuote(bindVal)
|
||||
case []byte:
|
||||
return stringQuote(string(bindVal))
|
||||
case uuid.UUID:
|
||||
return stringQuote(bindVal.String())
|
||||
case time.Time:
|
||||
return stringQuote(string(utils.FormatTimestamp(bindVal)))
|
||||
default:
|
||||
return "[Unsupported type]"
|
||||
}
|
||||
}
|
||||
|
||||
func stringQuote(value string) string {
|
||||
return `'` + strings.Replace(value, "'", "''", -1) + `'`
|
||||
}
|
||||
33
internal/jet/clause_test.go
Normal file
33
internal/jet/clause_test.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"gotest.tools/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestArgToString(t *testing.T) {
|
||||
assert.Equal(t, argToString(true), "TRUE")
|
||||
assert.Equal(t, argToString(false), "FALSE")
|
||||
|
||||
assert.Equal(t, argToString(int8(-8)), "-8")
|
||||
assert.Equal(t, argToString(int16(-16)), "-16")
|
||||
assert.Equal(t, argToString(int(-32)), "-32")
|
||||
assert.Equal(t, argToString(int32(-32)), "-32")
|
||||
assert.Equal(t, argToString(int64(-64)), "-64")
|
||||
assert.Equal(t, argToString(uint8(8)), "8")
|
||||
assert.Equal(t, argToString(uint16(16)), "16")
|
||||
assert.Equal(t, argToString(uint(32)), "32")
|
||||
assert.Equal(t, argToString(uint32(32)), "32")
|
||||
assert.Equal(t, argToString(uint64(64)), "64")
|
||||
|
||||
assert.Equal(t, argToString("john"), "'john'")
|
||||
assert.Equal(t, argToString([]byte("john")), "'john'")
|
||||
assert.Equal(t, argToString(uuid.MustParse("b68dbff4-a87d-11e9-a7f2-98ded00c39c6")), "'b68dbff4-a87d-11e9-a7f2-98ded00c39c6'")
|
||||
|
||||
time, err := time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", "Mon Jan 2 15:04:05 -0700 MST 2006")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, argToString(time), "'2006-01-02 15:04:05-07:00'")
|
||||
assert.Equal(t, argToString(map[string]bool{}), "[Unsupported type]")
|
||||
}
|
||||
159
internal/jet/column.go
Normal file
159
internal/jet/column.go
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
// Modeling of columns
|
||||
|
||||
package jet
|
||||
|
||||
type IColumn interface {
|
||||
Name() string
|
||||
TableName() string
|
||||
|
||||
SetTableName(table string)
|
||||
SetSubQuery(subQuery SelectTable)
|
||||
DefaultAlias() string
|
||||
}
|
||||
|
||||
// Column is common column interface for all types of columns.
|
||||
type Column interface {
|
||||
Expression
|
||||
IColumn
|
||||
}
|
||||
|
||||
// The base type for real materialized columns.
|
||||
type columnImpl struct {
|
||||
expressionInterfaceImpl
|
||||
noOpVisitorImpl
|
||||
|
||||
name string
|
||||
tableName string
|
||||
|
||||
subQuery SelectTable
|
||||
}
|
||||
|
||||
func newColumn(name string, tableName string, parent Column) columnImpl {
|
||||
bc := columnImpl{
|
||||
name: name,
|
||||
tableName: tableName,
|
||||
}
|
||||
|
||||
bc.expressionInterfaceImpl.parent = parent
|
||||
|
||||
return bc
|
||||
}
|
||||
|
||||
func (c *columnImpl) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *columnImpl) TableName() string {
|
||||
return c.tableName
|
||||
}
|
||||
|
||||
func (c *columnImpl) SetTableName(table string) {
|
||||
c.tableName = table
|
||||
}
|
||||
|
||||
func (c *columnImpl) SetSubQuery(subQuery SelectTable) {
|
||||
c.subQuery = subQuery
|
||||
}
|
||||
|
||||
func (c *columnImpl) DefaultAlias() string {
|
||||
if c.tableName != "" {
|
||||
return c.tableName + "." + c.name
|
||||
}
|
||||
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *columnImpl) serializeForOrderBy(statement StatementType, out *SqlBuilder) error {
|
||||
if statement == SetStatementType {
|
||||
// set Statement (UNION, EXCEPT ...) can reference only select projections in order by clause
|
||||
out.writeAlias(c.DefaultAlias()) //always quote
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.serialize(statement, out)
|
||||
}
|
||||
|
||||
func (c columnImpl) serializeForProjection(statement StatementType, out *SqlBuilder) error {
|
||||
err := c.serialize(statement, out)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.WriteString("AS")
|
||||
out.writeAlias(c.DefaultAlias())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c columnImpl) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
|
||||
if c.subQuery != nil {
|
||||
out.writeIdentifier(c.subQuery.Alias())
|
||||
out.writeByte('.')
|
||||
out.writeIdentifier(c.DefaultAlias(), true)
|
||||
} else {
|
||||
if c.tableName != "" {
|
||||
out.writeIdentifier(c.tableName)
|
||||
out.writeByte('.')
|
||||
}
|
||||
|
||||
out.writeIdentifier(c.name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
type IColumnList interface {
|
||||
Projection
|
||||
IColumn
|
||||
|
||||
Columns() []Column
|
||||
}
|
||||
|
||||
func ColumnList(columns ...Column) IColumnList {
|
||||
return columnListImpl(columns)
|
||||
}
|
||||
|
||||
// ColumnList is redefined type to support list of columns as single Projection
|
||||
type columnListImpl []Column
|
||||
|
||||
func (cl columnListImpl) Columns() []Column {
|
||||
return cl
|
||||
}
|
||||
|
||||
func (cl columnListImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
newProjectionList := ProjectionList{}
|
||||
|
||||
for _, column := range cl {
|
||||
newProjectionList = append(newProjectionList, column.fromImpl(subQuery))
|
||||
}
|
||||
|
||||
return newProjectionList
|
||||
}
|
||||
|
||||
func (cl columnListImpl) serializeForProjection(statement StatementType, out *SqlBuilder) error {
|
||||
projections := ColumnListToProjectionList(cl)
|
||||
|
||||
err := SerializeProjectionList(statement, projections, out)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dummy column interface implementation
|
||||
|
||||
// Name is placeholder for ColumnList to implement Column interface
|
||||
func (cl columnListImpl) Name() string { return "" }
|
||||
|
||||
// TableName is placeholder for ColumnList to implement Column interface
|
||||
func (cl columnListImpl) TableName() string { return "" }
|
||||
func (cl columnListImpl) SetTableName(name string) {}
|
||||
func (cl columnListImpl) SetSubQuery(subQuery SelectTable) {}
|
||||
func (cl columnListImpl) DefaultAlias() string { return "" }
|
||||
14
internal/jet/column_test.go
Normal file
14
internal/jet/column_test.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package jet
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestColumn(t *testing.T) {
|
||||
column := newColumn("col", "", nil)
|
||||
column.expressionInterfaceImpl.parent = &column
|
||||
|
||||
assertClauseSerialize(t, column, "col")
|
||||
column.SetTableName("table1")
|
||||
assertClauseSerialize(t, column, "table1.col")
|
||||
assertProjectionSerialize(t, &column, `table1.col AS "table1.col"`)
|
||||
assertProjectionSerialize(t, column.AS("alias1"), `table1.col AS "alias1"`)
|
||||
}
|
||||
333
internal/jet/column_types.go
Normal file
333
internal/jet/column_types.go
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
package jet
|
||||
|
||||
// ColumnBool is interface for SQL boolean columns.
|
||||
type ColumnBool interface {
|
||||
BoolExpression
|
||||
IColumn
|
||||
|
||||
From(subQuery SelectTable) ColumnBool
|
||||
}
|
||||
|
||||
type boolColumnImpl struct {
|
||||
boolInterfaceImpl
|
||||
|
||||
columnImpl
|
||||
}
|
||||
|
||||
func (i *boolColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
newBoolColumn := BoolColumn(i.name)
|
||||
newBoolColumn.SetTableName(i.tableName)
|
||||
newBoolColumn.SetSubQuery(subQuery)
|
||||
|
||||
return newBoolColumn
|
||||
}
|
||||
|
||||
func (i *boolColumnImpl) From(subQuery SelectTable) ColumnBool {
|
||||
newBoolColumn := i.fromImpl(subQuery).(ColumnBool)
|
||||
|
||||
return newBoolColumn
|
||||
}
|
||||
|
||||
// BoolColumn creates named bool column.
|
||||
func BoolColumn(name string) ColumnBool {
|
||||
boolColumn := &boolColumnImpl{}
|
||||
boolColumn.columnImpl = newColumn(name, "", boolColumn)
|
||||
boolColumn.boolInterfaceImpl.parent = boolColumn
|
||||
|
||||
return boolColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnFloat is interface for SQL real, numeric, decimal or double precision column.
|
||||
type ColumnFloat interface {
|
||||
FloatExpression
|
||||
IColumn
|
||||
|
||||
From(subQuery SelectTable) ColumnFloat
|
||||
}
|
||||
|
||||
type floatColumnImpl struct {
|
||||
floatInterfaceImpl
|
||||
columnImpl
|
||||
}
|
||||
|
||||
func (i *floatColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
newFloatColumn := FloatColumn(i.name)
|
||||
newFloatColumn.SetTableName(i.tableName)
|
||||
newFloatColumn.SetSubQuery(subQuery)
|
||||
|
||||
return newFloatColumn
|
||||
}
|
||||
|
||||
func (i *floatColumnImpl) From(subQuery SelectTable) ColumnFloat {
|
||||
newFloatColumn := i.fromImpl(subQuery).(ColumnFloat)
|
||||
|
||||
return newFloatColumn
|
||||
}
|
||||
|
||||
// FloatColumn creates named float column.
|
||||
func FloatColumn(name string) ColumnFloat {
|
||||
floatColumn := &floatColumnImpl{}
|
||||
floatColumn.floatInterfaceImpl.parent = floatColumn
|
||||
floatColumn.columnImpl = newColumn(name, "", floatColumn)
|
||||
|
||||
return floatColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnInteger is interface for SQL smallint, integer, bigint columns.
|
||||
type ColumnInteger interface {
|
||||
IntegerExpression
|
||||
IColumn
|
||||
|
||||
From(subQuery SelectTable) ColumnInteger
|
||||
}
|
||||
|
||||
type integerColumnImpl struct {
|
||||
integerInterfaceImpl
|
||||
|
||||
columnImpl
|
||||
}
|
||||
|
||||
func (i *integerColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
newIntColumn := IntegerColumn(i.name)
|
||||
newIntColumn.SetTableName(i.tableName)
|
||||
newIntColumn.SetSubQuery(subQuery)
|
||||
|
||||
return newIntColumn
|
||||
}
|
||||
|
||||
func (i *integerColumnImpl) From(subQuery SelectTable) ColumnInteger {
|
||||
return i.fromImpl(subQuery).(ColumnInteger)
|
||||
}
|
||||
|
||||
// IntegerColumn creates named integer column.
|
||||
func IntegerColumn(name string) ColumnInteger {
|
||||
integerColumn := &integerColumnImpl{}
|
||||
integerColumn.integerInterfaceImpl.parent = integerColumn
|
||||
integerColumn.columnImpl = newColumn(name, "", integerColumn)
|
||||
|
||||
return integerColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnString is interface for SQL text, character, character varying
|
||||
// bytea, uuid columns and enums types.
|
||||
type ColumnString interface {
|
||||
StringExpression
|
||||
IColumn
|
||||
|
||||
From(subQuery SelectTable) ColumnString
|
||||
}
|
||||
|
||||
type stringColumnImpl struct {
|
||||
stringInterfaceImpl
|
||||
|
||||
columnImpl
|
||||
}
|
||||
|
||||
func (i *stringColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
newStrColumn := StringColumn(i.name)
|
||||
newStrColumn.SetTableName(i.tableName)
|
||||
newStrColumn.SetSubQuery(subQuery)
|
||||
|
||||
return newStrColumn
|
||||
}
|
||||
|
||||
func (i *stringColumnImpl) From(subQuery SelectTable) ColumnString {
|
||||
return i.fromImpl(subQuery).(ColumnString)
|
||||
}
|
||||
|
||||
// StringColumn creates named string column.
|
||||
func StringColumn(name string) ColumnString {
|
||||
stringColumn := &stringColumnImpl{}
|
||||
stringColumn.stringInterfaceImpl.parent = stringColumn
|
||||
stringColumn.columnImpl = newColumn(name, "", stringColumn)
|
||||
|
||||
return stringColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnTime is interface for SQL time column.
|
||||
type ColumnTime interface {
|
||||
TimeExpression
|
||||
IColumn
|
||||
|
||||
From(subQuery SelectTable) ColumnTime
|
||||
}
|
||||
|
||||
type timeColumnImpl struct {
|
||||
timeInterfaceImpl
|
||||
columnImpl
|
||||
}
|
||||
|
||||
func (i *timeColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
newTimeColumn := TimeColumn(i.name)
|
||||
newTimeColumn.SetTableName(i.tableName)
|
||||
newTimeColumn.SetSubQuery(subQuery)
|
||||
|
||||
return newTimeColumn
|
||||
}
|
||||
|
||||
func (i *timeColumnImpl) From(subQuery SelectTable) ColumnTime {
|
||||
return i.fromImpl(subQuery).(ColumnTime)
|
||||
}
|
||||
|
||||
// TimeColumn creates named time column
|
||||
func TimeColumn(name string) ColumnTime {
|
||||
timeColumn := &timeColumnImpl{}
|
||||
timeColumn.timeInterfaceImpl.parent = timeColumn
|
||||
timeColumn.columnImpl = newColumn(name, "", timeColumn)
|
||||
return timeColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnTimez is interface of SQL time with time zone columns.
|
||||
type ColumnTimez interface {
|
||||
TimezExpression
|
||||
IColumn
|
||||
|
||||
From(subQuery SelectTable) ColumnTimez
|
||||
}
|
||||
|
||||
type timezColumnImpl struct {
|
||||
timezInterfaceImpl
|
||||
|
||||
columnImpl
|
||||
}
|
||||
|
||||
func (i *timezColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
newTimezColumn := TimezColumn(i.name)
|
||||
newTimezColumn.SetTableName(i.tableName)
|
||||
newTimezColumn.SetSubQuery(subQuery)
|
||||
|
||||
return newTimezColumn
|
||||
}
|
||||
|
||||
func (i *timezColumnImpl) From(subQuery SelectTable) ColumnTimez {
|
||||
return i.fromImpl(subQuery).(ColumnTimez)
|
||||
}
|
||||
|
||||
// TimezColumn creates named time with time zone column.
|
||||
func TimezColumn(name string) ColumnTimez {
|
||||
timezColumn := &timezColumnImpl{}
|
||||
timezColumn.timezInterfaceImpl.parent = timezColumn
|
||||
timezColumn.columnImpl = newColumn(name, "", timezColumn)
|
||||
|
||||
return timezColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnTimestamp is interface of SQL timestamp columns.
|
||||
type ColumnTimestamp interface {
|
||||
TimestampExpression
|
||||
IColumn
|
||||
|
||||
From(subQuery SelectTable) ColumnTimestamp
|
||||
}
|
||||
|
||||
type timestampColumnImpl struct {
|
||||
timestampInterfaceImpl
|
||||
|
||||
columnImpl
|
||||
}
|
||||
|
||||
func (i *timestampColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
newTimestampColumn := TimestampColumn(i.name)
|
||||
newTimestampColumn.SetTableName(i.tableName)
|
||||
newTimestampColumn.SetSubQuery(subQuery)
|
||||
|
||||
return newTimestampColumn
|
||||
}
|
||||
|
||||
func (i *timestampColumnImpl) From(subQuery SelectTable) ColumnTimestamp {
|
||||
return i.fromImpl(subQuery).(ColumnTimestamp)
|
||||
}
|
||||
|
||||
// TimestampColumn creates named timestamp column
|
||||
func TimestampColumn(name string) ColumnTimestamp {
|
||||
timestampColumn := ×tampColumnImpl{}
|
||||
timestampColumn.timestampInterfaceImpl.parent = timestampColumn
|
||||
timestampColumn.columnImpl = newColumn(name, "", timestampColumn)
|
||||
|
||||
return timestampColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnTimestampz is interface of SQL timestamp with timezone columns.
|
||||
type ColumnTimestampz interface {
|
||||
TimestampzExpression
|
||||
IColumn
|
||||
|
||||
From(subQuery SelectTable) ColumnTimestampz
|
||||
}
|
||||
|
||||
type timestampzColumnImpl struct {
|
||||
timestampzInterfaceImpl
|
||||
|
||||
columnImpl
|
||||
}
|
||||
|
||||
func (i *timestampzColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
newTimestampzColumn := TimestampzColumn(i.name)
|
||||
newTimestampzColumn.SetTableName(i.tableName)
|
||||
newTimestampzColumn.SetSubQuery(subQuery)
|
||||
|
||||
return newTimestampzColumn
|
||||
}
|
||||
|
||||
func (i *timestampzColumnImpl) From(subQuery SelectTable) ColumnTimestampz {
|
||||
return i.fromImpl(subQuery).(ColumnTimestampz)
|
||||
}
|
||||
|
||||
// TimestampzColumn creates named timestamp with time zone column.
|
||||
func TimestampzColumn(name string) ColumnTimestampz {
|
||||
timestampzColumn := ×tampzColumnImpl{}
|
||||
timestampzColumn.timestampzInterfaceImpl.parent = timestampzColumn
|
||||
timestampzColumn.columnImpl = newColumn(name, "", timestampzColumn)
|
||||
|
||||
return timestampzColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnDate is interface of SQL date columns.
|
||||
type ColumnDate interface {
|
||||
DateExpression
|
||||
IColumn
|
||||
|
||||
From(subQuery SelectTable) ColumnDate
|
||||
}
|
||||
|
||||
type dateColumnImpl struct {
|
||||
dateInterfaceImpl
|
||||
|
||||
columnImpl
|
||||
}
|
||||
|
||||
func (i *dateColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
newDateColumn := DateColumn(i.name)
|
||||
newDateColumn.SetTableName(i.tableName)
|
||||
newDateColumn.SetSubQuery(subQuery)
|
||||
|
||||
return newDateColumn
|
||||
}
|
||||
|
||||
func (i *dateColumnImpl) From(subQuery SelectTable) ColumnDate {
|
||||
return i.fromImpl(subQuery).(ColumnDate)
|
||||
}
|
||||
|
||||
// DateColumn creates named date column.
|
||||
func DateColumn(name string) ColumnDate {
|
||||
dateColumn := &dateColumnImpl{}
|
||||
dateColumn.dateInterfaceImpl.parent = dateColumn
|
||||
dateColumn.columnImpl = newColumn(name, "", dateColumn)
|
||||
return dateColumn
|
||||
}
|
||||
45
internal/jet/column_types_test.go
Normal file
45
internal/jet/column_types_test.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var subQuery = table1.SELECT(table1ColFloat, table1ColInt).AsTable("sub_query")
|
||||
|
||||
func TestNewBoolColumn(t *testing.T) {
|
||||
boolColumn := BoolColumn("colBool").From(subQuery)
|
||||
assertClauseSerialize(t, boolColumn, `sub_query."colBool"`)
|
||||
assertClauseSerialize(t, boolColumn.EQ(Bool(true)), `(sub_query."colBool" = $1)`, true)
|
||||
assertProjectionSerialize(t, boolColumn, `sub_query."colBool" AS "colBool"`)
|
||||
|
||||
boolColumn2 := table1ColBool.From(subQuery)
|
||||
assertClauseSerialize(t, boolColumn2, `sub_query."table1.col_bool"`)
|
||||
assertClauseSerialize(t, boolColumn2.EQ(Bool(true)), `(sub_query."table1.col_bool" = $1)`, true)
|
||||
assertProjectionSerialize(t, boolColumn2, `sub_query."table1.col_bool" AS "table1.col_bool"`)
|
||||
}
|
||||
|
||||
func TestNewIntColumn(t *testing.T) {
|
||||
intColumn := IntegerColumn("col_int").From(subQuery)
|
||||
assertClauseSerialize(t, intColumn, `sub_query."col_int"`)
|
||||
assertClauseSerialize(t, intColumn.EQ(Int(12)), `(sub_query."col_int" = $1)`, int64(12))
|
||||
assertProjectionSerialize(t, intColumn, `sub_query."col_int" AS "col_int"`)
|
||||
|
||||
intColumn2 := table1ColInt.From(subQuery)
|
||||
assertClauseSerialize(t, intColumn2, `sub_query."table1.col_int"`)
|
||||
assertClauseSerialize(t, intColumn2.EQ(Int(14)), `(sub_query."table1.col_int" = $1)`, int64(14))
|
||||
assertProjectionSerialize(t, intColumn2, `sub_query."table1.col_int" AS "table1.col_int"`)
|
||||
|
||||
}
|
||||
|
||||
func TestNewFloatColumnColumn(t *testing.T) {
|
||||
floatColumn := FloatColumn("col_float").From(subQuery)
|
||||
assertClauseSerialize(t, floatColumn, `sub_query."col_float"`)
|
||||
assertClauseSerialize(t, floatColumn.EQ(Float(1.11)), `(sub_query."col_float" = $1)`, float64(1.11))
|
||||
assertProjectionSerialize(t, floatColumn, `sub_query."col_float" AS "col_float"`)
|
||||
|
||||
floatColumn2 := table1ColFloat.From(subQuery)
|
||||
assertClauseSerialize(t, floatColumn2, `sub_query."table1.col_float"`)
|
||||
assertClauseSerialize(t, floatColumn2.EQ(Float(2.22)), `(sub_query."table1.col_float" = $1)`, float64(2.22))
|
||||
assertProjectionSerialize(t, floatColumn2, `sub_query."table1.col_float" AS "table1.col_float"`)
|
||||
|
||||
}
|
||||
72
internal/jet/date_expression.go
Normal file
72
internal/jet/date_expression.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package jet
|
||||
|
||||
// DateExpression is interface for all SQL date expressions.
|
||||
type DateExpression interface {
|
||||
Expression
|
||||
|
||||
EQ(rhs DateExpression) BoolExpression
|
||||
NOT_EQ(rhs DateExpression) BoolExpression
|
||||
IS_DISTINCT_FROM(rhs DateExpression) BoolExpression
|
||||
IS_NOT_DISTINCT_FROM(rhs DateExpression) BoolExpression
|
||||
|
||||
LT(rhs DateExpression) BoolExpression
|
||||
LT_EQ(rhs DateExpression) BoolExpression
|
||||
GT(rhs DateExpression) BoolExpression
|
||||
GT_EQ(rhs DateExpression) BoolExpression
|
||||
}
|
||||
|
||||
type dateInterfaceImpl struct {
|
||||
parent DateExpression
|
||||
}
|
||||
|
||||
func (t *dateInterfaceImpl) EQ(rhs DateExpression) BoolExpression {
|
||||
return eq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *dateInterfaceImpl) NOT_EQ(rhs DateExpression) BoolExpression {
|
||||
return notEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *dateInterfaceImpl) IS_DISTINCT_FROM(rhs DateExpression) BoolExpression {
|
||||
return isDistinctFrom(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *dateInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs DateExpression) BoolExpression {
|
||||
return isNotDistinctFrom(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *dateInterfaceImpl) LT(rhs DateExpression) BoolExpression {
|
||||
return lt(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *dateInterfaceImpl) LT_EQ(rhs DateExpression) BoolExpression {
|
||||
return ltEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *dateInterfaceImpl) GT(rhs DateExpression) BoolExpression {
|
||||
return gt(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *dateInterfaceImpl) GT_EQ(rhs DateExpression) BoolExpression {
|
||||
return gtEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type dateExpressionWrapper struct {
|
||||
dateInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newDateExpressionWrap(expression Expression) DateExpression {
|
||||
dateExpressionWrap := dateExpressionWrapper{Expression: expression}
|
||||
dateExpressionWrap.dateInterfaceImpl.parent = &dateExpressionWrap
|
||||
return &dateExpressionWrap
|
||||
}
|
||||
|
||||
// DateExp is date expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as date expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func DateExp(expression Expression) DateExpression {
|
||||
return newDateExpressionWrap(expression)
|
||||
}
|
||||
110
internal/jet/delete_statement.go
Normal file
110
internal/jet/delete_statement.go
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/go-jet/jet/execution"
|
||||
)
|
||||
|
||||
// DeleteStatement is interface for SQL DELETE statement
|
||||
type DeleteStatement interface {
|
||||
Statement
|
||||
|
||||
WHERE(expression BoolExpression) DeleteStatement
|
||||
|
||||
RETURNING(projections ...Projection) DeleteStatement
|
||||
}
|
||||
|
||||
func newDeleteStatement(table WritableTable) DeleteStatement {
|
||||
return &deleteStatementImpl{
|
||||
table: table,
|
||||
}
|
||||
}
|
||||
|
||||
type deleteStatementImpl struct {
|
||||
table WritableTable
|
||||
where BoolExpression
|
||||
returning []Projection
|
||||
}
|
||||
|
||||
func (d *deleteStatementImpl) WHERE(expression BoolExpression) DeleteStatement {
|
||||
d.where = expression
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *deleteStatementImpl) RETURNING(projections ...Projection) DeleteStatement {
|
||||
d.returning = projections
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *deleteStatementImpl) accept(visitor visitor) {
|
||||
visitor.visit(d)
|
||||
|
||||
d.table.accept(visitor)
|
||||
}
|
||||
|
||||
func (d *deleteStatementImpl) serializeImpl(out *SqlBuilder) error {
|
||||
if d == nil {
|
||||
return errors.New("jet: delete statement is nil")
|
||||
}
|
||||
out.newLine()
|
||||
out.WriteString("DELETE FROM")
|
||||
|
||||
if d.table == nil {
|
||||
return errors.New("jet: nil tableName")
|
||||
}
|
||||
|
||||
if err := d.table.serialize(DeleteStatementType, out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.where == nil {
|
||||
return errors.New("jet: deleting without a WHERE clause")
|
||||
}
|
||||
|
||||
if err := out.writeWhere(DeleteStatementType, d.where); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := out.writeReturning(DeleteStatementType, d.returning); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *deleteStatementImpl) Sql(dialect ...Dialect) (query string, args []interface{}, err error) {
|
||||
queryData := &SqlBuilder{
|
||||
Dialect: detectDialect(d, dialect...),
|
||||
}
|
||||
|
||||
err = d.serializeImpl(queryData)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
query, args = queryData.finalize()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *deleteStatementImpl) DebugSql(dialect ...Dialect) (query string, err error) {
|
||||
return debugSql(d, dialect...)
|
||||
}
|
||||
|
||||
func (d *deleteStatementImpl) Query(db execution.DB, destination interface{}) error {
|
||||
return query(d, db, destination)
|
||||
}
|
||||
|
||||
func (d *deleteStatementImpl) QueryContext(context context.Context, db execution.DB, destination interface{}) error {
|
||||
return queryContext(context, d, db, destination)
|
||||
}
|
||||
|
||||
func (d *deleteStatementImpl) Exec(db execution.DB) (res sql.Result, err error) {
|
||||
return exec(d, db)
|
||||
}
|
||||
|
||||
func (d *deleteStatementImpl) ExecContext(context context.Context, db execution.DB) (res sql.Result, err error) {
|
||||
return execContext(context, d, db)
|
||||
}
|
||||
25
internal/jet/delete_statement_test.go
Normal file
25
internal/jet/delete_statement_test.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeleteUnconditionally(t *testing.T) {
|
||||
assertStatementErr(t, table1.DELETE(), `jet: deleting without a WHERE clause`)
|
||||
assertStatementErr(t, table1.DELETE().WHERE(nil), `jet: deleting without a WHERE clause`)
|
||||
}
|
||||
|
||||
func TestDeleteWithWhere(t *testing.T) {
|
||||
assertStatement(t, table1.DELETE().WHERE(table1Col1.EQ(Int(1))), `
|
||||
DELETE FROM db.table1
|
||||
WHERE table1.col1 = $1;
|
||||
`, int64(1))
|
||||
}
|
||||
|
||||
func TestDeleteWithWhereAndReturning(t *testing.T) {
|
||||
assertStatement(t, table1.DELETE().WHERE(table1Col1.EQ(Int(1))).RETURNING(table1Col1), `
|
||||
DELETE FROM db.table1
|
||||
WHERE table1.col1 = $1
|
||||
RETURNING table1.col1 AS "table1.col1";
|
||||
`, int64(1))
|
||||
}
|
||||
104
internal/jet/dialects.go
Normal file
104
internal/jet/dialects.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
package jet
|
||||
|
||||
var ANSII = NewDialect(DialectParams{ // just for tests
|
||||
AliasQuoteChar: '"',
|
||||
ArgumentPlaceholder: func(ord int) string {
|
||||
return "#"
|
||||
},
|
||||
})
|
||||
|
||||
type Dialect interface {
|
||||
Name() string
|
||||
PackageName() string
|
||||
SerializeOverride(operator string) SerializeOverride
|
||||
CastOverride() CastOverride
|
||||
AliasQuoteChar() byte
|
||||
IdentifierQuoteChar() byte
|
||||
ArgumentPlaceholder() QueryPlaceholderFunc
|
||||
UpdateAssigment() func(columns []IColumn, values []Clause, out *SqlBuilder) (err error)
|
||||
SupportsReturning() bool
|
||||
}
|
||||
|
||||
type SerializeFunc func(statement StatementType, out *SqlBuilder, options ...SerializeOption) error
|
||||
type SerializeOverride func(expressions ...Expression) SerializeFunc
|
||||
|
||||
type QueryPlaceholderFunc func(ord int) string
|
||||
|
||||
type CastOverride func(expression Expression, castType string) SerializeFunc
|
||||
type UpdateAssigmentFunc func(columns []IColumn, values []Clause, out *SqlBuilder) (err error)
|
||||
|
||||
type DialectParams struct {
|
||||
Name string
|
||||
PackageName string
|
||||
SerializeOverrides map[string]SerializeOverride
|
||||
CastOverride CastOverride
|
||||
AliasQuoteChar byte
|
||||
IdentifierQuoteChar byte
|
||||
ArgumentPlaceholder QueryPlaceholderFunc
|
||||
UpdateAssigment func(columns []IColumn, values []Clause, out *SqlBuilder) (err error)
|
||||
|
||||
SupportsReturning bool
|
||||
}
|
||||
|
||||
func NewDialect(params DialectParams) Dialect {
|
||||
return &dialectImpl{
|
||||
name: params.Name,
|
||||
packageName: params.PackageName,
|
||||
serializeOverrides: params.SerializeOverrides,
|
||||
castOverride: params.CastOverride,
|
||||
aliasQuoteChar: params.AliasQuoteChar,
|
||||
identifierQuoteChar: params.IdentifierQuoteChar,
|
||||
argumentPlaceholder: params.ArgumentPlaceholder,
|
||||
updateAssigment: params.UpdateAssigment,
|
||||
supportsReturning: params.SupportsReturning,
|
||||
}
|
||||
}
|
||||
|
||||
type dialectImpl struct {
|
||||
name string
|
||||
packageName string
|
||||
serializeOverrides map[string]SerializeOverride
|
||||
castOverride CastOverride
|
||||
aliasQuoteChar byte
|
||||
identifierQuoteChar byte
|
||||
argumentPlaceholder QueryPlaceholderFunc
|
||||
updateAssigment UpdateAssigmentFunc
|
||||
|
||||
supportsReturning bool
|
||||
}
|
||||
|
||||
func (d *dialectImpl) Name() string {
|
||||
return d.name
|
||||
}
|
||||
|
||||
func (d *dialectImpl) PackageName() string {
|
||||
return d.packageName
|
||||
}
|
||||
|
||||
func (d *dialectImpl) SerializeOverride(operator string) SerializeOverride {
|
||||
return d.serializeOverrides[operator]
|
||||
}
|
||||
|
||||
func (d *dialectImpl) CastOverride() CastOverride {
|
||||
return d.castOverride
|
||||
}
|
||||
|
||||
func (d *dialectImpl) AliasQuoteChar() byte {
|
||||
return d.aliasQuoteChar
|
||||
}
|
||||
|
||||
func (d *dialectImpl) IdentifierQuoteChar() byte {
|
||||
return d.identifierQuoteChar
|
||||
}
|
||||
|
||||
func (d *dialectImpl) ArgumentPlaceholder() QueryPlaceholderFunc {
|
||||
return d.argumentPlaceholder
|
||||
}
|
||||
|
||||
func (d *dialectImpl) UpdateAssigment() func(columns []IColumn, values []Clause, out *SqlBuilder) (err error) {
|
||||
return d.updateAssigment
|
||||
}
|
||||
|
||||
func (d *dialectImpl) SupportsReturning() bool {
|
||||
return d.supportsReturning
|
||||
}
|
||||
24
internal/jet/enum_value.go
Normal file
24
internal/jet/enum_value.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package jet
|
||||
|
||||
type enumValue struct {
|
||||
expressionInterfaceImpl
|
||||
stringInterfaceImpl
|
||||
noOpVisitorImpl
|
||||
|
||||
name string
|
||||
}
|
||||
|
||||
// NewEnumValue creates new named enum value
|
||||
func NewEnumValue(name string) StringExpression {
|
||||
enumValue := &enumValue{name: name}
|
||||
|
||||
enumValue.expressionInterfaceImpl.parent = enumValue
|
||||
enumValue.stringInterfaceImpl.parent = enumValue
|
||||
|
||||
return enumValue
|
||||
}
|
||||
|
||||
func (e enumValue) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
out.insertConstantArgument(e.name)
|
||||
return nil
|
||||
}
|
||||
223
internal/jet/expression.go
Normal file
223
internal/jet/expression.go
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Expression is common interface for all expressions.
|
||||
// Can be Bool, Int, Float, String, Date, Time, Timez, Timestamp or Timestampz expressions.
|
||||
type Expression interface {
|
||||
acceptsVisitor
|
||||
|
||||
expression
|
||||
}
|
||||
|
||||
type expression interface {
|
||||
Clause
|
||||
Projection
|
||||
groupByClause
|
||||
orderByClause
|
||||
|
||||
// Test expression whether it is a NULL value.
|
||||
IS_NULL() BoolExpression
|
||||
// Test expression whether it is a non-NULL value.
|
||||
IS_NOT_NULL() BoolExpression
|
||||
|
||||
// Check if this expressions matches any in expressions list
|
||||
IN(expressions ...Expression) BoolExpression
|
||||
// Check if this expressions is different of all expressions in expressions list
|
||||
NOT_IN(expressions ...Expression) BoolExpression
|
||||
|
||||
// The temporary alias name to assign to the expression
|
||||
AS(alias string) Projection
|
||||
|
||||
// Expression will be used to sort query result in ascending order
|
||||
ASC() orderByClause
|
||||
// Expression will be used to sort query result in ascending order
|
||||
DESC() orderByClause
|
||||
}
|
||||
|
||||
type expressionInterfaceImpl struct {
|
||||
parent Expression
|
||||
}
|
||||
|
||||
func (e *expressionInterfaceImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
return e.parent
|
||||
}
|
||||
|
||||
func (e *expressionInterfaceImpl) IS_NULL() BoolExpression {
|
||||
return newPostifxBoolExpression(e.parent, "IS NULL")
|
||||
}
|
||||
|
||||
func (e *expressionInterfaceImpl) IS_NOT_NULL() BoolExpression {
|
||||
return newPostifxBoolExpression(e.parent, "IS NOT NULL")
|
||||
}
|
||||
|
||||
func (e *expressionInterfaceImpl) IN(expressions ...Expression) BoolExpression {
|
||||
return newBinaryBoolOperator(e.parent, WRAP(expressions...), "IN")
|
||||
}
|
||||
|
||||
func (e *expressionInterfaceImpl) NOT_IN(expressions ...Expression) BoolExpression {
|
||||
return newBinaryBoolOperator(e.parent, WRAP(expressions...), "NOT IN")
|
||||
}
|
||||
|
||||
func (e *expressionInterfaceImpl) AS(alias string) Projection {
|
||||
return newAlias(e.parent, alias)
|
||||
}
|
||||
|
||||
func (e *expressionInterfaceImpl) ASC() orderByClause {
|
||||
return newOrderByClause(e.parent, true)
|
||||
}
|
||||
|
||||
func (e *expressionInterfaceImpl) DESC() orderByClause {
|
||||
return newOrderByClause(e.parent, false)
|
||||
}
|
||||
|
||||
func (e *expressionInterfaceImpl) serializeForGroupBy(statement StatementType, out *SqlBuilder) error {
|
||||
return e.parent.serialize(statement, out, noWrap)
|
||||
}
|
||||
|
||||
func (e *expressionInterfaceImpl) serializeForProjection(statement StatementType, out *SqlBuilder) error {
|
||||
return e.parent.serialize(statement, out, noWrap)
|
||||
}
|
||||
|
||||
func (e *expressionInterfaceImpl) serializeForOrderBy(statement StatementType, out *SqlBuilder) error {
|
||||
return e.parent.serialize(statement, out, noWrap)
|
||||
}
|
||||
|
||||
// Representation of binary operations (e.g. comparisons, arithmetic)
|
||||
type binaryOpExpression struct {
|
||||
lhs, rhs Expression
|
||||
operator string
|
||||
}
|
||||
|
||||
func newBinaryExpression(lhs, rhs Expression, operator string) binaryOpExpression {
|
||||
binaryExpression := binaryOpExpression{
|
||||
lhs: lhs,
|
||||
rhs: rhs,
|
||||
operator: operator,
|
||||
}
|
||||
|
||||
return binaryExpression
|
||||
}
|
||||
|
||||
func (c *binaryOpExpression) accept(visitor visitor) {
|
||||
c.lhs.accept(visitor)
|
||||
c.rhs.accept(visitor)
|
||||
}
|
||||
|
||||
func (c *binaryOpExpression) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) (err error) {
|
||||
if c == nil {
|
||||
return errors.New("jet: binary Expression is nil")
|
||||
}
|
||||
if c.lhs == nil {
|
||||
return errors.New("jet: nil lhs")
|
||||
}
|
||||
if c.rhs == nil {
|
||||
return errors.New("jet: nil rhs")
|
||||
}
|
||||
|
||||
wrap := !contains(options, noWrap)
|
||||
|
||||
if wrap {
|
||||
out.WriteString("(")
|
||||
}
|
||||
|
||||
if serializeOverride := out.Dialect.SerializeOverride(c.operator); serializeOverride != nil {
|
||||
|
||||
serializeOverrideFunc := serializeOverride(c.lhs, c.rhs)
|
||||
err = serializeOverrideFunc(statement, out, options...)
|
||||
|
||||
} else {
|
||||
if err := c.lhs.serialize(statement, out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.WriteString(c.operator)
|
||||
|
||||
if err := c.rhs.serialize(statement, out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if wrap {
|
||||
out.WriteString(")")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// A prefix operator Expression
|
||||
type prefixOpExpression struct {
|
||||
expression Expression
|
||||
operator string
|
||||
}
|
||||
|
||||
func newPrefixExpression(expression Expression, operator string) prefixOpExpression {
|
||||
prefixExpression := prefixOpExpression{
|
||||
expression: expression,
|
||||
operator: operator,
|
||||
}
|
||||
|
||||
return prefixExpression
|
||||
}
|
||||
|
||||
func (p *prefixOpExpression) accept(visitor visitor) {
|
||||
p.expression.accept(visitor)
|
||||
}
|
||||
|
||||
func (p *prefixOpExpression) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
if p == nil {
|
||||
return errors.New("jet: Prefix Expression is nil")
|
||||
}
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(p.operator)
|
||||
|
||||
if p.expression == nil {
|
||||
return errors.New("jet: nil prefix Expression")
|
||||
}
|
||||
if err := p.expression.serialize(statement, out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.WriteString(")")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// A postifx operator Expression
|
||||
type postfixOpExpression struct {
|
||||
expression Expression
|
||||
operator string
|
||||
}
|
||||
|
||||
func newPostfixOpExpression(expression Expression, operator string) postfixOpExpression {
|
||||
postfixOpExpression := postfixOpExpression{
|
||||
expression: expression,
|
||||
operator: operator,
|
||||
}
|
||||
|
||||
return postfixOpExpression
|
||||
}
|
||||
|
||||
func (p *postfixOpExpression) accept(visitor visitor) {
|
||||
p.expression.accept(visitor)
|
||||
}
|
||||
|
||||
func (p *postfixOpExpression) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
if p == nil {
|
||||
return errors.New("jet: Postifx operator Expression is nil")
|
||||
}
|
||||
|
||||
if p.expression == nil {
|
||||
return errors.New("jet: nil prefix Expression")
|
||||
}
|
||||
if err := p.expression.serialize(statement, out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.WriteString(p.operator)
|
||||
|
||||
return nil
|
||||
}
|
||||
58
internal/jet/expression_test.go
Normal file
58
internal/jet/expression_test.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExpressionIS_NULL(t *testing.T) {
|
||||
assertClauseSerialize(t, table2Col3.IS_NULL(), "table2.col3 IS NULL")
|
||||
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NULL(), "(table2.col3 + table2.col3) IS NULL")
|
||||
assertClauseSerializeErr(t, table2Col3.ADD(nil), "jet: nil rhs")
|
||||
}
|
||||
|
||||
func TestExpressionIS_NOT_NULL(t *testing.T) {
|
||||
assertClauseSerialize(t, table2Col3.IS_NOT_NULL(), "table2.col3 IS NOT NULL")
|
||||
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NOT_NULL(), "(table2.col3 + table2.col3) IS NOT NULL")
|
||||
}
|
||||
|
||||
func TestExpressionIS_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table2Col3.IS_DISTINCT_FROM(table2Col4), "(table2.col3 IS DISTINCT FROM table2.col4)")
|
||||
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_DISTINCT_FROM(Int(23)), "((table2.col3 + table2.col3) IS DISTINCT FROM $1)", int64(23))
|
||||
}
|
||||
|
||||
func TestExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table2Col3.IS_NOT_DISTINCT_FROM(table2Col4), "(table2.col3 IS NOT DISTINCT FROM table2.col4)")
|
||||
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NOT_DISTINCT_FROM(Int(23)), "((table2.col3 + table2.col3) IS NOT DISTINCT FROM $1)", int64(23))
|
||||
}
|
||||
|
||||
func TestIN(t *testing.T) {
|
||||
|
||||
assertClauseSerialize(t, Float(1.11).IN(table1.SELECT(table1Col1)),
|
||||
`($1 IN ((
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)))`, float64(1.11))
|
||||
|
||||
assertClauseSerialize(t, ROW(Int(12), table1Col1).IN(table2.SELECT(table2Col3, table3Col1)),
|
||||
`(ROW($1, table1.col1) IN ((
|
||||
SELECT table2.col3 AS "table2.col3",
|
||||
table3.col1 AS "table3.col1"
|
||||
FROM db.table2
|
||||
)))`, int64(12))
|
||||
}
|
||||
|
||||
func TestNOT_IN(t *testing.T) {
|
||||
|
||||
assertClauseSerialize(t, Float(1.11).NOT_IN(table1.SELECT(table1Col1)),
|
||||
`($1 NOT IN ((
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)))`, float64(1.11))
|
||||
|
||||
assertClauseSerialize(t, ROW(Int(12), table1Col1).NOT_IN(table2.SELECT(table2Col3, table3Col1)),
|
||||
`(ROW($1, table1.col1) NOT IN ((
|
||||
SELECT table2.col3 AS "table2.col3",
|
||||
table3.col1 AS "table3.col1"
|
||||
FROM db.table2
|
||||
)))`, int64(12))
|
||||
}
|
||||
124
internal/jet/float_expression.go
Normal file
124
internal/jet/float_expression.go
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
package jet
|
||||
|
||||
//FloatExpression is interface for SQL float columns
|
||||
type FloatExpression interface {
|
||||
Expression
|
||||
numericExpression
|
||||
|
||||
EQ(rhs FloatExpression) BoolExpression
|
||||
NOT_EQ(rhs FloatExpression) BoolExpression
|
||||
IS_DISTINCT_FROM(rhs FloatExpression) BoolExpression
|
||||
IS_NOT_DISTINCT_FROM(rhs FloatExpression) BoolExpression
|
||||
|
||||
LT(rhs FloatExpression) BoolExpression
|
||||
LT_EQ(rhs FloatExpression) BoolExpression
|
||||
GT(rhs FloatExpression) BoolExpression
|
||||
GT_EQ(rhs FloatExpression) BoolExpression
|
||||
|
||||
ADD(rhs NumericExpression) FloatExpression
|
||||
SUB(rhs NumericExpression) FloatExpression
|
||||
MUL(rhs NumericExpression) FloatExpression
|
||||
DIV(rhs NumericExpression) FloatExpression
|
||||
MOD(rhs NumericExpression) FloatExpression
|
||||
POW(rhs NumericExpression) FloatExpression
|
||||
}
|
||||
|
||||
type floatInterfaceImpl struct {
|
||||
numericExpressionImpl
|
||||
parent FloatExpression
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) EQ(rhs FloatExpression) BoolExpression {
|
||||
return eq(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) NOT_EQ(rhs FloatExpression) BoolExpression {
|
||||
return notEq(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) IS_DISTINCT_FROM(rhs FloatExpression) BoolExpression {
|
||||
return isDistinctFrom(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs FloatExpression) BoolExpression {
|
||||
return isNotDistinctFrom(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) GT(rhs FloatExpression) BoolExpression {
|
||||
return gt(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) GT_EQ(rhs FloatExpression) BoolExpression {
|
||||
return gtEq(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) LT(expression FloatExpression) BoolExpression {
|
||||
return lt(n.parent, expression)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) LT_EQ(expression FloatExpression) BoolExpression {
|
||||
return ltEq(n.parent, expression)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) ADD(expression NumericExpression) FloatExpression {
|
||||
return newBinaryFloatExpression(n.parent, expression, "+")
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) SUB(expression NumericExpression) FloatExpression {
|
||||
return newBinaryFloatExpression(n.parent, expression, "-")
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) MUL(expression NumericExpression) FloatExpression {
|
||||
return newBinaryFloatExpression(n.parent, expression, "*")
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) DIV(expression NumericExpression) FloatExpression {
|
||||
return newBinaryFloatExpression(n.parent, expression, "/")
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) MOD(expression NumericExpression) FloatExpression {
|
||||
return newBinaryFloatExpression(n.parent, expression, "%")
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) POW(expression NumericExpression) FloatExpression {
|
||||
return POW(n.parent, expression)
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
type binaryFloatExpression struct {
|
||||
expressionInterfaceImpl
|
||||
floatInterfaceImpl
|
||||
|
||||
binaryOpExpression
|
||||
}
|
||||
|
||||
func newBinaryFloatExpression(lhs, rhs Expression, operator string) FloatExpression {
|
||||
floatExpression := binaryFloatExpression{}
|
||||
|
||||
floatExpression.binaryOpExpression = newBinaryExpression(lhs, rhs, operator)
|
||||
|
||||
floatExpression.expressionInterfaceImpl.parent = &floatExpression
|
||||
floatExpression.floatInterfaceImpl.parent = &floatExpression
|
||||
|
||||
return &floatExpression
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type floatExpressionWrapper struct {
|
||||
floatInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newFloatExpressionWrap(expression Expression) FloatExpression {
|
||||
floatExpressionWrap := floatExpressionWrapper{Expression: expression}
|
||||
floatExpressionWrap.floatInterfaceImpl.parent = &floatExpressionWrap
|
||||
return &floatExpressionWrap
|
||||
}
|
||||
|
||||
// FloatExp is date expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as float expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func FloatExp(expression Expression) FloatExpression {
|
||||
return newFloatExpressionWrap(expression)
|
||||
}
|
||||
82
internal/jet/float_expression_test.go
Normal file
82
internal/jet/float_expression_test.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFloatExpressionEQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.EQ(table2ColFloat), "(table1.col_float = table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.EQ(Float(2.11)), "(table1.col_float = $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionNOT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.NOT_EQ(table2ColFloat), "(table1.col_float != table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.NOT_EQ(Float(2.11)), "(table1.col_float != $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionIS_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.IS_DISTINCT_FROM(table2ColFloat), "(table1.col_float IS DISTINCT FROM table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.IS_DISTINCT_FROM(Float(2.11)), "(table1.col_float IS DISTINCT FROM $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.IS_NOT_DISTINCT_FROM(table2ColFloat), "(table1.col_float IS NOT DISTINCT FROM table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.IS_NOT_DISTINCT_FROM(Float(2.11)), "(table1.col_float IS NOT DISTINCT FROM $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionGT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.GT(table2ColFloat), "(table1.col_float > table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.GT(Float(2.11)), "(table1.col_float > $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionGT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.GT_EQ(table2ColFloat), "(table1.col_float >= table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.GT_EQ(Float(2.11)), "(table1.col_float >= $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionLT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.LT(table2ColFloat), "(table1.col_float < table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.LT(Float(2.11)), "(table1.col_float < $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionLT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.LT_EQ(table2ColFloat), "(table1.col_float <= table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.LT_EQ(Float(2.11)), "(table1.col_float <= $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionADD(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.ADD(table2ColFloat), "(table1.col_float + table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.ADD(Float(2.11)), "(table1.col_float + $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionSUB(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.SUB(table2ColFloat), "(table1.col_float - table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.SUB(Float(2.11)), "(table1.col_float - $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionMUL(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.MUL(table2ColFloat), "(table1.col_float * table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.MUL(Float(2.11)), "(table1.col_float * $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionDIV(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.DIV(table2ColFloat), "(table1.col_float / table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.DIV(Float(2.11)), "(table1.col_float / $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionMOD(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.MOD(table2ColFloat), "(table1.col_float % table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.MOD(Float(2.11)), "(table1.col_float % $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionPOW(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.POW(table2ColFloat), "POW(table1.col_float, table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.POW(Float(2.11)), "POW(table1.col_float, $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExp(t *testing.T) {
|
||||
assertClauseSerialize(t, FloatExp(table1ColInt), "table1.col_int")
|
||||
assertClauseSerialize(t, FloatExp(table1ColInt.ADD(table3ColInt)), "(table1.col_int + table3.col_int)")
|
||||
assertClauseSerialize(t, FloatExp(table1ColInt.ADD(table3ColInt)).ADD(Float(11.11)),
|
||||
"((table1.col_int + table3.col_int) + $1)", float64(11.11))
|
||||
}
|
||||
653
internal/jet/func_expression.go
Normal file
653
internal/jet/func_expression.go
Normal file
|
|
@ -0,0 +1,653 @@
|
|||
package jet
|
||||
|
||||
import "errors"
|
||||
|
||||
// ROW is construct one table row from list of expressions.
|
||||
func ROW(expressions ...Expression) Expression {
|
||||
return newFunc("ROW", expressions, nil)
|
||||
}
|
||||
|
||||
// ------------------ Mathematical functions ---------------//
|
||||
|
||||
// ABSf calculates absolute value from float expression
|
||||
func ABSf(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("ABS", floatExpression)
|
||||
}
|
||||
|
||||
// ABSi calculates absolute value from int expression
|
||||
func ABSi(integerExpression IntegerExpression) IntegerExpression {
|
||||
return newIntegerFunc("ABS", integerExpression)
|
||||
}
|
||||
|
||||
func POW(base, exponent NumericExpression) FloatExpression {
|
||||
return NewFloatFunc("POW", base, exponent)
|
||||
}
|
||||
|
||||
func POWER(base, exponent NumericExpression) FloatExpression {
|
||||
return NewFloatFunc("POWER", base, exponent)
|
||||
}
|
||||
|
||||
// SQRT calculates square root of numeric expression
|
||||
func SQRT(numericExpression NumericExpression) FloatExpression {
|
||||
return NewFloatFunc("SQRT", numericExpression)
|
||||
}
|
||||
|
||||
// CBRT calculates cube root of numeric expression
|
||||
func CBRT(numericExpression NumericExpression) FloatExpression {
|
||||
return NewFloatFunc("CBRT", numericExpression)
|
||||
}
|
||||
|
||||
// CEIL calculates ceil of float expression
|
||||
func CEIL(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("CEIL", floatExpression)
|
||||
}
|
||||
|
||||
// FLOOR calculates floor of float expression
|
||||
func FLOOR(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("FLOOR", floatExpression)
|
||||
}
|
||||
|
||||
// ROUND calculates round of a float expressions with optional precision
|
||||
func ROUND(floatExpression FloatExpression, precision ...IntegerExpression) FloatExpression {
|
||||
if len(precision) > 0 {
|
||||
return NewFloatFunc("ROUND", floatExpression, precision[0])
|
||||
}
|
||||
return NewFloatFunc("ROUND", floatExpression)
|
||||
}
|
||||
|
||||
// SIGN returns sign of float expression
|
||||
func SIGN(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("SIGN", floatExpression)
|
||||
}
|
||||
|
||||
// TRUNC calculates trunc of float expression with optional precision
|
||||
func TRUNC(floatExpression FloatExpression, precision ...IntegerExpression) FloatExpression {
|
||||
if len(precision) > 0 {
|
||||
return NewFloatFunc("TRUNC", floatExpression, precision[0])
|
||||
}
|
||||
return NewFloatFunc("TRUNC", floatExpression)
|
||||
}
|
||||
|
||||
// LN calculates natural algorithm of float expression
|
||||
func LN(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("LN", floatExpression)
|
||||
}
|
||||
|
||||
// LOG calculates logarithm of float expression
|
||||
func LOG(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("LOG", floatExpression)
|
||||
}
|
||||
|
||||
// ----------------- Aggregate functions -------------------//
|
||||
|
||||
// AVG is aggregate function used to calculate avg value from numeric expression
|
||||
func AVG(numericExpression NumericExpression) FloatExpression {
|
||||
return NewFloatFunc("AVG", numericExpression)
|
||||
}
|
||||
|
||||
// BIT_AND is aggregate function used to calculates the bitwise AND of all non-null input values, or null if none.
|
||||
func BIT_AND(integerExpression IntegerExpression) IntegerExpression {
|
||||
return newIntegerFunc("BIT_AND", integerExpression)
|
||||
}
|
||||
|
||||
// BIT_OR is aggregate function used to calculates the bitwise OR of all non-null input values, or null if none.
|
||||
func BIT_OR(integerExpression IntegerExpression) IntegerExpression {
|
||||
return newIntegerFunc("BIT_OR", integerExpression)
|
||||
}
|
||||
|
||||
// BOOL_AND is aggregate function. Returns true if all input values are true, otherwise false
|
||||
func BOOL_AND(boolExpression BoolExpression) BoolExpression {
|
||||
return newBoolFunc("BOOL_AND", boolExpression)
|
||||
}
|
||||
|
||||
// BOOL_OR is aggregate function. Returns true if at least one input value is true, otherwise false
|
||||
func BOOL_OR(boolExpression BoolExpression) BoolExpression {
|
||||
return newBoolFunc("BOOL_OR", boolExpression)
|
||||
}
|
||||
|
||||
// COUNT is aggregate function. Returns number of input rows for which the value of expression is not null.
|
||||
func COUNT(expression Expression) IntegerExpression {
|
||||
return newIntegerFunc("COUNT", expression)
|
||||
}
|
||||
|
||||
// EVERY is aggregate function. Returns true if all input values are true, otherwise false
|
||||
func EVERY(boolExpression BoolExpression) BoolExpression {
|
||||
return newBoolFunc("EVERY", boolExpression)
|
||||
}
|
||||
|
||||
// MAXf is aggregate function. Returns maximum value of float expression across all input values
|
||||
func MAXf(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("MAX", floatExpression)
|
||||
}
|
||||
|
||||
// MAXi is aggregate function. Returns maximum value of int expression across all input values
|
||||
func MAXi(integerExpression IntegerExpression) IntegerExpression {
|
||||
return newIntegerFunc("MAX", integerExpression)
|
||||
}
|
||||
|
||||
// MINf is aggregate function. Returns minimum value of float expression across all input values
|
||||
func MINf(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("MIN", floatExpression)
|
||||
}
|
||||
|
||||
// MINi is aggregate function. Returns minimum value of int expression across all input values
|
||||
func MINi(integerExpression IntegerExpression) IntegerExpression {
|
||||
return newIntegerFunc("MIN", integerExpression)
|
||||
}
|
||||
|
||||
// SUMf is aggregate function. Returns sum of expression across all float expressions
|
||||
func SUMf(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("SUM", floatExpression)
|
||||
}
|
||||
|
||||
// SUMi is aggregate function. Returns sum of expression across all integer expression.
|
||||
func SUMi(integerExpression IntegerExpression) IntegerExpression {
|
||||
return newIntegerFunc("SUM", integerExpression)
|
||||
}
|
||||
|
||||
//------------ String functions ------------------//
|
||||
|
||||
// BIT_LENGTH returns number of bits in string expression
|
||||
func BIT_LENGTH(stringExpression StringExpression) IntegerExpression {
|
||||
return newIntegerFunc("BIT_LENGTH", stringExpression)
|
||||
}
|
||||
|
||||
// CHAR_LENGTH returns number of characters in string expression
|
||||
func CHAR_LENGTH(stringExpression StringExpression) IntegerExpression {
|
||||
return newIntegerFunc("CHAR_LENGTH", stringExpression)
|
||||
}
|
||||
|
||||
// OCTET_LENGTH returns number of bytes in string expression
|
||||
func OCTET_LENGTH(stringExpression StringExpression) IntegerExpression {
|
||||
return newIntegerFunc("OCTET_LENGTH", stringExpression)
|
||||
}
|
||||
|
||||
// LOWER returns string expression in lower case
|
||||
func LOWER(stringExpression StringExpression) StringExpression {
|
||||
return newStringFunc("LOWER", stringExpression)
|
||||
}
|
||||
|
||||
// UPPER returns string expression in upper case
|
||||
func UPPER(stringExpression StringExpression) StringExpression {
|
||||
return newStringFunc("UPPER", stringExpression)
|
||||
}
|
||||
|
||||
// BTRIM removes the longest string consisting only of characters
|
||||
// in characters (a space by default) from the start and end of string
|
||||
func BTRIM(stringExpression StringExpression, trimChars ...StringExpression) StringExpression {
|
||||
if len(trimChars) > 0 {
|
||||
return newStringFunc("BTRIM", stringExpression, trimChars[0])
|
||||
}
|
||||
return newStringFunc("BTRIM", stringExpression)
|
||||
}
|
||||
|
||||
// LTRIM removes the longest string containing only characters
|
||||
// from characters (a space by default) from the start of string
|
||||
func LTRIM(str StringExpression, trimChars ...StringExpression) StringExpression {
|
||||
if len(trimChars) > 0 {
|
||||
return newStringFunc("LTRIM", str, trimChars[0])
|
||||
}
|
||||
return newStringFunc("LTRIM", str)
|
||||
}
|
||||
|
||||
// RTRIM removes the longest string containing only characters
|
||||
// from characters (a space by default) from the end of string
|
||||
func RTRIM(str StringExpression, trimChars ...StringExpression) StringExpression {
|
||||
if len(trimChars) > 0 {
|
||||
return newStringFunc("RTRIM", str, trimChars[0])
|
||||
}
|
||||
return newStringFunc("RTRIM", str)
|
||||
}
|
||||
|
||||
// CHR returns character with the given code.
|
||||
func CHR(integerExpression IntegerExpression) StringExpression {
|
||||
return newStringFunc("CHR", integerExpression)
|
||||
}
|
||||
|
||||
//
|
||||
//func CONCAT(expressions ...Expression) StringExpression {
|
||||
// return newStringFunc("CONCAT", expressions...)
|
||||
//}
|
||||
//
|
||||
//func CONCAT_WS(expressions ...Expression) StringExpression {
|
||||
// return newStringFunc("CONCAT_WS", expressions...)
|
||||
//}
|
||||
|
||||
// CONVERT converts string to dest_encoding. The original encoding is
|
||||
// specified by src_encoding. The string must be valid in this encoding.
|
||||
func CONVERT(str StringExpression, srcEncoding StringExpression, destEncoding StringExpression) StringExpression {
|
||||
return newStringFunc("CONVERT", str, srcEncoding, destEncoding)
|
||||
}
|
||||
|
||||
// CONVERT_FROM converts string to the database encoding. The original
|
||||
// encoding is specified by src_encoding. The string must be valid in this encoding.
|
||||
func CONVERT_FROM(str StringExpression, srcEncoding StringExpression) StringExpression {
|
||||
return newStringFunc("CONVERT_FROM", str, srcEncoding)
|
||||
}
|
||||
|
||||
// CONVERT_TO converts string to dest_encoding.
|
||||
func CONVERT_TO(str StringExpression, toEncoding StringExpression) StringExpression {
|
||||
return newStringFunc("CONVERT_TO", str, toEncoding)
|
||||
}
|
||||
|
||||
// ENCODE encodes binary data into a textual representation.
|
||||
// Supported formats are: base64, hex, escape. escape converts zero bytes and
|
||||
// high-bit-set bytes to octal sequences (\nnn) and doubles backslashes.
|
||||
func ENCODE(data StringExpression, format StringExpression) StringExpression {
|
||||
return newStringFunc("ENCODE", data, format)
|
||||
}
|
||||
|
||||
// DECODE decodes binary data from textual representation in string.
|
||||
// Options for format are same as in encode.
|
||||
func DECODE(data StringExpression, format StringExpression) StringExpression {
|
||||
return newStringFunc("DECODE", data, format)
|
||||
}
|
||||
|
||||
//func FORMAT(formatStr StringExpression, formatArgs ...expressions) StringExpression {
|
||||
// args := []expressions{formatStr}
|
||||
// args = append(args, formatArgs...)
|
||||
// return newStringFunc("FORMAT", args...)
|
||||
//}
|
||||
|
||||
// INITCAP converts the first letter of each word to upper case
|
||||
// and the rest to lower case. Words are sequences of alphanumeric
|
||||
// characters separated by non-alphanumeric characters.
|
||||
func INITCAP(str StringExpression) StringExpression {
|
||||
return newStringFunc("INITCAP", str)
|
||||
}
|
||||
|
||||
// LEFT returns first n characters in the string.
|
||||
// When n is negative, return all but last |n| characters.
|
||||
func LEFT(str StringExpression, n IntegerExpression) StringExpression {
|
||||
return newStringFunc("LEFT", str, n)
|
||||
}
|
||||
|
||||
// RIGHT returns last n characters in the string.
|
||||
// When n is negative, return all but first |n| characters.
|
||||
func RIGHT(str StringExpression, n IntegerExpression) StringExpression {
|
||||
return newStringFunc("RIGHT", str, n)
|
||||
}
|
||||
|
||||
// LENGTH returns number of characters in string with a given encoding
|
||||
func LENGTH(str StringExpression, encoding ...StringExpression) StringExpression {
|
||||
if len(encoding) > 0 {
|
||||
return newStringFunc("LENGTH", str, encoding[0])
|
||||
}
|
||||
return newStringFunc("LENGTH", str)
|
||||
}
|
||||
|
||||
// LPAD fills up the string to length length by prepending the characters
|
||||
// fill (a space by default). If the string is already longer than length
|
||||
// then it is truncated (on the right).
|
||||
func LPAD(str StringExpression, length IntegerExpression, text ...StringExpression) StringExpression {
|
||||
if len(text) > 0 {
|
||||
return newStringFunc("LPAD", str, length, text[0])
|
||||
}
|
||||
|
||||
return newStringFunc("LPAD", str, length)
|
||||
}
|
||||
|
||||
// RPAD fills up the string to length length by appending the characters
|
||||
// fill (a space by default). If the string is already longer than length then it is truncated.
|
||||
func RPAD(str StringExpression, length IntegerExpression, text ...StringExpression) StringExpression {
|
||||
if len(text) > 0 {
|
||||
return newStringFunc("RPAD", str, length, text[0])
|
||||
}
|
||||
|
||||
return newStringFunc("RPAD", str, length)
|
||||
}
|
||||
|
||||
// MD5 calculates the MD5 hash of string, returning the result in hexadecimal
|
||||
func MD5(stringExpression StringExpression) StringExpression {
|
||||
return newStringFunc("MD5", stringExpression)
|
||||
}
|
||||
|
||||
// REPEAT repeats string the specified number of times
|
||||
func REPEAT(str StringExpression, n IntegerExpression) StringExpression {
|
||||
return newStringFunc("REPEAT", str, n)
|
||||
}
|
||||
|
||||
// REPLACE replaces all occurrences in string of substring from with substring to
|
||||
func REPLACE(text, from, to StringExpression) StringExpression {
|
||||
return newStringFunc("REPLACE", text, from, to)
|
||||
}
|
||||
|
||||
// REVERSE returns reversed string.
|
||||
func REVERSE(stringExpression StringExpression) StringExpression {
|
||||
return newStringFunc("REVERSE", stringExpression)
|
||||
}
|
||||
|
||||
// STRPOS returns location of specified substring (same as position(substring in string),
|
||||
// but note the reversed argument order)
|
||||
func STRPOS(str, substring StringExpression) IntegerExpression {
|
||||
return newIntegerFunc("STRPOS", str, substring)
|
||||
}
|
||||
|
||||
// SUBSTR extracts substring
|
||||
func SUBSTR(str StringExpression, from IntegerExpression, count ...IntegerExpression) StringExpression {
|
||||
if len(count) > 0 {
|
||||
return newStringFunc("SUBSTR", str, from, count[0])
|
||||
}
|
||||
return newStringFunc("SUBSTR", str, from)
|
||||
}
|
||||
|
||||
// TO_ASCII convert string to ASCII from another encoding
|
||||
func TO_ASCII(str StringExpression, encoding ...StringExpression) StringExpression {
|
||||
if len(encoding) > 0 {
|
||||
return newStringFunc("TO_ASCII", str, encoding[0])
|
||||
}
|
||||
return newStringFunc("TO_ASCII", str)
|
||||
}
|
||||
|
||||
// TO_HEX converts number to its equivalent hexadecimal representation
|
||||
func TO_HEX(number IntegerExpression) StringExpression {
|
||||
return newStringFunc("TO_HEX", number)
|
||||
}
|
||||
|
||||
//----------Data Type Formatting Functions ----------------------//
|
||||
|
||||
// TO_CHAR converts expression to string with format
|
||||
func TO_CHAR(expression Expression, format StringExpression) StringExpression {
|
||||
return newStringFunc("TO_CHAR", expression, format)
|
||||
}
|
||||
|
||||
// TO_DATE converts string to date using format
|
||||
func TO_DATE(dateStr, format StringExpression) DateExpression {
|
||||
return newDateFunc("TO_DATE", dateStr, format)
|
||||
}
|
||||
|
||||
// TO_NUMBER converts string to numeric using format
|
||||
func TO_NUMBER(floatStr, format StringExpression) FloatExpression {
|
||||
return NewFloatFunc("TO_NUMBER", floatStr, format)
|
||||
}
|
||||
|
||||
// TO_TIMESTAMP converts string to time stamp with time zone using format
|
||||
func TO_TIMESTAMP(timestampzStr, format StringExpression) TimestampzExpression {
|
||||
return newTimestampzFunc("TO_TIMESTAMP", timestampzStr, format)
|
||||
}
|
||||
|
||||
//----------------- Date/Time Functions and Operators ---------------//
|
||||
|
||||
// CURRENT_DATE returns current date
|
||||
func CURRENT_DATE() DateExpression {
|
||||
dateFunc := newDateFunc("CURRENT_DATE")
|
||||
dateFunc.noBrackets = true
|
||||
return dateFunc
|
||||
}
|
||||
|
||||
// CURRENT_TIME returns current time with time zone
|
||||
func CURRENT_TIME(precision ...int) TimezExpression {
|
||||
var timezFunc *timezFunc
|
||||
|
||||
if len(precision) > 0 {
|
||||
timezFunc = newTimezFunc("CURRENT_TIME", constLiteral(precision[0]))
|
||||
} else {
|
||||
timezFunc = newTimezFunc("CURRENT_TIME")
|
||||
}
|
||||
|
||||
timezFunc.noBrackets = true
|
||||
|
||||
return timezFunc
|
||||
}
|
||||
|
||||
// CURRENT_TIMESTAMP returns current timestamp with time zone
|
||||
func CURRENT_TIMESTAMP(precision ...int) TimestampzExpression {
|
||||
var timestampzFunc *timestampzFunc
|
||||
|
||||
if len(precision) > 0 {
|
||||
timestampzFunc = newTimestampzFunc("CURRENT_TIMESTAMP", constLiteral(precision[0]))
|
||||
} else {
|
||||
timestampzFunc = newTimestampzFunc("CURRENT_TIMESTAMP")
|
||||
}
|
||||
|
||||
timestampzFunc.noBrackets = true
|
||||
|
||||
return timestampzFunc
|
||||
}
|
||||
|
||||
// LOCALTIME returns local time of day using optional precision
|
||||
func LOCALTIME(precision ...int) TimeExpression {
|
||||
var timeFunc *timeFunc
|
||||
|
||||
if len(precision) > 0 {
|
||||
timeFunc = newTimeFunc("LOCALTIME", constLiteral(precision[0]))
|
||||
} else {
|
||||
timeFunc = newTimeFunc("LOCALTIME")
|
||||
}
|
||||
|
||||
timeFunc.noBrackets = true
|
||||
|
||||
return timeFunc
|
||||
}
|
||||
|
||||
// LOCALTIMESTAMP returns current date and time using optional precision
|
||||
func LOCALTIMESTAMP(precision ...int) TimestampExpression {
|
||||
var timestampFunc *timestampFunc
|
||||
|
||||
if len(precision) > 0 {
|
||||
timestampFunc = newTimestampFunc("LOCALTIMESTAMP", constLiteral(precision[0]))
|
||||
} else {
|
||||
timestampFunc = newTimestampFunc("LOCALTIMESTAMP")
|
||||
}
|
||||
|
||||
timestampFunc.noBrackets = true
|
||||
|
||||
return timestampFunc
|
||||
}
|
||||
|
||||
// NOW returns current date and time
|
||||
func NOW() TimestampzExpression {
|
||||
return newTimestampzFunc("NOW")
|
||||
}
|
||||
|
||||
// --------------- Conditional Expressions Functions -------------//
|
||||
|
||||
// COALESCE function returns the first of its arguments that is not null.
|
||||
func COALESCE(value Expression, values ...Expression) Expression {
|
||||
var allValues = []Expression{value}
|
||||
allValues = append(allValues, values...)
|
||||
return newFunc("COALESCE", allValues, nil)
|
||||
}
|
||||
|
||||
// NULLIF function returns a null value if value1 equals value2; otherwise it returns value1.
|
||||
func NULLIF(value1, value2 Expression) Expression {
|
||||
return newFunc("NULLIF", []Expression{value1, value2}, nil)
|
||||
}
|
||||
|
||||
// GREATEST selects the largest value from a list of expressions
|
||||
func GREATEST(value Expression, values ...Expression) Expression {
|
||||
var allValues = []Expression{value}
|
||||
allValues = append(allValues, values...)
|
||||
return newFunc("GREATEST", allValues, nil)
|
||||
}
|
||||
|
||||
// LEAST selects the smallest value from a list of expressions
|
||||
func LEAST(value Expression, values ...Expression) Expression {
|
||||
var allValues = []Expression{value}
|
||||
allValues = append(allValues, values...)
|
||||
return newFunc("LEAST", allValues, nil)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------//
|
||||
|
||||
type funcExpressionImpl struct {
|
||||
expressionInterfaceImpl
|
||||
|
||||
name string
|
||||
expressions []Expression
|
||||
noBrackets bool
|
||||
}
|
||||
|
||||
func newFunc(name string, expressions []Expression, parent Expression) *funcExpressionImpl {
|
||||
funcExp := &funcExpressionImpl{
|
||||
name: name,
|
||||
expressions: expressions,
|
||||
}
|
||||
|
||||
if parent != nil {
|
||||
funcExp.expressionInterfaceImpl.parent = parent
|
||||
} else {
|
||||
funcExp.expressionInterfaceImpl.parent = funcExp
|
||||
}
|
||||
|
||||
return funcExp
|
||||
}
|
||||
|
||||
func (f *funcExpressionImpl) accept(visitor visitor) {
|
||||
visitor.visit(f)
|
||||
|
||||
for _, exp := range f.expressions {
|
||||
exp.accept(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *funcExpressionImpl) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
if f == nil {
|
||||
return errors.New("jet: Function expressions is nil. ")
|
||||
}
|
||||
|
||||
addBrackets := !f.noBrackets || len(f.expressions) > 0
|
||||
|
||||
if addBrackets {
|
||||
out.WriteString(f.name + "(")
|
||||
} else {
|
||||
out.WriteString(f.name)
|
||||
}
|
||||
|
||||
err := serializeExpressionList(statement, f.expressions, ", ", out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if addBrackets {
|
||||
out.WriteString(")")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type boolFunc struct {
|
||||
funcExpressionImpl
|
||||
boolInterfaceImpl
|
||||
}
|
||||
|
||||
func newBoolFunc(name string, expressions ...Expression) BoolExpression {
|
||||
boolFunc := &boolFunc{}
|
||||
|
||||
boolFunc.funcExpressionImpl = *newFunc(name, expressions, boolFunc)
|
||||
boolFunc.boolInterfaceImpl.parent = boolFunc
|
||||
|
||||
return boolFunc
|
||||
}
|
||||
|
||||
type floatFunc struct {
|
||||
funcExpressionImpl
|
||||
floatInterfaceImpl
|
||||
}
|
||||
|
||||
func NewFloatFunc(name string, expressions ...Expression) FloatExpression {
|
||||
floatFunc := &floatFunc{}
|
||||
|
||||
floatFunc.funcExpressionImpl = *newFunc(name, expressions, floatFunc)
|
||||
floatFunc.floatInterfaceImpl.parent = floatFunc
|
||||
|
||||
return floatFunc
|
||||
}
|
||||
|
||||
type integerFunc struct {
|
||||
funcExpressionImpl
|
||||
integerInterfaceImpl
|
||||
}
|
||||
|
||||
func newIntegerFunc(name string, expressions ...Expression) IntegerExpression {
|
||||
floatFunc := &integerFunc{}
|
||||
|
||||
floatFunc.funcExpressionImpl = *newFunc(name, expressions, floatFunc)
|
||||
floatFunc.integerInterfaceImpl.parent = floatFunc
|
||||
|
||||
return floatFunc
|
||||
}
|
||||
|
||||
type stringFunc struct {
|
||||
funcExpressionImpl
|
||||
stringInterfaceImpl
|
||||
}
|
||||
|
||||
func newStringFunc(name string, expressions ...Expression) StringExpression {
|
||||
stringFunc := &stringFunc{}
|
||||
|
||||
stringFunc.funcExpressionImpl = *newFunc(name, expressions, stringFunc)
|
||||
stringFunc.stringInterfaceImpl.parent = stringFunc
|
||||
|
||||
return stringFunc
|
||||
}
|
||||
|
||||
type dateFunc struct {
|
||||
funcExpressionImpl
|
||||
dateInterfaceImpl
|
||||
}
|
||||
|
||||
func newDateFunc(name string, expressions ...Expression) *dateFunc {
|
||||
dateFunc := &dateFunc{}
|
||||
|
||||
dateFunc.funcExpressionImpl = *newFunc(name, expressions, dateFunc)
|
||||
dateFunc.dateInterfaceImpl.parent = dateFunc
|
||||
|
||||
return dateFunc
|
||||
}
|
||||
|
||||
type timeFunc struct {
|
||||
funcExpressionImpl
|
||||
timeInterfaceImpl
|
||||
}
|
||||
|
||||
func newTimeFunc(name string, expressions ...Expression) *timeFunc {
|
||||
timeFun := &timeFunc{}
|
||||
|
||||
timeFun.funcExpressionImpl = *newFunc(name, expressions, timeFun)
|
||||
timeFun.timeInterfaceImpl.parent = timeFun
|
||||
|
||||
return timeFun
|
||||
}
|
||||
|
||||
type timezFunc struct {
|
||||
funcExpressionImpl
|
||||
timezInterfaceImpl
|
||||
}
|
||||
|
||||
func newTimezFunc(name string, expressions ...Expression) *timezFunc {
|
||||
timezFun := &timezFunc{}
|
||||
|
||||
timezFun.funcExpressionImpl = *newFunc(name, expressions, timezFun)
|
||||
timezFun.timezInterfaceImpl.parent = timezFun
|
||||
|
||||
return timezFun
|
||||
}
|
||||
|
||||
type timestampFunc struct {
|
||||
funcExpressionImpl
|
||||
timestampInterfaceImpl
|
||||
}
|
||||
|
||||
func newTimestampFunc(name string, expressions ...Expression) *timestampFunc {
|
||||
timestampFunc := ×tampFunc{}
|
||||
|
||||
timestampFunc.funcExpressionImpl = *newFunc(name, expressions, timestampFunc)
|
||||
timestampFunc.timestampInterfaceImpl.parent = timestampFunc
|
||||
|
||||
return timestampFunc
|
||||
}
|
||||
|
||||
type timestampzFunc struct {
|
||||
funcExpressionImpl
|
||||
timestampzInterfaceImpl
|
||||
}
|
||||
|
||||
func newTimestampzFunc(name string, expressions ...Expression) *timestampzFunc {
|
||||
timestampzFunc := ×tampzFunc{}
|
||||
|
||||
timestampzFunc.funcExpressionImpl = *newFunc(name, expressions, timestampzFunc)
|
||||
timestampzFunc.timestampzInterfaceImpl.parent = timestampzFunc
|
||||
|
||||
return timestampzFunc
|
||||
}
|
||||
162
internal/jet/func_expression_test.go
Normal file
162
internal/jet/func_expression_test.go
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncAVG(t *testing.T) {
|
||||
assertClauseSerialize(t, AVG(table1ColFloat), "AVG(table1.col_float)")
|
||||
assertClauseSerialize(t, AVG(table1ColInt), "AVG(table1.col_int)")
|
||||
}
|
||||
|
||||
func TestFuncBIT_AND(t *testing.T) {
|
||||
assertClauseSerialize(t, BIT_AND(table1ColInt), "BIT_AND(table1.col_int)")
|
||||
}
|
||||
|
||||
func TestFuncBIT_OR(t *testing.T) {
|
||||
assertClauseSerialize(t, BIT_OR(table1ColInt), "BIT_OR(table1.col_int)")
|
||||
}
|
||||
|
||||
func TestFuncBOOL_AND(t *testing.T) {
|
||||
assertClauseSerialize(t, BOOL_AND(table1ColBool), "BOOL_AND(table1.col_bool)")
|
||||
}
|
||||
|
||||
func TestFuncBOOL_OR(t *testing.T) {
|
||||
assertClauseSerialize(t, BOOL_OR(table1ColBool), "BOOL_OR(table1.col_bool)")
|
||||
}
|
||||
|
||||
func TestFuncEVERY(t *testing.T) {
|
||||
assertClauseSerialize(t, EVERY(table1ColBool), "EVERY(table1.col_bool)")
|
||||
}
|
||||
|
||||
func TestFuncMIN(t *testing.T) {
|
||||
t.Run("float", func(t *testing.T) {
|
||||
assertClauseSerialize(t, MINf(table1ColFloat), "MIN(table1.col_float)")
|
||||
})
|
||||
|
||||
t.Run("integer", func(t *testing.T) {
|
||||
assertClauseSerialize(t, MINi(table1ColInt), "MIN(table1.col_int)")
|
||||
})
|
||||
}
|
||||
|
||||
func TestFuncMAX(t *testing.T) {
|
||||
t.Run("float", func(t *testing.T) {
|
||||
assertClauseSerialize(t, MAXf(table1ColFloat), "MAX(table1.col_float)")
|
||||
assertClauseSerialize(t, MAXf(Float(11.2222)), "MAX($1)", float64(11.2222))
|
||||
})
|
||||
|
||||
t.Run("integer", func(t *testing.T) {
|
||||
assertClauseSerialize(t, MAXi(table1ColInt), "MAX(table1.col_int)")
|
||||
assertClauseSerialize(t, MAXi(Int(11)), "MAX($1)", int64(11))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFuncSUM(t *testing.T) {
|
||||
t.Run("float", func(t *testing.T) {
|
||||
assertClauseSerialize(t, SUMf(table1ColFloat), "SUM(table1.col_float)")
|
||||
assertClauseSerialize(t, SUMf(Float(11.2222)), "SUM($1)", float64(11.2222))
|
||||
})
|
||||
|
||||
t.Run("integer", func(t *testing.T) {
|
||||
assertClauseSerialize(t, SUMi(table1ColInt), "SUM(table1.col_int)")
|
||||
assertClauseSerialize(t, SUMi(Int(11)), "SUM($1)", int64(11))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFuncCOUNT(t *testing.T) {
|
||||
assertClauseSerialize(t, COUNT(STAR), "COUNT(*)")
|
||||
assertClauseSerialize(t, COUNT(table1ColFloat), "COUNT(table1.col_float)")
|
||||
assertClauseSerialize(t, COUNT(Float(11.2222)), "COUNT($1)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncABS(t *testing.T) {
|
||||
t.Run("float", func(t *testing.T) {
|
||||
assertClauseSerialize(t, ABSf(table1ColFloat), "ABS(table1.col_float)")
|
||||
assertClauseSerialize(t, ABSf(Float(11.2222)), "ABS($1)", float64(11.2222))
|
||||
})
|
||||
|
||||
t.Run("integer", func(t *testing.T) {
|
||||
assertClauseSerialize(t, ABSi(table1ColInt), "ABS(table1.col_int)")
|
||||
assertClauseSerialize(t, ABSi(Int(11)), "ABS($1)", int64(11))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFuncSQRT(t *testing.T) {
|
||||
assertClauseSerialize(t, SQRT(table1ColFloat), "SQRT(table1.col_float)")
|
||||
assertClauseSerialize(t, SQRT(Float(11.2222)), "SQRT($1)", float64(11.2222))
|
||||
assertClauseSerialize(t, SQRT(table1ColInt), "SQRT(table1.col_int)")
|
||||
assertClauseSerialize(t, SQRT(Int(11)), "SQRT($1)", int64(11))
|
||||
}
|
||||
|
||||
func TestFuncCBRT(t *testing.T) {
|
||||
assertClauseSerialize(t, CBRT(table1ColFloat), "CBRT(table1.col_float)")
|
||||
assertClauseSerialize(t, CBRT(Float(11.2222)), "CBRT($1)", float64(11.2222))
|
||||
assertClauseSerialize(t, CBRT(table1ColInt), "CBRT(table1.col_int)")
|
||||
assertClauseSerialize(t, CBRT(Int(11)), "CBRT($1)", int64(11))
|
||||
}
|
||||
|
||||
func TestFuncCEIL(t *testing.T) {
|
||||
assertClauseSerialize(t, CEIL(table1ColFloat), "CEIL(table1.col_float)")
|
||||
assertClauseSerialize(t, CEIL(Float(11.2222)), "CEIL($1)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncFLOOR(t *testing.T) {
|
||||
assertClauseSerialize(t, FLOOR(table1ColFloat), "FLOOR(table1.col_float)")
|
||||
assertClauseSerialize(t, FLOOR(Float(11.2222)), "FLOOR($1)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncROUND(t *testing.T) {
|
||||
assertClauseSerialize(t, ROUND(table1ColFloat), "ROUND(table1.col_float)")
|
||||
assertClauseSerialize(t, ROUND(Float(11.2222)), "ROUND($1)", float64(11.2222))
|
||||
|
||||
assertClauseSerialize(t, ROUND(table1ColFloat, Int(2)), "ROUND(table1.col_float, $1)", int64(2))
|
||||
assertClauseSerialize(t, ROUND(Float(11.2222), Int(1)), "ROUND($1, $2)", float64(11.2222), int64(1))
|
||||
}
|
||||
|
||||
func TestFuncSIGN(t *testing.T) {
|
||||
assertClauseSerialize(t, SIGN(table1ColFloat), "SIGN(table1.col_float)")
|
||||
assertClauseSerialize(t, SIGN(Float(11.2222)), "SIGN($1)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncTRUNC(t *testing.T) {
|
||||
assertClauseSerialize(t, TRUNC(table1ColFloat), "TRUNC(table1.col_float)")
|
||||
assertClauseSerialize(t, TRUNC(Float(11.2222)), "TRUNC($1)", float64(11.2222))
|
||||
|
||||
assertClauseSerialize(t, TRUNC(table1ColFloat, Int(2)), "TRUNC(table1.col_float, $1)", int64(2))
|
||||
assertClauseSerialize(t, TRUNC(Float(11.2222), Int(1)), "TRUNC($1, $2)", float64(11.2222), int64(1))
|
||||
}
|
||||
|
||||
func TestFuncLN(t *testing.T) {
|
||||
assertClauseSerialize(t, LN(table1ColFloat), "LN(table1.col_float)")
|
||||
assertClauseSerialize(t, LN(Float(11.2222)), "LN($1)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncLOG(t *testing.T) {
|
||||
assertClauseSerialize(t, LOG(table1ColFloat), "LOG(table1.col_float)")
|
||||
assertClauseSerialize(t, LOG(Float(11.2222)), "LOG($1)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncCOALESCE(t *testing.T) {
|
||||
assertClauseSerialize(t, COALESCE(table1ColFloat), "COALESCE(table1.col_float)")
|
||||
assertClauseSerialize(t, COALESCE(Float(11.2222), NULL, String("str")), "COALESCE($1, NULL, $2)", float64(11.2222), "str")
|
||||
}
|
||||
|
||||
func TestFuncNULLIF(t *testing.T) {
|
||||
assertClauseSerialize(t, NULLIF(table1ColFloat, table2ColInt), "NULLIF(table1.col_float, table2.col_int)")
|
||||
assertClauseSerialize(t, NULLIF(Float(11.2222), NULL), "NULLIF($1, NULL)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncGREATEST(t *testing.T) {
|
||||
assertClauseSerialize(t, GREATEST(table1ColFloat), "GREATEST(table1.col_float)")
|
||||
assertClauseSerialize(t, GREATEST(Float(11.2222), NULL, String("str")), "GREATEST($1, NULL, $2)", float64(11.2222), "str")
|
||||
}
|
||||
|
||||
func TestFuncLEAST(t *testing.T) {
|
||||
assertClauseSerialize(t, LEAST(table1ColFloat), "LEAST(table1.col_float)")
|
||||
assertClauseSerialize(t, LEAST(Float(11.2222), NULL, String("str")), "LEAST($1, NULL, $2)", float64(11.2222), "str")
|
||||
}
|
||||
|
||||
func TestTO_ASCII(t *testing.T) {
|
||||
assertClauseSerialize(t, TO_ASCII(String("Karel")), `TO_ASCII($1)`, "Karel")
|
||||
assertClauseSerialize(t, TO_ASCII(String("Karel")), `TO_ASCII($1)`, "Karel")
|
||||
}
|
||||
5
internal/jet/group_by_clause.go
Normal file
5
internal/jet/group_by_clause.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package jet
|
||||
|
||||
type groupByClause interface {
|
||||
serializeForGroupBy(statement StatementType, out *SqlBuilder) error
|
||||
}
|
||||
178
internal/jet/insert_statement.go
Normal file
178
internal/jet/insert_statement.go
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/go-jet/jet/execution"
|
||||
"github.com/go-jet/jet/internal/utils"
|
||||
)
|
||||
|
||||
// InsertStatement is interface for SQL INSERT statements
|
||||
type InsertStatement interface {
|
||||
Statement
|
||||
|
||||
// Insert row of values
|
||||
VALUES(value interface{}, values ...interface{}) InsertStatement
|
||||
// Insert row of values, where value for each column is extracted from filed of structure data.
|
||||
// If data is not struct or there is no field for every column selected, this method will panic.
|
||||
MODEL(data interface{}) InsertStatement
|
||||
|
||||
MODELS(data interface{}) InsertStatement
|
||||
|
||||
QUERY(selectStatement SelectStatement) InsertStatement
|
||||
|
||||
RETURNING(projections ...Projection) InsertStatement
|
||||
}
|
||||
|
||||
func newInsertStatement(t WritableTable, columns []IColumn) InsertStatement {
|
||||
return &insertStatementImpl{
|
||||
table: t,
|
||||
columns: columns,
|
||||
}
|
||||
}
|
||||
|
||||
type insertStatementImpl struct {
|
||||
table WritableTable
|
||||
columns []IColumn
|
||||
rows [][]Clause
|
||||
query SelectStatement
|
||||
returning []Projection
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) VALUES(value interface{}, values ...interface{}) InsertStatement {
|
||||
i.rows = append(i.rows, unwindRowFromValues(value, values))
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) MODEL(data interface{}) InsertStatement {
|
||||
i.rows = append(i.rows, unwindRowFromModel(i.getColumns(), data))
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) MODELS(data interface{}) InsertStatement {
|
||||
i.rows = append(i.rows, unwindRowsFromModels(i.getColumns(), data)...)
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) RETURNING(projections ...Projection) InsertStatement {
|
||||
i.returning = projections
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) QUERY(selectStatement SelectStatement) InsertStatement {
|
||||
i.query = selectStatement
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) getColumns() []IColumn {
|
||||
if len(i.columns) > 0 {
|
||||
return i.columns
|
||||
}
|
||||
|
||||
return i.table.columns()
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) accept(visitor visitor) {
|
||||
visitor.visit(i)
|
||||
|
||||
i.table.accept(visitor)
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) DebugSql(dialect ...Dialect) (query string, err error) {
|
||||
return debugSql(i, dialect...)
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) Sql(dialect ...Dialect) (query string, args []interface{}, err error) {
|
||||
out := &SqlBuilder{
|
||||
Dialect: detectDialect(i, dialect...),
|
||||
}
|
||||
|
||||
out.newLine()
|
||||
out.WriteString("INSERT INTO")
|
||||
|
||||
if utils.IsNil(i.table) {
|
||||
return "", nil, errors.New("jet: table is nil")
|
||||
}
|
||||
|
||||
err = i.table.serialize(InsertStatementType, out)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(i.columns) > 0 {
|
||||
out.WriteString("(")
|
||||
|
||||
err = SerializeColumnNames(i.columns, out)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out.WriteString(")")
|
||||
}
|
||||
|
||||
if len(i.rows) == 0 && i.query == nil {
|
||||
return "", nil, errors.New("jet: no row values or query specified")
|
||||
}
|
||||
|
||||
if len(i.rows) > 0 && i.query != nil {
|
||||
return "", nil, errors.New("jet: only row values or query has to be specified")
|
||||
}
|
||||
|
||||
if len(i.rows) > 0 {
|
||||
out.WriteString("VALUES")
|
||||
|
||||
for rowIndex, row := range i.rows {
|
||||
if rowIndex > 0 {
|
||||
out.WriteString(",")
|
||||
}
|
||||
|
||||
out.increaseIdent()
|
||||
out.newLine()
|
||||
out.WriteString("(")
|
||||
|
||||
err = SerializeClauseList(InsertStatementType, row, out)
|
||||
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
out.writeByte(')')
|
||||
out.decreaseIdent()
|
||||
}
|
||||
}
|
||||
|
||||
if i.query != nil {
|
||||
err = i.query.serialize(InsertStatementType, out)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = out.writeReturning(InsertStatementType, i.returning); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
query, args = out.finalize()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) Query(db execution.DB, destination interface{}) error {
|
||||
return query(i, db, destination)
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) QueryContext(context context.Context, db execution.DB, destination interface{}) error {
|
||||
return queryContext(context, i, db, destination)
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) Exec(db execution.DB) (res sql.Result, err error) {
|
||||
return exec(i, db)
|
||||
}
|
||||
|
||||
func (i *insertStatementImpl) ExecContext(context context.Context, db execution.DB) (res sql.Result, err error) {
|
||||
return execContext(context, i, db)
|
||||
}
|
||||
147
internal/jet/insert_statement_test.go
Normal file
147
internal/jet/insert_statement_test.go
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"gotest.tools/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInvalidInsert(t *testing.T) {
|
||||
assertStatementErr(t, table1.INSERT(table1Col1), "jet: no row values or query specified")
|
||||
assertStatementErr(t, table1.INSERT(nil).VALUES(1), "jet: nil column in columns list")
|
||||
}
|
||||
|
||||
func TestInsertNilValue(t *testing.T) {
|
||||
assertStatement(t, table1.INSERT(table1Col1).VALUES(nil), `
|
||||
INSERT INTO db.table1 (col1) VALUES
|
||||
($1);
|
||||
`, nil)
|
||||
}
|
||||
|
||||
func TestInsertSingleValue(t *testing.T) {
|
||||
assertStatement(t, table1.INSERT(table1Col1).VALUES(1), `
|
||||
INSERT INTO db.table1 (col1) VALUES
|
||||
($1);
|
||||
`, int(1))
|
||||
}
|
||||
|
||||
func TestInsertWithColumnList(t *testing.T) {
|
||||
columnList := ColumnList(table3ColInt, table3StrCol)
|
||||
|
||||
assertStatement(t, table3.INSERT(columnList).VALUES(1, 3), `
|
||||
INSERT INTO db.table3 (col_int, col2) VALUES
|
||||
($1, $2);
|
||||
`, 1, 3)
|
||||
}
|
||||
|
||||
func TestInsertDate(t *testing.T) {
|
||||
date := time.Date(1999, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
|
||||
assertStatement(t, table1.INSERT(table1ColTime).VALUES(date), `
|
||||
INSERT INTO db.table1 (col_time) VALUES
|
||||
($1);
|
||||
`, date)
|
||||
}
|
||||
|
||||
func TestInsertMultipleValues(t *testing.T) {
|
||||
assertStatement(t, table1.INSERT(table1Col1, table1ColFloat, table1Col3).VALUES(1, 2, 3), `
|
||||
INSERT INTO db.table1 (col1, col_float, col3) VALUES
|
||||
($1, $2, $3);
|
||||
`, 1, 2, 3)
|
||||
}
|
||||
|
||||
func TestInsertMultipleRows(t *testing.T) {
|
||||
stmt := table1.INSERT(table1Col1, table1ColFloat).
|
||||
VALUES(1, 2).
|
||||
VALUES(11, 22).
|
||||
VALUES(111, 222)
|
||||
|
||||
assertStatement(t, stmt, `
|
||||
INSERT INTO db.table1 (col1, col_float) VALUES
|
||||
($1, $2),
|
||||
($3, $4),
|
||||
($5, $6);
|
||||
`, 1, 2, 11, 22, 111, 222)
|
||||
}
|
||||
|
||||
func TestInsertValuesFromModel(t *testing.T) {
|
||||
type Table1Model struct {
|
||||
Col1 *int
|
||||
ColFloat float64
|
||||
}
|
||||
|
||||
one := 1
|
||||
|
||||
toInsert := Table1Model{
|
||||
Col1: &one,
|
||||
ColFloat: 1.11,
|
||||
}
|
||||
|
||||
stmt := table1.INSERT(table1Col1, table1ColFloat).
|
||||
MODEL(toInsert).
|
||||
MODEL(&toInsert)
|
||||
|
||||
expectedSQL := `
|
||||
INSERT INTO db.table1 (col1, col_float) VALUES
|
||||
($1, $2),
|
||||
($3, $4);
|
||||
`
|
||||
|
||||
assertStatement(t, stmt, expectedSQL, int(1), float64(1.11), int(1), float64(1.11))
|
||||
}
|
||||
|
||||
func TestInsertValuesFromModelColumnMismatch(t *testing.T) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
assert.Equal(t, r, "missing struct field for column : col1")
|
||||
}()
|
||||
type Table1Model struct {
|
||||
Col1Prim int
|
||||
Col2 string
|
||||
}
|
||||
|
||||
newData := Table1Model{
|
||||
Col1Prim: 1,
|
||||
Col2: "one",
|
||||
}
|
||||
|
||||
table1.
|
||||
INSERT(table1Col1, table1ColFloat).
|
||||
MODEL(newData)
|
||||
}
|
||||
|
||||
func TestInsertFromNonStructModel(t *testing.T) {
|
||||
|
||||
defer func() {
|
||||
r := recover()
|
||||
assert.Equal(t, r, "argument mismatch: expected struct, got []int")
|
||||
}()
|
||||
|
||||
table2.INSERT(table2ColInt).MODEL([]int{})
|
||||
}
|
||||
|
||||
func TestInsertQuery(t *testing.T) {
|
||||
|
||||
stmt := table1.INSERT(table1Col1).
|
||||
QUERY(table1.SELECT(table1Col1))
|
||||
|
||||
var expectedSQL = `
|
||||
INSERT INTO db.table1 (col1) (
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
);
|
||||
`
|
||||
assertStatement(t, stmt, expectedSQL)
|
||||
}
|
||||
|
||||
func TestInsertDefaultValue(t *testing.T) {
|
||||
stmt := table1.INSERT(table1Col1, table1ColFloat).
|
||||
VALUES(DEFAULT, "two")
|
||||
|
||||
var expectedSQL = `
|
||||
INSERT INTO db.table1 (col1, col_float) VALUES
|
||||
(DEFAULT, $1);
|
||||
`
|
||||
|
||||
assertStatement(t, stmt, expectedSQL, "two")
|
||||
}
|
||||
207
internal/jet/integer_expression.go
Normal file
207
internal/jet/integer_expression.go
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
package jet
|
||||
|
||||
// IntegerExpression interface
|
||||
type IntegerExpression interface {
|
||||
Expression
|
||||
numericExpression
|
||||
|
||||
// Check if expression is equal to rhs
|
||||
EQ(rhs IntegerExpression) BoolExpression
|
||||
// Check if expression is not equal to rhs
|
||||
NOT_EQ(rhs IntegerExpression) BoolExpression
|
||||
// Check if expression is distinct from rhs
|
||||
IS_DISTINCT_FROM(rhs IntegerExpression) BoolExpression
|
||||
// Check if expression is not distinct from rhs
|
||||
IS_NOT_DISTINCT_FROM(rhs IntegerExpression) BoolExpression
|
||||
|
||||
// Check if expression is less then rhs
|
||||
LT(rhs IntegerExpression) BoolExpression
|
||||
// Check if expression is less then equal rhs
|
||||
LT_EQ(rhs IntegerExpression) BoolExpression
|
||||
// Check if expression is greater then rhs
|
||||
GT(rhs IntegerExpression) BoolExpression
|
||||
// Check if expression is greater then equal rhs
|
||||
GT_EQ(rhs IntegerExpression) BoolExpression
|
||||
|
||||
// expression + rhs
|
||||
ADD(rhs IntegerExpression) IntegerExpression
|
||||
// expression - rhs
|
||||
SUB(rhs IntegerExpression) IntegerExpression
|
||||
// expression * rhs
|
||||
MUL(rhs IntegerExpression) IntegerExpression
|
||||
// expression / rhs
|
||||
DIV(rhs IntegerExpression) IntegerExpression
|
||||
// expression % rhs
|
||||
MOD(rhs IntegerExpression) IntegerExpression
|
||||
// expression ^ rhs
|
||||
POW(rhs IntegerExpression) IntegerExpression
|
||||
|
||||
// expression & rhs
|
||||
BIT_AND(rhs IntegerExpression) IntegerExpression
|
||||
// expression | rhs
|
||||
BIT_OR(rhs IntegerExpression) IntegerExpression
|
||||
// expression # rhs
|
||||
BIT_XOR(rhs IntegerExpression) IntegerExpression
|
||||
// expression << rhs
|
||||
BIT_SHIFT_LEFT(shift IntegerExpression) IntegerExpression
|
||||
// expression >> rhs
|
||||
BIT_SHIFT_RIGHT(shift IntegerExpression) IntegerExpression
|
||||
}
|
||||
|
||||
type integerInterfaceImpl struct {
|
||||
numericExpressionImpl
|
||||
parent IntegerExpression
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) EQ(rhs IntegerExpression) BoolExpression {
|
||||
return eq(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) NOT_EQ(rhs IntegerExpression) BoolExpression {
|
||||
return notEq(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) IS_DISTINCT_FROM(rhs IntegerExpression) BoolExpression {
|
||||
return isDistinctFrom(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs IntegerExpression) BoolExpression {
|
||||
return isNotDistinctFrom(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) GT(rhs IntegerExpression) BoolExpression {
|
||||
return gt(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) GT_EQ(rhs IntegerExpression) BoolExpression {
|
||||
return gtEq(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) LT(expression IntegerExpression) BoolExpression {
|
||||
return lt(i.parent, expression)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) LT_EQ(expression IntegerExpression) BoolExpression {
|
||||
return ltEq(i.parent, expression)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) ADD(expression IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerExpression(i.parent, expression, "+")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) SUB(expression IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerExpression(i.parent, expression, "-")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) MUL(expression IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerExpression(i.parent, expression, "*")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) DIV(expression IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerExpression(i.parent, expression, "/")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) MOD(expression IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerExpression(i.parent, expression, "%")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) POW(expression IntegerExpression) IntegerExpression {
|
||||
return IntExp(POW(i.parent, expression))
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) BIT_AND(expression IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerExpression(i.parent, expression, "&")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) BIT_OR(expression IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerExpression(i.parent, expression, "|")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) BIT_XOR(expression IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerExpression(i.parent, expression, "#")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) BIT_SHIFT_LEFT(intExpression IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerExpression(i.parent, intExpression, "<<")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) BIT_SHIFT_RIGHT(intExpression IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerExpression(i.parent, intExpression, ">>")
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
type binaryIntegerExpression struct {
|
||||
expressionInterfaceImpl
|
||||
integerInterfaceImpl
|
||||
|
||||
binaryOpExpression
|
||||
}
|
||||
|
||||
func newBinaryIntegerExpression(lhs, rhs IntegerExpression, operator string) IntegerExpression {
|
||||
integerExpression := binaryIntegerExpression{}
|
||||
|
||||
integerExpression.expressionInterfaceImpl.parent = &integerExpression
|
||||
integerExpression.integerInterfaceImpl.parent = &integerExpression
|
||||
|
||||
integerExpression.binaryOpExpression = newBinaryExpression(lhs, rhs, operator)
|
||||
|
||||
return &integerExpression
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
type prefixIntegerOpExpression struct {
|
||||
expressionInterfaceImpl
|
||||
integerInterfaceImpl
|
||||
|
||||
prefixOpExpression
|
||||
}
|
||||
|
||||
func newPrefixIntegerOperator(expression IntegerExpression, operator string) IntegerExpression {
|
||||
integerExpression := prefixIntegerOpExpression{}
|
||||
integerExpression.prefixOpExpression = newPrefixExpression(expression, operator)
|
||||
|
||||
integerExpression.expressionInterfaceImpl.parent = &integerExpression
|
||||
integerExpression.integerInterfaceImpl.parent = &integerExpression
|
||||
|
||||
return &integerExpression
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
type prefixFloatOpExpression struct {
|
||||
expressionInterfaceImpl
|
||||
floatInterfaceImpl
|
||||
|
||||
prefixOpExpression
|
||||
}
|
||||
|
||||
func newPrefixFloatOperator(expression FloatExpression, operator string) FloatExpression {
|
||||
floatOpExpression := prefixFloatOpExpression{}
|
||||
floatOpExpression.prefixOpExpression = newPrefixExpression(expression, operator)
|
||||
|
||||
floatOpExpression.expressionInterfaceImpl.parent = &floatOpExpression
|
||||
floatOpExpression.floatInterfaceImpl.parent = &floatOpExpression
|
||||
|
||||
return &floatOpExpression
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
type integerExpressionWrapper struct {
|
||||
integerInterfaceImpl
|
||||
|
||||
Expression
|
||||
}
|
||||
|
||||
func newIntExpressionWrap(expression Expression) IntegerExpression {
|
||||
intExpressionWrap := integerExpressionWrapper{Expression: expression}
|
||||
|
||||
intExpressionWrap.integerInterfaceImpl.parent = &intExpressionWrap
|
||||
|
||||
return &intExpressionWrap
|
||||
}
|
||||
|
||||
// IntExp is int expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as int expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func IntExp(expression Expression) IntegerExpression {
|
||||
return newIntExpressionWrap(expression)
|
||||
}
|
||||
106
internal/jet/integer_expression_test.go
Normal file
106
internal/jet/integer_expression_test.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegerExpressionEQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.EQ(table2ColInt), "(table1.col_int = table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.EQ(Int(11)), "(table1.col_int = $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionNOT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.NOT_EQ(table2ColInt), "(table1.col_int != table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.NOT_EQ(Int(11)), "(table1.col_int != $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionGT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.GT(table2ColInt), "(table1.col_int > table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.GT(Int(11)), "(table1.col_int > $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionGT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.GT_EQ(table2ColInt), "(table1.col_int >= table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.GT_EQ(Int(11)), "(table1.col_int >= $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionLT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.LT(table2ColInt), "(table1.col_int < table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.LT(Int(11)), "(table1.col_int < $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionLT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.LT_EQ(table2ColInt), "(table1.col_int <= table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.LT_EQ(Int(11)), "(table1.col_int <= $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionADD(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.ADD(table2ColInt), "(table1.col_int + table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.ADD(Int(11)), "(table1.col_int + $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionSUB(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.SUB(table2ColInt), "(table1.col_int - table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.SUB(Int(11)), "(table1.col_int - $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionMUL(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.MUL(table2ColInt), "(table1.col_int * table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.MUL(Int(11)), "(table1.col_int * $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionDIV(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.DIV(table2ColInt), "(table1.col_int / table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.DIV(Int(11)), "(table1.col_int / $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionMOD(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.MOD(table2ColInt), "(table1.col_int % table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.MOD(Int(11)), "(table1.col_int % $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionPOW(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.POW(table2ColInt), "POW(table1.col_int, table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.POW(Int(11)), "POW(table1.col_int, $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionBIT_NOT(t *testing.T) {
|
||||
assertClauseSerialize(t, BIT_NOT(table2ColInt), "(~ table2.col_int)")
|
||||
assertClauseSerialize(t, BIT_NOT(Int(11)), "(~ $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionBIT_AND(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.BIT_AND(table2ColInt), "(table1.col_int & table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.BIT_AND(Int(11)), "(table1.col_int & $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionBIT_OR(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.BIT_OR(table2ColInt), "(table1.col_int | table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.BIT_OR(Int(11)), "(table1.col_int | $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionBIT_XOR(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.BIT_XOR(table2ColInt), "(table1.col_int # table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.BIT_XOR(Int(11)), "(table1.col_int # $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionBIT_SHIFT_LEFT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.BIT_SHIFT_LEFT(table2ColInt), "(table1.col_int << table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.BIT_SHIFT_LEFT(Int(11)), "(table1.col_int << $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionBIT_SHIFT_RIGHT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.BIT_SHIFT_RIGHT(table2ColInt), "(table1.col_int >> table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.BIT_SHIFT_RIGHT(Int(11)), "(table1.col_int >> $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionIntExp(t *testing.T) {
|
||||
assertClauseSerialize(t, IntExp(table1ColFloat), "table1.col_float")
|
||||
assertClauseSerialize(t, IntExp(table1ColFloat.ADD(table2ColFloat)).ADD(Int(11)),
|
||||
"((table1.col_float + table2.col_float) + $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpression_MINUSi(t *testing.T) {
|
||||
assertClauseSerialize(t, MINUSi(table2ColInt), "(- table2.col_int)")
|
||||
assertClauseSerialize(t, MINUSi(Int(3)), "(- $1)", int64(3))
|
||||
}
|
||||
21
internal/jet/keyword.go
Normal file
21
internal/jet/keyword.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package jet
|
||||
|
||||
const (
|
||||
// DEFAULT is jet equivalent of SQL DEFAULT
|
||||
DEFAULT keywordClause = "DEFAULT"
|
||||
)
|
||||
|
||||
var (
|
||||
// NULL is jet equivalent of SQL NULL
|
||||
NULL = newNullLiteral()
|
||||
// STAR is jet equivalent of SQL *
|
||||
STAR = newStarLiteral()
|
||||
)
|
||||
|
||||
type keywordClause string
|
||||
|
||||
func (k keywordClause) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
out.WriteString(string(k))
|
||||
|
||||
return nil
|
||||
}
|
||||
186
internal/jet/literal_expression.go
Normal file
186
internal/jet/literal_expression.go
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
package jet
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Representation of an escaped literal
|
||||
type literalExpression struct {
|
||||
expressionInterfaceImpl
|
||||
noOpVisitorImpl
|
||||
|
||||
value interface{}
|
||||
constant bool
|
||||
}
|
||||
|
||||
func literal(value interface{}, optionalConstant ...bool) *literalExpression {
|
||||
exp := literalExpression{value: value}
|
||||
|
||||
if len(optionalConstant) > 0 {
|
||||
exp.constant = optionalConstant[0]
|
||||
}
|
||||
|
||||
exp.expressionInterfaceImpl.parent = &exp
|
||||
|
||||
return &exp
|
||||
}
|
||||
|
||||
func constLiteral(value interface{}) *literalExpression {
|
||||
exp := literal(value)
|
||||
exp.constant = true
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
func (l literalExpression) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
if l.constant {
|
||||
out.insertConstantArgument(l.value)
|
||||
} else {
|
||||
out.insertParametrizedArgument(l.value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Int is constructor for integer expressions literals.
|
||||
func Int(value int64, constant ...bool) IntegerExpression {
|
||||
return IntExp(literal(value, constant...))
|
||||
}
|
||||
|
||||
// Bool creates new bool literal expression
|
||||
func Bool(value bool) BoolExpression {
|
||||
return BoolExp(literal(value))
|
||||
}
|
||||
|
||||
// Float creates new float literal expression
|
||||
func Float(value float64) FloatExpression {
|
||||
return FloatExp(literal(value))
|
||||
}
|
||||
|
||||
// String creates new string literal expression
|
||||
func String(value string) StringExpression {
|
||||
return StringExp(literal(value))
|
||||
}
|
||||
|
||||
// Time creates new time literal expression
|
||||
func Time(hour, minute, second, milliseconds int) TimeExpression {
|
||||
timeStr := fmt.Sprintf("%02d:%02d:%02d.%03d", hour, minute, second, milliseconds)
|
||||
|
||||
return TimeExp(literal(timeStr))
|
||||
}
|
||||
|
||||
// Timez creates new time with time zone literal expression
|
||||
func Timez(hour, minute, second, milliseconds, timezone int) TimezExpression {
|
||||
timeStr := fmt.Sprintf("%02d:%02d:%02d.%03d %+03d", hour, minute, second, milliseconds, timezone)
|
||||
|
||||
return TimezExp(literal(timeStr))
|
||||
}
|
||||
|
||||
// Timestamp creates new timestamp literal expression
|
||||
func Timestamp(year, month, day, hour, minute, second, milliseconds int) TimestampExpression {
|
||||
timeStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%03d", year, month, day, hour, minute, second, milliseconds)
|
||||
|
||||
return TimestampExp(literal(timeStr))
|
||||
}
|
||||
|
||||
// Timestampz creates new timestamp with time zone literal expression
|
||||
func Timestampz(year, month, day, hour, minute, second, milliseconds, timezone int) TimestampzExpression {
|
||||
timeStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%03d %+04d",
|
||||
year, month, day, hour, minute, second, milliseconds, timezone)
|
||||
|
||||
return TimestampzExp(literal(timeStr))
|
||||
}
|
||||
|
||||
//Date creates new date expression
|
||||
func Date(year, month, day int) DateExpression {
|
||||
timeStr := fmt.Sprintf("%04d-%02d-%02d", year, month, day)
|
||||
|
||||
return DateExp(literal(timeStr))
|
||||
}
|
||||
|
||||
//--------------------------------------------------//
|
||||
type nullLiteral struct {
|
||||
expressionInterfaceImpl
|
||||
noOpVisitorImpl
|
||||
}
|
||||
|
||||
func newNullLiteral() Expression {
|
||||
nullExpression := &nullLiteral{}
|
||||
|
||||
nullExpression.expressionInterfaceImpl.parent = nullExpression
|
||||
|
||||
return nullExpression
|
||||
}
|
||||
|
||||
func (n *nullLiteral) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
out.WriteString("NULL")
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------//
|
||||
type starLiteral struct {
|
||||
expressionInterfaceImpl
|
||||
noOpVisitorImpl
|
||||
}
|
||||
|
||||
func newStarLiteral() Expression {
|
||||
starExpression := &starLiteral{}
|
||||
|
||||
starExpression.expressionInterfaceImpl.parent = starExpression
|
||||
|
||||
return starExpression
|
||||
}
|
||||
|
||||
func (n *starLiteral) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
out.WriteString("*")
|
||||
return nil
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type wrap struct {
|
||||
expressionInterfaceImpl
|
||||
expressions []Expression
|
||||
}
|
||||
|
||||
func (n *wrap) accept(visitor visitor) {
|
||||
for _, exp := range n.expressions {
|
||||
exp.accept(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *wrap) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
out.WriteString("(")
|
||||
err := serializeExpressionList(statement, n.expressions, ", ", out)
|
||||
out.WriteString(")")
|
||||
return err
|
||||
}
|
||||
|
||||
// WRAP wraps list of expressions with brackets '(' and ')'
|
||||
func WRAP(expression ...Expression) Expression {
|
||||
wrap := &wrap{expressions: expression}
|
||||
wrap.expressionInterfaceImpl.parent = wrap
|
||||
|
||||
return wrap
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type rawExpression struct {
|
||||
expressionInterfaceImpl
|
||||
noOpVisitorImpl
|
||||
|
||||
raw string
|
||||
}
|
||||
|
||||
func (n *rawExpression) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
out.WriteString(n.raw)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RAW can be used for any unsupported functions, operators or expressions.
|
||||
// For example: RAW("current_database()")
|
||||
func RAW(raw string) Expression {
|
||||
rawExp := &rawExpression{raw: raw}
|
||||
rawExp.expressionInterfaceImpl.parent = rawExp
|
||||
|
||||
return rawExp
|
||||
}
|
||||
7
internal/jet/literal_expression_test.go
Normal file
7
internal/jet/literal_expression_test.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package jet
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRawExpression(t *testing.T) {
|
||||
assertClauseSerialize(t, RAW("current_database()"), "current_database()")
|
||||
}
|
||||
112
internal/jet/lock_statement.go
Normal file
112
internal/jet/lock_statement.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/go-jet/jet/execution"
|
||||
)
|
||||
|
||||
// TableLockMode is a type of possible SQL table lock
|
||||
type TableLockMode string
|
||||
|
||||
// LockStatement interface for SQL LOCK statement
|
||||
type LockStatement interface {
|
||||
Statement
|
||||
|
||||
IN(lockMode string) LockStatement
|
||||
NOWAIT() LockStatement
|
||||
}
|
||||
|
||||
type lockStatementImpl struct {
|
||||
tables []WritableTable
|
||||
lockMode string
|
||||
nowait bool
|
||||
}
|
||||
|
||||
// LOCK creates lock statement for list of tables.
|
||||
func LOCK(tables ...WritableTable) LockStatement {
|
||||
return &lockStatementImpl{
|
||||
tables: tables,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lockStatementImpl) IN(lockMode string) LockStatement {
|
||||
l.lockMode = lockMode
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *lockStatementImpl) NOWAIT() LockStatement {
|
||||
l.nowait = true
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *lockStatementImpl) DebugSql(dialect ...Dialect) (query string, err error) {
|
||||
return debugSql(l, dialect...)
|
||||
}
|
||||
|
||||
func (l *lockStatementImpl) accept(visitor visitor) {
|
||||
visitor.visit(l)
|
||||
|
||||
for _, table := range l.tables {
|
||||
table.accept(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lockStatementImpl) Sql(dialect ...Dialect) (query string, args []interface{}, err error) {
|
||||
if l == nil {
|
||||
return "", nil, errors.New("jet: nil Statement")
|
||||
}
|
||||
|
||||
if len(l.tables) == 0 {
|
||||
return "", nil, errors.New("jet: There is no table selected to be locked")
|
||||
}
|
||||
|
||||
out := &SqlBuilder{
|
||||
Dialect: detectDialect(l, dialect...),
|
||||
}
|
||||
|
||||
out.newLine()
|
||||
out.WriteString("LOCK TABLE")
|
||||
|
||||
for i, table := range l.tables {
|
||||
if i > 0 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
|
||||
err := table.serialize(LockStatementType, out)
|
||||
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if l.lockMode != "" {
|
||||
out.WriteString("IN")
|
||||
out.WriteString(string(l.lockMode))
|
||||
out.WriteString("MODE")
|
||||
}
|
||||
|
||||
if l.nowait {
|
||||
out.WriteString("NOWAIT")
|
||||
}
|
||||
|
||||
query, args = out.finalize()
|
||||
return
|
||||
}
|
||||
|
||||
func (l *lockStatementImpl) Query(db execution.DB, destination interface{}) error {
|
||||
return query(l, db, destination)
|
||||
}
|
||||
|
||||
func (l *lockStatementImpl) QueryContext(context context.Context, db execution.DB, destination interface{}) error {
|
||||
return queryContext(context, l, db, destination)
|
||||
}
|
||||
|
||||
func (l *lockStatementImpl) Exec(db execution.DB) (sql.Result, error) {
|
||||
return exec(l, db)
|
||||
}
|
||||
|
||||
func (l *lockStatementImpl) ExecContext(context context.Context, db execution.DB) (res sql.Result, err error) {
|
||||
return execContext(context, l, db)
|
||||
}
|
||||
15
internal/jet/numeric_expression.go
Normal file
15
internal/jet/numeric_expression.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package jet
|
||||
|
||||
// NumericExpression is common interface for all integer and float expressions
|
||||
type NumericExpression interface {
|
||||
Expression
|
||||
numericExpression
|
||||
}
|
||||
|
||||
type numericExpression interface {
|
||||
isNumericExpression()
|
||||
}
|
||||
|
||||
type numericExpressionImpl struct{}
|
||||
|
||||
func (n *numericExpressionImpl) isNumericExpression() {}
|
||||
192
internal/jet/operators.go
Normal file
192
internal/jet/operators.go
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
package jet
|
||||
|
||||
import "errors"
|
||||
|
||||
// --------- Arithmetic operators -------------//
|
||||
|
||||
// MINUSi changes the sign of the intExp.
|
||||
func MINUSi(intExp IntegerExpression) IntegerExpression {
|
||||
return newPrefixIntegerOperator(intExp, "-")
|
||||
}
|
||||
|
||||
// MINUSi changes the sign of the intExp.
|
||||
func MINUSf(floatExp FloatExpression) FloatExpression {
|
||||
return newPrefixFloatOperator(floatExp, "-")
|
||||
}
|
||||
|
||||
//----------- Logical operators ---------------//
|
||||
|
||||
// NOT returns negation of bool expression result
|
||||
func NOT(exp BoolExpression) BoolExpression {
|
||||
return newPrefixBoolOperator(exp, "NOT")
|
||||
}
|
||||
|
||||
// BIT_NOT inverts every bit in integer expression result
|
||||
func BIT_NOT(expr IntegerExpression) IntegerExpression {
|
||||
return newPrefixIntegerOperator(expr, "~")
|
||||
}
|
||||
|
||||
//----------- Comparison operators ---------------//
|
||||
|
||||
// EXISTS checks for existence of the rows in subQuery
|
||||
func EXISTS(subQuery SelectStatement) BoolExpression {
|
||||
return newPrefixBoolOperator(subQuery, "EXISTS")
|
||||
}
|
||||
|
||||
// Returns a representation of "a=b"
|
||||
func eq(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperator(lhs, rhs, "=")
|
||||
}
|
||||
|
||||
// Returns a representation of "a!=b"
|
||||
func notEq(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperator(lhs, rhs, "!=")
|
||||
}
|
||||
|
||||
func isDistinctFrom(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperator(lhs, rhs, "IS DISTINCT FROM")
|
||||
}
|
||||
|
||||
func isNotDistinctFrom(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperator(lhs, rhs, "IS NOT DISTINCT FROM")
|
||||
}
|
||||
|
||||
// Returns a representation of "a<b"
|
||||
func lt(lhs Expression, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperator(lhs, rhs, "<")
|
||||
}
|
||||
|
||||
// Returns a representation of "a<=b"
|
||||
func ltEq(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperator(lhs, rhs, "<=")
|
||||
}
|
||||
|
||||
// Returns a representation of "a>b"
|
||||
func gt(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperator(lhs, rhs, ">")
|
||||
}
|
||||
|
||||
// Returns a representation of "a>=b"
|
||||
func gtEq(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperator(lhs, rhs, ">=")
|
||||
}
|
||||
|
||||
// --------------- CASE operator -------------------//
|
||||
|
||||
// CaseOperator is interface for SQL case operator
|
||||
type CaseOperator interface {
|
||||
Expression
|
||||
|
||||
WHEN(condition Expression) CaseOperator
|
||||
THEN(then Expression) CaseOperator
|
||||
ELSE(els Expression) CaseOperator
|
||||
}
|
||||
|
||||
type caseOperatorImpl struct {
|
||||
expressionInterfaceImpl
|
||||
|
||||
expression Expression
|
||||
when []Expression
|
||||
then []Expression
|
||||
els Expression
|
||||
}
|
||||
|
||||
// CASE create CASE operator with optional list of expressions
|
||||
func CASE(expression ...Expression) CaseOperator {
|
||||
caseExp := &caseOperatorImpl{}
|
||||
|
||||
if len(expression) > 0 {
|
||||
caseExp.expression = expression[0]
|
||||
}
|
||||
|
||||
caseExp.expressionInterfaceImpl.parent = caseExp
|
||||
|
||||
return caseExp
|
||||
}
|
||||
|
||||
func (c *caseOperatorImpl) WHEN(when Expression) CaseOperator {
|
||||
c.when = append(c.when, when)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *caseOperatorImpl) THEN(then Expression) CaseOperator {
|
||||
c.then = append(c.then, then)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *caseOperatorImpl) ELSE(els Expression) CaseOperator {
|
||||
c.els = els
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *caseOperatorImpl) accept(visitor visitor) {
|
||||
visitor.visit(c)
|
||||
|
||||
c.expression.accept(visitor)
|
||||
|
||||
for _, when := range c.when {
|
||||
when.accept(visitor)
|
||||
}
|
||||
|
||||
for _, then := range c.then {
|
||||
then.accept(visitor)
|
||||
}
|
||||
|
||||
if c.els != nil {
|
||||
c.els.accept(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *caseOperatorImpl) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
if c == nil {
|
||||
return errors.New("jet: Case Expression is nil. ")
|
||||
}
|
||||
|
||||
out.WriteString("(CASE")
|
||||
|
||||
if c.expression != nil {
|
||||
err := c.expression.serialize(statement, out)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.when) == 0 || len(c.then) == 0 {
|
||||
return errors.New("jet: Invalid case Statement. There should be at least one when/then Expression pair. ")
|
||||
}
|
||||
|
||||
if len(c.when) != len(c.then) {
|
||||
return errors.New("jet: When and then Expression count mismatch. ")
|
||||
}
|
||||
|
||||
for i, when := range c.when {
|
||||
out.WriteString("WHEN")
|
||||
err := when.serialize(statement, out, noWrap)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.WriteString("THEN")
|
||||
err = c.then[i].serialize(statement, out, noWrap)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.els != nil {
|
||||
out.WriteString("ELSE")
|
||||
err := c.els.serialize(statement, out, noWrap)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteString("END)")
|
||||
|
||||
return nil
|
||||
}
|
||||
31
internal/jet/operators_test.go
Normal file
31
internal/jet/operators_test.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package jet
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestOperatorNOT(t *testing.T) {
|
||||
notExpression := NOT(Int(2).EQ(Int(1)))
|
||||
|
||||
assertClauseSerialize(t, NOT(table1ColBool), "(NOT table1.col_bool)")
|
||||
assertClauseSerialize(t, notExpression, "(NOT ($1 = $2))", int64(2), int64(1))
|
||||
assertProjectionSerialize(t, notExpression.AS("alias_not_expression"), `(NOT ($1 = $2)) AS "alias_not_expression"`, int64(2), int64(1))
|
||||
assertClauseSerialize(t, notExpression.AND(Int(4).EQ(Int(5))), `((NOT ($1 = $2)) AND ($3 = $4))`, int64(2), int64(1), int64(4), int64(5))
|
||||
}
|
||||
|
||||
func TestCase1(t *testing.T) {
|
||||
query := CASE().
|
||||
WHEN(table3Col1.EQ(Int(1))).THEN(table3Col1.ADD(Int(1))).
|
||||
WHEN(table3Col1.EQ(Int(2))).THEN(table3Col1.ADD(Int(2)))
|
||||
|
||||
assertClauseSerialize(t, query, `(CASE WHEN table3.col1 = $1 THEN table3.col1 + $2 WHEN table3.col1 = $3 THEN table3.col1 + $4 END)`,
|
||||
int64(1), int64(1), int64(2), int64(2))
|
||||
}
|
||||
|
||||
func TestCase2(t *testing.T) {
|
||||
query := CASE(table3Col1).
|
||||
WHEN(Int(1)).THEN(table3Col1.ADD(Int(1))).
|
||||
WHEN(Int(2)).THEN(table3Col1.ADD(Int(2))).
|
||||
ELSE(Int(0))
|
||||
|
||||
assertClauseSerialize(t, query, `(CASE table3.col1 WHEN $1 THEN table3.col1 + $2 WHEN $3 THEN table3.col1 + $4 ELSE $5 END)`,
|
||||
int64(1), int64(1), int64(2), int64(2), int64(0))
|
||||
}
|
||||
35
internal/jet/order_by_clause.go
Normal file
35
internal/jet/order_by_clause.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package jet
|
||||
|
||||
import "errors"
|
||||
|
||||
// OrderByClause
|
||||
type orderByClause interface {
|
||||
serializeForOrderBy(statement StatementType, out *SqlBuilder) error
|
||||
}
|
||||
|
||||
type orderByClauseImpl struct {
|
||||
expression Expression
|
||||
ascent bool
|
||||
}
|
||||
|
||||
func (o *orderByClauseImpl) serializeForOrderBy(statement StatementType, out *SqlBuilder) error {
|
||||
if o.expression == nil {
|
||||
return errors.New("jet: nil orderBy by clause")
|
||||
}
|
||||
|
||||
if err := o.expression.serializeForOrderBy(statement, out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.ascent {
|
||||
out.WriteString("ASC")
|
||||
} else {
|
||||
out.WriteString("DESC")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newOrderByClause(expression Expression, ascent bool) orderByClause {
|
||||
return &orderByClauseImpl{expression: expression, ascent: ascent}
|
||||
}
|
||||
33
internal/jet/projection.go
Normal file
33
internal/jet/projection.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package jet
|
||||
|
||||
type Projection interface {
|
||||
serializeForProjection(statement StatementType, out *SqlBuilder) error
|
||||
fromImpl(subQuery SelectTable) Projection
|
||||
}
|
||||
|
||||
func SerializeForProjection(projection Projection, statementType StatementType, out *SqlBuilder) error {
|
||||
return projection.serializeForProjection(statementType, out)
|
||||
}
|
||||
|
||||
// ProjectionList is a redefined type, so that ProjectionList can be used as a Projection.
|
||||
type ProjectionList []Projection
|
||||
|
||||
func (cl ProjectionList) fromImpl(subQuery SelectTable) Projection {
|
||||
newProjectionList := ProjectionList{}
|
||||
|
||||
for _, projection := range cl {
|
||||
newProjectionList = append(newProjectionList, projection.fromImpl(subQuery))
|
||||
}
|
||||
|
||||
return newProjectionList
|
||||
}
|
||||
|
||||
func (cl ProjectionList) serializeForProjection(statement StatementType, out *SqlBuilder) error {
|
||||
err := SerializeProjectionList(statement, cl, out)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
354
internal/jet/select_statement.go
Normal file
354
internal/jet/select_statement.go
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/go-jet/jet/execution"
|
||||
)
|
||||
|
||||
// SelectStatement is interface for SQL SELECT statements
|
||||
type SelectStatement interface {
|
||||
Statement
|
||||
expression
|
||||
|
||||
DISTINCT() SelectStatement
|
||||
FROM(table ReadableTable) SelectStatement
|
||||
WHERE(expression BoolExpression) SelectStatement
|
||||
GROUP_BY(groupByClauses ...groupByClause) SelectStatement
|
||||
HAVING(boolExpression BoolExpression) SelectStatement
|
||||
ORDER_BY(orderByClauses ...orderByClause) SelectStatement
|
||||
LIMIT(limit int64) SelectStatement
|
||||
OFFSET(offset int64) SelectStatement
|
||||
FOR(lock SelectLock) SelectStatement
|
||||
|
||||
UNION(rhs SelectStatement) SelectStatement
|
||||
UNION_ALL(rhs SelectStatement) SelectStatement
|
||||
INTERSECT(rhs SelectStatement) SelectStatement
|
||||
INTERSECT_ALL(rhs SelectStatement) SelectStatement
|
||||
EXCEPT(rhs SelectStatement) SelectStatement
|
||||
EXCEPT_ALL(rhs SelectStatement) SelectStatement
|
||||
|
||||
AsTable(alias string) SelectTable
|
||||
|
||||
projections() []Projection
|
||||
}
|
||||
|
||||
//SELECT creates new SelectStatement with list of projections
|
||||
func SELECT(projection1 Projection, projections ...Projection) SelectStatement {
|
||||
return newSelectStatement(nil, append([]Projection{projection1}, projections...))
|
||||
}
|
||||
|
||||
type selectStatementImpl struct {
|
||||
expressionInterfaceImpl
|
||||
parent SelectStatement
|
||||
|
||||
table ReadableTable
|
||||
distinct bool
|
||||
projectionList []Projection
|
||||
where BoolExpression
|
||||
groupBy []groupByClause
|
||||
having BoolExpression
|
||||
|
||||
orderBy []orderByClause
|
||||
limit, offset int64
|
||||
|
||||
lockFor SelectLock
|
||||
}
|
||||
|
||||
func newSelectStatement(table ReadableTable, projections []Projection) SelectStatement {
|
||||
newSelect := &selectStatementImpl{
|
||||
table: table,
|
||||
projectionList: projections,
|
||||
limit: -1,
|
||||
offset: -1,
|
||||
distinct: false,
|
||||
}
|
||||
|
||||
newSelect.expressionInterfaceImpl.parent = newSelect
|
||||
newSelect.parent = newSelect
|
||||
|
||||
return newSelect
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) FROM(table ReadableTable) SelectStatement {
|
||||
s.table = table
|
||||
return s.parent
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) AsTable(alias string) SelectTable {
|
||||
return newSelectTable(s.parent, alias)
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) WHERE(expression BoolExpression) SelectStatement {
|
||||
s.where = expression
|
||||
return s.parent
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) GROUP_BY(groupByClauses ...groupByClause) SelectStatement {
|
||||
s.groupBy = groupByClauses
|
||||
return s.parent
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) HAVING(expression BoolExpression) SelectStatement {
|
||||
s.having = expression
|
||||
return s.parent
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) ORDER_BY(clauses ...orderByClause) SelectStatement {
|
||||
s.orderBy = clauses
|
||||
return s.parent
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) OFFSET(offset int64) SelectStatement {
|
||||
s.offset = offset
|
||||
return s.parent
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) LIMIT(limit int64) SelectStatement {
|
||||
s.limit = limit
|
||||
return s.parent
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) DISTINCT() SelectStatement {
|
||||
s.distinct = true
|
||||
return s.parent
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) FOR(lock SelectLock) SelectStatement {
|
||||
s.lockFor = lock
|
||||
return s.parent
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) UNION(rhs SelectStatement) SelectStatement {
|
||||
return UNION(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) UNION_ALL(rhs SelectStatement) SelectStatement {
|
||||
return UNION_ALL(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) INTERSECT(rhs SelectStatement) SelectStatement {
|
||||
return INTERSECT(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) INTERSECT_ALL(rhs SelectStatement) SelectStatement {
|
||||
return INTERSECT_ALL(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) EXCEPT(rhs SelectStatement) SelectStatement {
|
||||
return EXCEPT(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) EXCEPT_ALL(rhs SelectStatement) SelectStatement {
|
||||
return EXCEPT_ALL(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) projections() []Projection {
|
||||
return s.projectionList
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
if s == nil {
|
||||
return errors.New("jet: Select expression is nil. ")
|
||||
}
|
||||
out.WriteString("(")
|
||||
|
||||
out.increaseIdent()
|
||||
err := s.serializeImpl(out)
|
||||
out.decreaseIdent()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.newLine()
|
||||
out.WriteString(")")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) serializeImpl(out *SqlBuilder) error {
|
||||
if s == nil {
|
||||
return errors.New("jet: Select expression is nil. ")
|
||||
}
|
||||
|
||||
out.newLine()
|
||||
out.WriteString("SELECT")
|
||||
|
||||
if s.distinct {
|
||||
out.WriteString("DISTINCT")
|
||||
}
|
||||
|
||||
if len(s.projectionList) == 0 {
|
||||
return errors.New("jet: no column selected for Projection")
|
||||
}
|
||||
|
||||
err := out.writeProjections(SelectStatementType, s.projectionList)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.table != nil {
|
||||
if err := out.writeFrom(SelectStatementType, s.table); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.where != nil {
|
||||
err := out.writeWhere(SelectStatementType, s.where)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if s.groupBy != nil && len(s.groupBy) > 0 {
|
||||
err := out.writeGroupBy(SelectStatementType, s.groupBy)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.having != nil {
|
||||
err := out.writeHaving(SelectStatementType, s.having)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.orderBy != nil {
|
||||
err := out.writeOrderBy(SelectStatementType, s.orderBy)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.limit >= 0 {
|
||||
out.newLine()
|
||||
out.WriteString("LIMIT")
|
||||
out.insertParametrizedArgument(s.limit)
|
||||
}
|
||||
|
||||
if s.offset >= 0 {
|
||||
out.newLine()
|
||||
out.WriteString("OFFSET")
|
||||
out.insertParametrizedArgument(s.offset)
|
||||
}
|
||||
|
||||
if s.lockFor != nil {
|
||||
out.newLine()
|
||||
out.WriteString("FOR")
|
||||
err := s.lockFor.serialize(SelectStatementType, out)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) accept(visitor visitor) {
|
||||
visitor.visit(s)
|
||||
|
||||
if s.table != nil {
|
||||
s.table.accept(visitor)
|
||||
}
|
||||
|
||||
if s.where != nil {
|
||||
s.where.accept(visitor)
|
||||
}
|
||||
|
||||
if s.having != nil {
|
||||
s.having.accept(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) Sql(dialect ...Dialect) (query string, args []interface{}, err error) {
|
||||
|
||||
queryData := &SqlBuilder{
|
||||
Dialect: detectDialect(s, dialect...),
|
||||
}
|
||||
|
||||
err = s.serializeImpl(queryData)
|
||||
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
query, args = queryData.finalize()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) DebugSql(dialect ...Dialect) (query string, err error) {
|
||||
return debugSql(s.parent, dialect...)
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) Query(db execution.DB, destination interface{}) error {
|
||||
return query(s.parent, db, destination)
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) QueryContext(context context.Context, db execution.DB, destination interface{}) error {
|
||||
return queryContext(context, s.parent, db, destination)
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) Exec(db execution.DB) (res sql.Result, err error) {
|
||||
return exec(s.parent, db)
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) ExecContext(context context.Context, db execution.DB) (res sql.Result, err error) {
|
||||
return execContext(context, s.parent, db)
|
||||
}
|
||||
|
||||
// SelectLock is interface for SELECT statement locks
|
||||
type SelectLock interface {
|
||||
Clause
|
||||
|
||||
NOWAIT() SelectLock
|
||||
SKIP_LOCKED() SelectLock
|
||||
}
|
||||
|
||||
type selectLockImpl struct {
|
||||
lockStrength string
|
||||
noWait, skipLocked bool
|
||||
}
|
||||
|
||||
func NewSelectLock(name string) func() SelectLock {
|
||||
return func() SelectLock {
|
||||
return newSelectLock(name)
|
||||
}
|
||||
}
|
||||
|
||||
func newSelectLock(lockStrength string) SelectLock {
|
||||
return &selectLockImpl{lockStrength: lockStrength}
|
||||
}
|
||||
|
||||
func (s *selectLockImpl) NOWAIT() SelectLock {
|
||||
s.noWait = true
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *selectLockImpl) SKIP_LOCKED() SelectLock {
|
||||
s.skipLocked = true
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *selectLockImpl) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
out.WriteString(s.lockStrength)
|
||||
|
||||
if s.noWait {
|
||||
out.WriteString("NOWAIT")
|
||||
}
|
||||
|
||||
if s.skipLocked {
|
||||
out.WriteString("SKIP LOCKED")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
197
internal/jet/select_statement_test.go
Normal file
197
internal/jet/select_statement_test.go
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
package jet
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestInvalidSelect(t *testing.T) {
|
||||
assertStatementErr(t, SELECT(nil), "jet: Projection is nil")
|
||||
}
|
||||
|
||||
func TestSelectColumnList(t *testing.T) {
|
||||
columnList := ColumnList(table2ColInt, table2ColFloat, table3ColInt)
|
||||
|
||||
assertStatement(t, SELECT(columnList).FROM(table2), `
|
||||
SELECT table2.col_int AS "table2.col_int",
|
||||
table2.col_float AS "table2.col_float",
|
||||
table3.col_int AS "table3.col_int"
|
||||
FROM db.table2;
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSelectLiterals(t *testing.T) {
|
||||
assertStatement(t, SELECT(Int(1), Float(2.2), Bool(false)).FROM(table1), `
|
||||
SELECT 1,
|
||||
2.2,
|
||||
FALSE
|
||||
FROM db.table1;
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSelectDistinct(t *testing.T) {
|
||||
assertStatement(t, SELECT(table1ColBool).DISTINCT(), `
|
||||
SELECT DISTINCT table1.col_bool AS "table1.col_bool";
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSelectFrom(t *testing.T) {
|
||||
assertStatement(t, SELECT(table1ColInt, table2ColFloat).FROM(table1), `
|
||||
SELECT table1.col_int AS "table1.col_int",
|
||||
table2.col_float AS "table2.col_float"
|
||||
FROM db.table1;
|
||||
`)
|
||||
assertStatement(t, SELECT(table1ColInt, table2ColFloat).FROM(table1.INNER_JOIN(table2, table1ColInt.EQ(table2ColInt))), `
|
||||
SELECT table1.col_int AS "table1.col_int",
|
||||
table2.col_float AS "table2.col_float"
|
||||
FROM db.table1
|
||||
INNER JOIN db.table2 ON (table1.col_int = table2.col_int);
|
||||
`)
|
||||
assertStatement(t, table1.INNER_JOIN(table2, table1ColInt.EQ(table2ColInt)).SELECT(table1ColInt, table2ColFloat), `
|
||||
SELECT table1.col_int AS "table1.col_int",
|
||||
table2.col_float AS "table2.col_float"
|
||||
FROM db.table1
|
||||
INNER JOIN db.table2 ON (table1.col_int = table2.col_int);
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSelectWhere(t *testing.T) {
|
||||
assertStatement(t, SELECT(table1ColInt).FROM(table1).WHERE(Bool(true)), `
|
||||
SELECT table1.col_int AS "table1.col_int"
|
||||
FROM db.table1
|
||||
WHERE $1;
|
||||
`, true)
|
||||
assertStatement(t, SELECT(table1ColInt).FROM(table1).WHERE(table1ColInt.GT_EQ(Int(10))), `
|
||||
SELECT table1.col_int AS "table1.col_int"
|
||||
FROM db.table1
|
||||
WHERE table1.col_int >= $1;
|
||||
`, int64(10))
|
||||
}
|
||||
|
||||
func TestSelectGroupBy(t *testing.T) {
|
||||
assertStatement(t, SELECT(table2ColInt).FROM(table2).GROUP_BY(table2ColFloat), `
|
||||
SELECT table2.col_int AS "table2.col_int"
|
||||
FROM db.table2
|
||||
GROUP BY table2.col_float;
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSelectHaving(t *testing.T) {
|
||||
assertStatement(t, SELECT(table3ColInt).FROM(table3).HAVING(table1ColBool.EQ(Bool(true))), `
|
||||
SELECT table3.col_int AS "table3.col_int"
|
||||
FROM db.table3
|
||||
HAVING table1.col_bool = $1;
|
||||
`, true)
|
||||
}
|
||||
|
||||
func TestSelectOrderBy(t *testing.T) {
|
||||
assertStatement(t, SELECT(table2ColFloat).FROM(table2).ORDER_BY(table2ColInt.DESC()), `
|
||||
SELECT table2.col_float AS "table2.col_float"
|
||||
FROM db.table2
|
||||
ORDER BY table2.col_int DESC;
|
||||
`)
|
||||
assertStatement(t, SELECT(table2ColFloat).FROM(table2).ORDER_BY(table2ColInt.DESC(), table2ColInt.ASC()), `
|
||||
SELECT table2.col_float AS "table2.col_float"
|
||||
FROM db.table2
|
||||
ORDER BY table2.col_int DESC, table2.col_int ASC;
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSelectLimitOffset(t *testing.T) {
|
||||
assertStatement(t, SELECT(table2ColInt).FROM(table2).LIMIT(10), `
|
||||
SELECT table2.col_int AS "table2.col_int"
|
||||
FROM db.table2
|
||||
LIMIT $1;
|
||||
`, int64(10))
|
||||
assertStatement(t, SELECT(table2ColInt).FROM(table2).LIMIT(10).OFFSET(2), `
|
||||
SELECT table2.col_int AS "table2.col_int"
|
||||
FROM db.table2
|
||||
LIMIT $1
|
||||
OFFSET $2;
|
||||
`, int64(10), int64(2))
|
||||
}
|
||||
|
||||
func TestSelectSets(t *testing.T) {
|
||||
select1 := SELECT(table1ColBool).FROM(table1)
|
||||
select2 := SELECT(table2ColBool).FROM(table2)
|
||||
|
||||
assertStatement(t, select1.UNION(select2), `
|
||||
(
|
||||
(
|
||||
SELECT table1.col_bool AS "table1.col_bool"
|
||||
FROM db.table1
|
||||
)
|
||||
UNION
|
||||
(
|
||||
SELECT table2.col_bool AS "table2.col_bool"
|
||||
FROM db.table2
|
||||
)
|
||||
);
|
||||
`)
|
||||
assertStatement(t, select1.UNION_ALL(select2), `
|
||||
(
|
||||
(
|
||||
SELECT table1.col_bool AS "table1.col_bool"
|
||||
FROM db.table1
|
||||
)
|
||||
UNION ALL
|
||||
(
|
||||
SELECT table2.col_bool AS "table2.col_bool"
|
||||
FROM db.table2
|
||||
)
|
||||
);
|
||||
`)
|
||||
|
||||
assertStatement(t, select1.INTERSECT(select2), `
|
||||
(
|
||||
(
|
||||
SELECT table1.col_bool AS "table1.col_bool"
|
||||
FROM db.table1
|
||||
)
|
||||
INTERSECT
|
||||
(
|
||||
SELECT table2.col_bool AS "table2.col_bool"
|
||||
FROM db.table2
|
||||
)
|
||||
);
|
||||
`)
|
||||
|
||||
assertStatement(t, select1.INTERSECT_ALL(select2), `
|
||||
(
|
||||
(
|
||||
SELECT table1.col_bool AS "table1.col_bool"
|
||||
FROM db.table1
|
||||
)
|
||||
INTERSECT ALL
|
||||
(
|
||||
SELECT table2.col_bool AS "table2.col_bool"
|
||||
FROM db.table2
|
||||
)
|
||||
);
|
||||
`)
|
||||
assertStatement(t, select1.EXCEPT(select2), `
|
||||
(
|
||||
(
|
||||
SELECT table1.col_bool AS "table1.col_bool"
|
||||
FROM db.table1
|
||||
)
|
||||
EXCEPT
|
||||
(
|
||||
SELECT table2.col_bool AS "table2.col_bool"
|
||||
FROM db.table2
|
||||
)
|
||||
);
|
||||
`)
|
||||
|
||||
assertStatement(t, select1.EXCEPT_ALL(select2), `
|
||||
(
|
||||
(
|
||||
SELECT table1.col_bool AS "table1.col_bool"
|
||||
FROM db.table1
|
||||
)
|
||||
EXCEPT ALL
|
||||
(
|
||||
SELECT table2.col_bool AS "table2.col_bool"
|
||||
FROM db.table2
|
||||
)
|
||||
);
|
||||
`)
|
||||
|
||||
}
|
||||
72
internal/jet/select_table.go
Normal file
72
internal/jet/select_table.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package jet
|
||||
|
||||
import "errors"
|
||||
|
||||
// SelectTable is interface for SELECT sub-queries
|
||||
type SelectTable interface {
|
||||
ReadableTable
|
||||
|
||||
Alias() string
|
||||
|
||||
AllColumns() ProjectionList
|
||||
}
|
||||
|
||||
type selectTableImpl struct {
|
||||
readableTableInterfaceImpl
|
||||
selectStmt SelectStatement
|
||||
alias string
|
||||
|
||||
projections []Projection
|
||||
}
|
||||
|
||||
func newSelectTable(selectStmt SelectStatement, alias string) SelectTable {
|
||||
expTable := &selectTableImpl{selectStmt: selectStmt, alias: alias}
|
||||
|
||||
expTable.readableTableInterfaceImpl.parent = expTable
|
||||
|
||||
for _, projection := range selectStmt.projections() {
|
||||
newProjection := projection.fromImpl(expTable)
|
||||
|
||||
expTable.projections = append(expTable.projections, newProjection)
|
||||
}
|
||||
|
||||
return expTable
|
||||
}
|
||||
|
||||
func (s *selectTableImpl) Alias() string {
|
||||
return s.alias
|
||||
}
|
||||
|
||||
func (s *selectTableImpl) columns() []IColumn {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *selectTableImpl) accept(visitor visitor) {
|
||||
visitor.visit(s)
|
||||
s.selectStmt.accept(visitor)
|
||||
}
|
||||
|
||||
func (s *selectTableImpl) dialect() Dialect {
|
||||
return detectDialect(s.selectStmt)
|
||||
}
|
||||
|
||||
func (s *selectTableImpl) AllColumns() ProjectionList {
|
||||
return s.projections
|
||||
}
|
||||
|
||||
func (s *selectTableImpl) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
if s == nil {
|
||||
return errors.New("jet: Expression table is nil. ")
|
||||
}
|
||||
|
||||
err := s.selectStmt.serialize(statement, out)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.WriteString("AS")
|
||||
out.writeIdentifier(s.alias)
|
||||
|
||||
return nil
|
||||
}
|
||||
193
internal/jet/set_statement.go
Normal file
193
internal/jet/set_statement.go
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// UNION effectively appends the result of sub-queries(select statements) into single query.
|
||||
// It eliminates duplicate rows from its result.
|
||||
func UNION(lhs, rhs SelectStatement, selects ...SelectStatement) SelectStatement {
|
||||
return newSetStatementImpl(union, false, toSelectList(lhs, rhs, selects...))
|
||||
}
|
||||
|
||||
// UNION_ALL effectively appends the result of sub-queries(select statements) into single query.
|
||||
// It does not eliminates duplicate rows from its result.
|
||||
func UNION_ALL(lhs, rhs SelectStatement, selects ...SelectStatement) SelectStatement {
|
||||
return newSetStatementImpl(union, true, toSelectList(lhs, rhs, selects...))
|
||||
}
|
||||
|
||||
// INTERSECT returns all rows that are in query results.
|
||||
// It eliminates duplicate rows from its result.
|
||||
func INTERSECT(lhs, rhs SelectStatement, selects ...SelectStatement) SelectStatement {
|
||||
return newSetStatementImpl(intersect, false, toSelectList(lhs, rhs, selects...))
|
||||
}
|
||||
|
||||
// INTERSECT_ALL returns all rows that are in query results.
|
||||
// It does not eliminates duplicate rows from its result.
|
||||
func INTERSECT_ALL(lhs, rhs SelectStatement, selects ...SelectStatement) SelectStatement {
|
||||
return newSetStatementImpl(intersect, true, toSelectList(lhs, rhs, selects...))
|
||||
}
|
||||
|
||||
// EXCEPT returns all rows that are in the result of query lhs but not in the result of query rhs.
|
||||
// It eliminates duplicate rows from its result.
|
||||
func EXCEPT(lhs, rhs SelectStatement) SelectStatement {
|
||||
return newSetStatementImpl(except, false, toSelectList(lhs, rhs))
|
||||
}
|
||||
|
||||
// EXCEPT_ALL returns all rows that are in the result of query lhs but not in the result of query rhs.
|
||||
// It does not eliminates duplicate rows from its result.
|
||||
func EXCEPT_ALL(lhs, rhs SelectStatement) SelectStatement {
|
||||
return newSetStatementImpl(except, true, toSelectList(lhs, rhs))
|
||||
}
|
||||
|
||||
func toSelectList(lhs, rhs SelectStatement, selects ...SelectStatement) []SelectStatement {
|
||||
return append([]SelectStatement{lhs, rhs}, selects...)
|
||||
}
|
||||
|
||||
const (
|
||||
union = "UNION"
|
||||
intersect = "INTERSECT"
|
||||
except = "EXCEPT"
|
||||
)
|
||||
|
||||
// Similar to selectStatementImpl, but less complete
|
||||
type setStatementImpl struct {
|
||||
selectStatementImpl
|
||||
|
||||
operator string
|
||||
all bool
|
||||
selects []SelectStatement
|
||||
}
|
||||
|
||||
func newSetStatementImpl(operator string, all bool, selects []SelectStatement) SelectStatement {
|
||||
setStatement := &setStatementImpl{
|
||||
operator: operator,
|
||||
all: all,
|
||||
selects: selects,
|
||||
}
|
||||
|
||||
setStatement.selectStatementImpl.expressionInterfaceImpl.parent = setStatement
|
||||
setStatement.selectStatementImpl.parent = setStatement
|
||||
setStatement.limit = -1
|
||||
setStatement.offset = -1
|
||||
|
||||
return setStatement
|
||||
}
|
||||
|
||||
func (s *setStatementImpl) accept(visitor visitor) {
|
||||
visitor.visit(s)
|
||||
|
||||
for _, selects := range s.selects {
|
||||
selects.accept(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *setStatementImpl) projections() []Projection {
|
||||
if len(s.selects) > 0 {
|
||||
return s.selects[0].projections()
|
||||
}
|
||||
return []Projection{}
|
||||
}
|
||||
|
||||
func (s *setStatementImpl) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
if s == nil {
|
||||
return errors.New("jet: Set expression is nil. ")
|
||||
}
|
||||
|
||||
wrap := s.orderBy != nil || s.limit >= 0 || s.offset >= 0
|
||||
|
||||
if wrap {
|
||||
out.WriteString("(")
|
||||
out.increaseIdent()
|
||||
}
|
||||
|
||||
err := s.serializeImpl(out)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if wrap {
|
||||
out.decreaseIdent()
|
||||
out.newLine()
|
||||
out.WriteString(")")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *setStatementImpl) serializeImpl(out *SqlBuilder) error {
|
||||
if s == nil {
|
||||
return errors.New("jet: Set expression is nil. ")
|
||||
}
|
||||
|
||||
if len(s.selects) < 2 {
|
||||
return errors.New("jet: UNION Statement must have at least two SELECT statements")
|
||||
}
|
||||
|
||||
out.newLine()
|
||||
out.WriteString("(")
|
||||
out.increaseIdent()
|
||||
|
||||
for i, selectStmt := range s.selects {
|
||||
out.newLine()
|
||||
if i > 0 {
|
||||
out.WriteString(s.operator)
|
||||
|
||||
if s.all {
|
||||
out.WriteString("ALL")
|
||||
}
|
||||
out.newLine()
|
||||
}
|
||||
|
||||
if selectStmt == nil {
|
||||
return errors.New("jet: select statement is nil")
|
||||
}
|
||||
|
||||
err := selectStmt.serialize(SetStatementType, out)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
out.decreaseIdent()
|
||||
out.newLine()
|
||||
out.WriteString(")")
|
||||
|
||||
if s.orderBy != nil {
|
||||
err := out.writeOrderBy(SetStatementType, s.orderBy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.limit >= 0 {
|
||||
out.newLine()
|
||||
out.WriteString("LIMIT")
|
||||
out.insertParametrizedArgument(s.limit)
|
||||
}
|
||||
|
||||
if s.offset >= 0 {
|
||||
out.newLine()
|
||||
out.WriteString("OFFSET")
|
||||
out.insertParametrizedArgument(s.offset)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *setStatementImpl) Sql(dialect ...Dialect) (query string, args []interface{}, err error) {
|
||||
queryData := &SqlBuilder{
|
||||
Dialect: detectDialect(s, dialect...),
|
||||
}
|
||||
|
||||
err = s.serializeImpl(queryData)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
query, args = queryData.finalize()
|
||||
return
|
||||
}
|
||||
301
internal/jet/set_statement_test.go
Normal file
301
internal/jet/set_statement_test.go
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"gotest.tools/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnionTwoSelect(t *testing.T) {
|
||||
var expectedSQL = `
|
||||
(
|
||||
(
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)
|
||||
UNION
|
||||
(
|
||||
SELECT table2.col3 AS "table2.col3"
|
||||
FROM db.table2
|
||||
)
|
||||
);
|
||||
`
|
||||
unionStmt1 := table1.
|
||||
SELECT(table1Col1).
|
||||
UNION(
|
||||
table2.SELECT(table2Col3),
|
||||
)
|
||||
|
||||
unionStmt2 := UNION(table1.SELECT(table1Col1), table2.SELECT(table2Col3))
|
||||
|
||||
assertStatement(t, unionStmt1, expectedSQL)
|
||||
assertStatement(t, unionStmt2, expectedSQL)
|
||||
}
|
||||
|
||||
func TestUnionNilSelect(t *testing.T) {
|
||||
unionStmt := table1.
|
||||
SELECT(table1Col1).
|
||||
UNION(nil)
|
||||
|
||||
assertStatementErr(t, unionStmt, "jet: select statement is nil")
|
||||
}
|
||||
|
||||
func TestUnionThreeSelect1(t *testing.T) {
|
||||
|
||||
unionStmt1 := table1.SELECT(table1Col1).
|
||||
UNION(
|
||||
table2.SELECT(table2Col3),
|
||||
).
|
||||
UNION(
|
||||
table3.SELECT(table3Col1),
|
||||
)
|
||||
|
||||
var expectedSQL = `
|
||||
(
|
||||
|
||||
(
|
||||
(
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)
|
||||
UNION
|
||||
(
|
||||
SELECT table2.col3 AS "table2.col3"
|
||||
FROM db.table2
|
||||
)
|
||||
)
|
||||
UNION
|
||||
(
|
||||
SELECT table3.col1 AS "table3.col1"
|
||||
FROM db.table3
|
||||
)
|
||||
);
|
||||
`
|
||||
|
||||
assertStatement(t, unionStmt1, expectedSQL)
|
||||
}
|
||||
|
||||
func TestUnionThreeSelect2(t *testing.T) {
|
||||
|
||||
unionStmt2 := UNION(
|
||||
table1.SELECT(table1Col1),
|
||||
table2.SELECT(table2Col3),
|
||||
table3.SELECT(table3Col1),
|
||||
)
|
||||
|
||||
var expectedSQL = `
|
||||
(
|
||||
(
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)
|
||||
UNION
|
||||
(
|
||||
SELECT table2.col3 AS "table2.col3"
|
||||
FROM db.table2
|
||||
)
|
||||
UNION
|
||||
(
|
||||
SELECT table3.col1 AS "table3.col1"
|
||||
FROM db.table3
|
||||
)
|
||||
);
|
||||
`
|
||||
|
||||
assertStatement(t, unionStmt2, expectedSQL)
|
||||
}
|
||||
|
||||
func TestUnionWithOrderBy(t *testing.T) {
|
||||
unionStmt := UNION(
|
||||
table1.SELECT(table1Col1),
|
||||
table2.SELECT(table2Col3),
|
||||
).
|
||||
ORDER_BY(table1Col1.ASC())
|
||||
|
||||
assertStatement(t, unionStmt, `
|
||||
(
|
||||
(
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)
|
||||
UNION
|
||||
(
|
||||
SELECT table2.col3 AS "table2.col3"
|
||||
FROM db.table2
|
||||
)
|
||||
)
|
||||
ORDER BY "table1.col1" ASC;
|
||||
`)
|
||||
}
|
||||
|
||||
func TestUnionWithLimitAndOffset(t *testing.T) {
|
||||
query, args, err := UNION(
|
||||
table1.SELECT(table1Col1),
|
||||
table2.SELECT(table2Col3),
|
||||
).
|
||||
LIMIT(10).
|
||||
OFFSET(11).Sql()
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, query, `
|
||||
(
|
||||
(
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)
|
||||
UNION
|
||||
(
|
||||
SELECT table2.col3 AS "table2.col3"
|
||||
FROM db.table2
|
||||
)
|
||||
)
|
||||
LIMIT $1
|
||||
OFFSET $2;
|
||||
`)
|
||||
assert.Equal(t, len(args), 2)
|
||||
}
|
||||
|
||||
func TestUnionInUnion(t *testing.T) {
|
||||
expectedSQL := `
|
||||
(
|
||||
(
|
||||
SELECT table2.col3 AS "table2.col3",
|
||||
table2.col3 AS "table2.col3"
|
||||
FROM db.table2
|
||||
)
|
||||
UNION
|
||||
|
||||
(
|
||||
(
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)
|
||||
UNION ALL
|
||||
(
|
||||
SELECT table2.col3 AS "table2.col3"
|
||||
FROM db.table2
|
||||
)
|
||||
)
|
||||
);
|
||||
`
|
||||
query := UNION(
|
||||
SELECT(table2Col3, table2Col3).FROM(table2),
|
||||
UNION_ALL(table1.SELECT(table1Col1), table2.SELECT(table2Col3)),
|
||||
)
|
||||
|
||||
assertStatement(t, query, expectedSQL)
|
||||
}
|
||||
|
||||
func TestUnionALL(t *testing.T) {
|
||||
query, args, err := UNION_ALL(
|
||||
table1.SELECT(table1Col1),
|
||||
table2.SELECT(table2Col3),
|
||||
).Sql()
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, query, `
|
||||
(
|
||||
(
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)
|
||||
UNION ALL
|
||||
(
|
||||
SELECT table2.col3 AS "table2.col3"
|
||||
FROM db.table2
|
||||
)
|
||||
);
|
||||
`)
|
||||
assert.Equal(t, len(args), 0)
|
||||
}
|
||||
|
||||
func TestINTERSECT(t *testing.T) {
|
||||
query, args, err := INTERSECT(
|
||||
table1.SELECT(table1Col1),
|
||||
table2.SELECT(table2Col3),
|
||||
).Sql()
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, query, `
|
||||
(
|
||||
(
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)
|
||||
INTERSECT
|
||||
(
|
||||
SELECT table2.col3 AS "table2.col3"
|
||||
FROM db.table2
|
||||
)
|
||||
);
|
||||
`)
|
||||
assert.Equal(t, len(args), 0)
|
||||
}
|
||||
|
||||
func TestINTERSECT_ALL(t *testing.T) {
|
||||
query, args, err := INTERSECT_ALL(
|
||||
table1.SELECT(table1Col1),
|
||||
table2.SELECT(table2Col3),
|
||||
).Sql()
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, query, `
|
||||
(
|
||||
(
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)
|
||||
INTERSECT ALL
|
||||
(
|
||||
SELECT table2.col3 AS "table2.col3"
|
||||
FROM db.table2
|
||||
)
|
||||
);
|
||||
`)
|
||||
assert.Equal(t, len(args), 0)
|
||||
}
|
||||
|
||||
func TestEXCEPT(t *testing.T) {
|
||||
query, args, err := EXCEPT(
|
||||
table1.SELECT(table1Col1),
|
||||
table2.SELECT(table2Col3),
|
||||
).Sql()
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, query, `
|
||||
(
|
||||
(
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)
|
||||
EXCEPT
|
||||
(
|
||||
SELECT table2.col3 AS "table2.col3"
|
||||
FROM db.table2
|
||||
)
|
||||
);
|
||||
`)
|
||||
assert.Equal(t, len(args), 0)
|
||||
}
|
||||
|
||||
func TestEXCEPT_ALL(t *testing.T) {
|
||||
query, args, err := EXCEPT_ALL(
|
||||
table1.SELECT(table1Col1),
|
||||
table2.SELECT(table2Col3),
|
||||
).Sql()
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, query, `
|
||||
(
|
||||
(
|
||||
SELECT table1.col1 AS "table1.col1"
|
||||
FROM db.table1
|
||||
)
|
||||
EXCEPT ALL
|
||||
(
|
||||
SELECT table2.col3 AS "table2.col3"
|
||||
FROM db.table2
|
||||
)
|
||||
);
|
||||
`)
|
||||
assert.Equal(t, len(args), 0)
|
||||
}
|
||||
102
internal/jet/statement.go
Normal file
102
internal/jet/statement.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"github.com/go-jet/jet/execution"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//Statement is common interface for all statements(SELECT, INSERT, UPDATE, DELETE, LOCK)
|
||||
type Statement interface {
|
||||
acceptsVisitor
|
||||
// Sql returns parametrized sql query with list of arguments.
|
||||
// err is returned if statement is not composed correctly
|
||||
Sql(dialect ...Dialect) (query string, args []interface{}, err error)
|
||||
// DebugSql returns debug query where every parametrized placeholder is replaced with its argument.
|
||||
// Do not use it in production. Use it only for debug purposes.
|
||||
// err is returned if statement is not composed correctly
|
||||
DebugSql(dialect ...Dialect) (query string, err error)
|
||||
|
||||
// Query executes statement over database connection db and stores row result in destination.
|
||||
// Destination can be arbitrary structure
|
||||
Query(db execution.DB, destination interface{}) error
|
||||
// QueryContext executes statement with a context over database connection db and stores row result in destination.
|
||||
// Destination can be of arbitrary structure
|
||||
QueryContext(context context.Context, db execution.DB, destination interface{}) error
|
||||
|
||||
//Exec executes statement over db connection without returning any rows.
|
||||
Exec(db execution.DB) (sql.Result, error)
|
||||
//Exec executes statement with context over db connection without returning any rows.
|
||||
ExecContext(context context.Context, db execution.DB) (sql.Result, error)
|
||||
}
|
||||
|
||||
func debugSql(statement Statement, overrideDialect ...Dialect) (string, error) {
|
||||
dialect := detectDialect(statement, overrideDialect...)
|
||||
sqlQuery, args, err := statement.Sql()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//debugSQLQuery := sqlQuery
|
||||
//
|
||||
//for i, arg := range args {
|
||||
// argPlaceholder := dialect.ArgumentPlaceholder()(i + 1)
|
||||
// debugSQLQuery = strings.Replace(debugSQLQuery, argPlaceholder, argToString(arg), 1)
|
||||
//}
|
||||
//
|
||||
//return debugSQLQuery, nil
|
||||
return queryStringToDebugString(sqlQuery, args, dialect), nil
|
||||
}
|
||||
|
||||
func queryStringToDebugString(sqlQuery string, args []interface{}, dialect Dialect) string {
|
||||
debugSQLQuery := sqlQuery
|
||||
|
||||
for i, arg := range args {
|
||||
argPlaceholder := dialect.ArgumentPlaceholder()(i + 1)
|
||||
debugSQLQuery = strings.Replace(debugSQLQuery, argPlaceholder, argToString(arg), 1)
|
||||
}
|
||||
|
||||
return debugSQLQuery
|
||||
}
|
||||
|
||||
func query(statement Statement, db execution.DB, destination interface{}) error {
|
||||
query, args, err := statement.Sql()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return execution.Query(context.Background(), db, query, args, destination)
|
||||
}
|
||||
|
||||
func queryContext(context context.Context, statement Statement, db execution.DB, destination interface{}) error {
|
||||
query, args, err := statement.Sql()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return execution.Query(context, db, query, args, destination)
|
||||
}
|
||||
|
||||
func exec(statement Statement, db execution.DB) (res sql.Result, err error) {
|
||||
query, args, err := statement.Sql()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return db.Exec(query, args...)
|
||||
}
|
||||
|
||||
func execContext(context context.Context, statement Statement, db execution.DB) (res sql.Result, err error) {
|
||||
query, args, err := statement.Sql()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return db.ExecContext(context, query, args...)
|
||||
}
|
||||
117
internal/jet/string_expression.go
Normal file
117
internal/jet/string_expression.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package jet
|
||||
|
||||
// StringExpression interface
|
||||
type StringExpression interface {
|
||||
Expression
|
||||
|
||||
EQ(rhs StringExpression) BoolExpression
|
||||
NOT_EQ(rhs StringExpression) BoolExpression
|
||||
IS_DISTINCT_FROM(rhs StringExpression) BoolExpression
|
||||
IS_NOT_DISTINCT_FROM(rhs StringExpression) BoolExpression
|
||||
|
||||
LT(rhs StringExpression) BoolExpression
|
||||
LT_EQ(rhs StringExpression) BoolExpression
|
||||
GT(rhs StringExpression) BoolExpression
|
||||
GT_EQ(rhs StringExpression) BoolExpression
|
||||
|
||||
CONCAT(rhs Expression) StringExpression
|
||||
|
||||
LIKE(pattern StringExpression) BoolExpression
|
||||
NOT_LIKE(pattern StringExpression) BoolExpression
|
||||
SIMILAR_TO(pattern StringExpression) BoolExpression
|
||||
NOT_SIMILAR_TO(pattern StringExpression) BoolExpression
|
||||
}
|
||||
|
||||
type stringInterfaceImpl struct {
|
||||
parent StringExpression
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) EQ(rhs StringExpression) BoolExpression {
|
||||
return eq(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) NOT_EQ(rhs StringExpression) BoolExpression {
|
||||
return notEq(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) IS_DISTINCT_FROM(rhs StringExpression) BoolExpression {
|
||||
return isDistinctFrom(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs StringExpression) BoolExpression {
|
||||
return isNotDistinctFrom(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) GT(rhs StringExpression) BoolExpression {
|
||||
return gt(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) GT_EQ(rhs StringExpression) BoolExpression {
|
||||
return gtEq(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) LT(rhs StringExpression) BoolExpression {
|
||||
return lt(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) LT_EQ(rhs StringExpression) BoolExpression {
|
||||
return ltEq(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) CONCAT(rhs Expression) StringExpression {
|
||||
return newBinaryStringExpression(s.parent, rhs, "||")
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) LIKE(pattern StringExpression) BoolExpression {
|
||||
return newBinaryBoolOperator(s.parent, pattern, "LIKE")
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) NOT_LIKE(pattern StringExpression) BoolExpression {
|
||||
return newBinaryBoolOperator(s.parent, pattern, "NOT LIKE")
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) SIMILAR_TO(pattern StringExpression) BoolExpression {
|
||||
return newBinaryBoolOperator(s.parent, pattern, "SIMILAR TO")
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) NOT_SIMILAR_TO(pattern StringExpression) BoolExpression {
|
||||
return newBinaryBoolOperator(s.parent, pattern, "NOT SIMILAR TO")
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
type binaryStringExpression struct {
|
||||
expressionInterfaceImpl
|
||||
stringInterfaceImpl
|
||||
|
||||
binaryOpExpression
|
||||
}
|
||||
|
||||
func newBinaryStringExpression(lhs, rhs Expression, operator string) StringExpression {
|
||||
boolExpression := binaryStringExpression{}
|
||||
|
||||
boolExpression.binaryOpExpression = newBinaryExpression(lhs, rhs, operator)
|
||||
boolExpression.expressionInterfaceImpl.parent = &boolExpression
|
||||
boolExpression.stringInterfaceImpl.parent = &boolExpression
|
||||
|
||||
return &boolExpression
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type stringExpressionWrapper struct {
|
||||
stringInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newStringExpressionWrap(expression Expression) StringExpression {
|
||||
stringExpressionWrap := stringExpressionWrapper{Expression: expression}
|
||||
stringExpressionWrap.stringInterfaceImpl.parent = &stringExpressionWrap
|
||||
return &stringExpressionWrap
|
||||
}
|
||||
|
||||
// StringExp is string expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as string expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func StringExp(expression Expression) StringExpression {
|
||||
return newStringExpressionWrap(expression)
|
||||
}
|
||||
82
internal/jet/string_expression_test.go
Normal file
82
internal/jet/string_expression_test.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringEQ(t *testing.T) {
|
||||
exp := table3StrCol.EQ(table2ColStr)
|
||||
assertClauseSerialize(t, exp, "(table3.col2 = table2.col_str)")
|
||||
exp = table3StrCol.EQ(String("JOHN"))
|
||||
assertClauseSerialize(t, exp, "(table3.col2 = $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringNOT_EQ(t *testing.T) {
|
||||
exp := table3StrCol.NOT_EQ(table2ColStr)
|
||||
assertClauseSerialize(t, exp, "(table3.col2 != table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.NOT_EQ(String("JOHN")), "(table3.col2 != $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringExpressionIS_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.IS_DISTINCT_FROM(table2ColStr), "(table3.col2 IS DISTINCT FROM table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.IS_DISTINCT_FROM(String("JOHN")), "(table3.col2 IS DISTINCT FROM $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.IS_NOT_DISTINCT_FROM(table2ColStr), "(table3.col2 IS NOT DISTINCT FROM table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.IS_NOT_DISTINCT_FROM(String("JOHN")), "(table3.col2 IS NOT DISTINCT FROM $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringGT(t *testing.T) {
|
||||
exp := table3StrCol.GT(table2ColStr)
|
||||
assertClauseSerialize(t, exp, "(table3.col2 > table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.GT(String("JOHN")), "(table3.col2 > $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringGT_EQ(t *testing.T) {
|
||||
exp := table3StrCol.GT_EQ(table2ColStr)
|
||||
assertClauseSerialize(t, exp, "(table3.col2 >= table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.GT_EQ(String("JOHN")), "(table3.col2 >= $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringLT(t *testing.T) {
|
||||
exp := table3StrCol.LT(table2ColStr)
|
||||
assertClauseSerialize(t, exp, "(table3.col2 < table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.LT(String("JOHN")), "(table3.col2 < $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringLT_EQ(t *testing.T) {
|
||||
exp := table3StrCol.LT_EQ(table2ColStr)
|
||||
assertClauseSerialize(t, exp, "(table3.col2 <= table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.LT_EQ(String("JOHN")), "(table3.col2 <= $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringCONCAT(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.CONCAT(table2ColStr), "(table3.col2 || table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.CONCAT(String("JOHN")), "(table3.col2 || $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringLIKE(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.LIKE(table2ColStr), "(table3.col2 LIKE table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.LIKE(String("JOHN")), "(table3.col2 LIKE $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringNOT_LIKE(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.NOT_LIKE(table2ColStr), "(table3.col2 NOT LIKE table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.NOT_LIKE(String("JOHN")), "(table3.col2 NOT LIKE $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringSIMILAR_TO(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.SIMILAR_TO(table2ColStr), "(table3.col2 SIMILAR TO table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.SIMILAR_TO(String("JOHN")), "(table3.col2 SIMILAR TO $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringNOT_SIMILAR_TO(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.NOT_SIMILAR_TO(table2ColStr), "(table3.col2 NOT SIMILAR TO table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.NOT_SIMILAR_TO(String("JOHN")), "(table3.col2 NOT SIMILAR TO $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringExp(t *testing.T) {
|
||||
assertClauseSerialize(t, StringExp(table2ColFloat), "table2.col_float")
|
||||
assertClauseSerialize(t, StringExp(table2ColFloat).NOT_LIKE(String("abc")), "(table2.col_float NOT LIKE $1)", "abc")
|
||||
}
|
||||
342
internal/jet/table.go
Normal file
342
internal/jet/table.go
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/go-jet/jet/internal/utils"
|
||||
)
|
||||
|
||||
type table interface {
|
||||
dialect() Dialect
|
||||
columns() []IColumn
|
||||
}
|
||||
|
||||
type readableTable interface {
|
||||
// Generates a select query on the current tableName.
|
||||
SELECT(projection Projection, projections ...Projection) SelectStatement
|
||||
|
||||
// Creates a inner join tableName Expression using onCondition.
|
||||
INNER_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable
|
||||
|
||||
// Creates a left join tableName Expression using onCondition.
|
||||
LEFT_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable
|
||||
|
||||
// Creates a right join tableName Expression using onCondition.
|
||||
RIGHT_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable
|
||||
|
||||
// Creates a full join tableName Expression using onCondition.
|
||||
FULL_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable
|
||||
|
||||
// Creates a cross join tableName Expression using onCondition.
|
||||
CROSS_JOIN(table ReadableTable) ReadableTable
|
||||
}
|
||||
|
||||
type writableTable interface {
|
||||
INSERT(columns ...IColumn) InsertStatement
|
||||
UPDATE(column IColumn, columns ...IColumn) UpdateStatement
|
||||
DELETE() DeleteStatement
|
||||
|
||||
LOCK() LockStatement
|
||||
}
|
||||
|
||||
// ReadableTable interface
|
||||
type ReadableTable interface {
|
||||
table
|
||||
readableTable
|
||||
Clause
|
||||
acceptsVisitor
|
||||
}
|
||||
|
||||
// WritableTable interface
|
||||
type WritableTable interface {
|
||||
table
|
||||
writableTable
|
||||
Clause
|
||||
acceptsVisitor
|
||||
}
|
||||
|
||||
// Table interface
|
||||
type Table interface {
|
||||
table
|
||||
readableTable
|
||||
writableTable
|
||||
Clause
|
||||
acceptsVisitor
|
||||
|
||||
SchemaName() string
|
||||
TableName() string
|
||||
AS(alias string)
|
||||
}
|
||||
|
||||
type readableTableInterfaceImpl struct {
|
||||
parent ReadableTable
|
||||
}
|
||||
|
||||
// Generates a select query on the current tableName.
|
||||
func (r *readableTableInterfaceImpl) SELECT(projection1 Projection, projections ...Projection) SelectStatement {
|
||||
return newSelectStatement(r.parent, append([]Projection{projection1}, projections...))
|
||||
}
|
||||
|
||||
// Creates a inner join tableName Expression using onCondition.
|
||||
func (r *readableTableInterfaceImpl) INNER_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable {
|
||||
return newJoinTable(r.parent, table, innerJoin, onCondition)
|
||||
}
|
||||
|
||||
// Creates a left join tableName Expression using onCondition.
|
||||
func (r *readableTableInterfaceImpl) LEFT_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable {
|
||||
return newJoinTable(r.parent, table, leftJoin, onCondition)
|
||||
}
|
||||
|
||||
// Creates a right join tableName Expression using onCondition.
|
||||
func (r *readableTableInterfaceImpl) RIGHT_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable {
|
||||
return newJoinTable(r.parent, table, rightJoin, onCondition)
|
||||
}
|
||||
|
||||
func (r *readableTableInterfaceImpl) FULL_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable {
|
||||
return newJoinTable(r.parent, table, fullJoin, onCondition)
|
||||
}
|
||||
|
||||
func (r *readableTableInterfaceImpl) CROSS_JOIN(table ReadableTable) ReadableTable {
|
||||
return newJoinTable(r.parent, table, crossJoin, nil)
|
||||
}
|
||||
|
||||
type writableTableInterfaceImpl struct {
|
||||
parent WritableTable
|
||||
}
|
||||
|
||||
func (w *writableTableInterfaceImpl) INSERT(columns ...IColumn) InsertStatement {
|
||||
return newInsertStatement(w.parent, unwidColumnList(columns))
|
||||
}
|
||||
|
||||
func (w *writableTableInterfaceImpl) UPDATE(column IColumn, columns ...IColumn) UpdateStatement {
|
||||
return newUpdateStatement(w.parent, unwindColumns(column, columns...))
|
||||
}
|
||||
|
||||
func (w *writableTableInterfaceImpl) DELETE() DeleteStatement {
|
||||
return newDeleteStatement(w.parent)
|
||||
}
|
||||
|
||||
func (w *writableTableInterfaceImpl) LOCK() LockStatement {
|
||||
return LOCK(w.parent)
|
||||
}
|
||||
|
||||
// NewTable creates new table with schema Name, table Name and list of columns
|
||||
func NewTable(Dialect Dialect, schemaName, name string, columns ...Column) Table {
|
||||
|
||||
t := &tableImpl{
|
||||
Dialect: Dialect,
|
||||
schemaName: schemaName,
|
||||
name: name,
|
||||
columnList: columns,
|
||||
}
|
||||
for _, c := range columns {
|
||||
c.SetTableName(name)
|
||||
}
|
||||
|
||||
t.readableTableInterfaceImpl.parent = t
|
||||
t.writableTableInterfaceImpl.parent = t
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
type tableImpl struct {
|
||||
readableTableInterfaceImpl
|
||||
writableTableInterfaceImpl
|
||||
|
||||
Dialect Dialect
|
||||
schemaName string
|
||||
name string
|
||||
alias string
|
||||
columnList []Column
|
||||
}
|
||||
|
||||
func (t *tableImpl) AS(alias string) {
|
||||
t.alias = alias
|
||||
|
||||
for _, c := range t.columnList {
|
||||
c.SetTableName(alias)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tableImpl) SchemaName() string {
|
||||
return t.schemaName
|
||||
}
|
||||
|
||||
func (t *tableImpl) TableName() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t *tableImpl) columns() []IColumn {
|
||||
ret := []IColumn{}
|
||||
|
||||
for _, col := range t.columnList {
|
||||
ret = append(ret, col)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *tableImpl) dialect() Dialect {
|
||||
return t.Dialect
|
||||
}
|
||||
|
||||
func (t *tableImpl) accept(visitor visitor) {
|
||||
visitor.visit(t)
|
||||
}
|
||||
|
||||
func (t *tableImpl) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error {
|
||||
if t == nil {
|
||||
return errors.New("jet: tableImpl is nil. ")
|
||||
}
|
||||
|
||||
out.writeIdentifier(t.schemaName)
|
||||
out.WriteString(".")
|
||||
out.writeIdentifier(t.name)
|
||||
|
||||
if len(t.alias) > 0 {
|
||||
out.WriteString("AS")
|
||||
out.writeIdentifier(t.alias)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type joinType int
|
||||
|
||||
const (
|
||||
innerJoin joinType = iota
|
||||
leftJoin
|
||||
rightJoin
|
||||
fullJoin
|
||||
crossJoin
|
||||
)
|
||||
|
||||
// Join expressions are pseudo readable tables.
|
||||
type joinTable struct {
|
||||
readableTableInterfaceImpl
|
||||
|
||||
lhs ReadableTable
|
||||
rhs ReadableTable
|
||||
joinType joinType
|
||||
onCondition BoolExpression
|
||||
}
|
||||
|
||||
func newJoinTable(
|
||||
lhs ReadableTable,
|
||||
rhs ReadableTable,
|
||||
joinType joinType,
|
||||
onCondition BoolExpression) ReadableTable {
|
||||
|
||||
joinTable := &joinTable{
|
||||
lhs: lhs,
|
||||
rhs: rhs,
|
||||
joinType: joinType,
|
||||
onCondition: onCondition,
|
||||
}
|
||||
|
||||
joinTable.readableTableInterfaceImpl.parent = joinTable
|
||||
|
||||
return joinTable
|
||||
}
|
||||
|
||||
func (t *joinTable) SchemaName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *joinTable) TableName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *joinTable) columns() []IColumn {
|
||||
return append(t.lhs.columns(), t.rhs.columns()...)
|
||||
}
|
||||
|
||||
func (t *joinTable) accept(visitor visitor) {
|
||||
t.lhs.accept(visitor)
|
||||
t.rhs.accept(visitor)
|
||||
}
|
||||
|
||||
func (t *joinTable) dialect() Dialect {
|
||||
return detectDialect(t)
|
||||
}
|
||||
|
||||
func (t *joinTable) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) (err error) {
|
||||
if t == nil {
|
||||
return errors.New("jet: Join table is nil. ")
|
||||
}
|
||||
|
||||
if utils.IsNil(t.lhs) {
|
||||
return errors.New("jet: left hand side of join operation is nil table")
|
||||
}
|
||||
|
||||
if err = t.lhs.serialize(statement, out); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out.newLine()
|
||||
|
||||
switch t.joinType {
|
||||
case innerJoin:
|
||||
out.WriteString("INNER JOIN")
|
||||
case leftJoin:
|
||||
out.WriteString("LEFT JOIN")
|
||||
case rightJoin:
|
||||
out.WriteString("RIGHT JOIN")
|
||||
case fullJoin:
|
||||
out.WriteString("FULL JOIN")
|
||||
case crossJoin:
|
||||
out.WriteString("CROSS JOIN")
|
||||
}
|
||||
|
||||
if utils.IsNil(t.rhs) {
|
||||
return errors.New("jet: right hand side of join operation is nil table")
|
||||
}
|
||||
|
||||
if err = t.rhs.serialize(statement, out); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if t.onCondition == nil && t.joinType != crossJoin {
|
||||
return errors.New("jet: join condition is nil")
|
||||
}
|
||||
|
||||
if t.onCondition != nil {
|
||||
out.WriteString("ON")
|
||||
if err = t.onCondition.serialize(statement, out); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unwindColumns(column1 IColumn, columns ...IColumn) []IColumn {
|
||||
columnList := []IColumn{}
|
||||
|
||||
if val, ok := column1.(IColumnList); ok {
|
||||
for _, col := range val.Columns() {
|
||||
columnList = append(columnList, col)
|
||||
}
|
||||
columnList = append(columnList, columns...)
|
||||
} else {
|
||||
columnList = append(columnList, column1)
|
||||
columnList = append(columnList, columns...)
|
||||
}
|
||||
|
||||
return columnList
|
||||
}
|
||||
|
||||
func unwidColumnList(columns []IColumn) []IColumn {
|
||||
ret := []IColumn{}
|
||||
|
||||
for _, col := range columns {
|
||||
if columnList, ok := col.(IColumnList); ok {
|
||||
for _, c := range columnList.Columns() {
|
||||
ret = append(ret, c)
|
||||
}
|
||||
} else {
|
||||
ret = append(ret, col)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
101
internal/jet/table_test.go
Normal file
101
internal/jet/table_test.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJoinNilInputs(t *testing.T) {
|
||||
assertClauseSerializeErr(t, table2.INNER_JOIN(nil, table1ColBool.EQ(table2ColBool)),
|
||||
"jet: right hand side of join operation is nil table")
|
||||
assertClauseSerializeErr(t, table2.INNER_JOIN(table1, nil),
|
||||
"jet: join condition is nil")
|
||||
}
|
||||
|
||||
func TestINNER_JOIN(t *testing.T) {
|
||||
assertClauseSerialize(t, table1.
|
||||
INNER_JOIN(table2, table1ColInt.EQ(table2ColInt)),
|
||||
`db.table1
|
||||
INNER JOIN db.table2 ON (table1.col_int = table2.col_int)`)
|
||||
assertClauseSerialize(t, table1.
|
||||
INNER_JOIN(table2, table1ColInt.EQ(table2ColInt)).
|
||||
INNER_JOIN(table3, table1ColInt.EQ(table3ColInt)),
|
||||
`db.table1
|
||||
INNER JOIN db.table2 ON (table1.col_int = table2.col_int)
|
||||
INNER JOIN db.table3 ON (table1.col_int = table3.col_int)`)
|
||||
assertClauseSerialize(t, table1.
|
||||
INNER_JOIN(table2, table1ColInt.EQ(Int(1))).
|
||||
INNER_JOIN(table3, table1ColInt.EQ(Int(2))),
|
||||
`db.table1
|
||||
INNER JOIN db.table2 ON (table1.col_int = $1)
|
||||
INNER JOIN db.table3 ON (table1.col_int = $2)`, int64(1), int64(2))
|
||||
}
|
||||
|
||||
func TestLEFT_JOIN(t *testing.T) {
|
||||
assertClauseSerialize(t, table1.
|
||||
LEFT_JOIN(table2, table1ColInt.EQ(table2ColInt)),
|
||||
`db.table1
|
||||
LEFT JOIN db.table2 ON (table1.col_int = table2.col_int)`)
|
||||
assertClauseSerialize(t, table1.
|
||||
LEFT_JOIN(table2, table1ColInt.EQ(table2ColInt)).
|
||||
LEFT_JOIN(table3, table1ColInt.EQ(table3ColInt)),
|
||||
`db.table1
|
||||
LEFT JOIN db.table2 ON (table1.col_int = table2.col_int)
|
||||
LEFT JOIN db.table3 ON (table1.col_int = table3.col_int)`)
|
||||
assertClauseSerialize(t, table1.
|
||||
LEFT_JOIN(table2, table1ColInt.EQ(Int(1))).
|
||||
LEFT_JOIN(table3, table1ColInt.EQ(Int(2))),
|
||||
`db.table1
|
||||
LEFT JOIN db.table2 ON (table1.col_int = $1)
|
||||
LEFT JOIN db.table3 ON (table1.col_int = $2)`, int64(1), int64(2))
|
||||
}
|
||||
|
||||
func TestRIGHT_JOIN(t *testing.T) {
|
||||
assertClauseSerialize(t, table1.
|
||||
RIGHT_JOIN(table2, table1ColInt.EQ(table2ColInt)),
|
||||
`db.table1
|
||||
RIGHT JOIN db.table2 ON (table1.col_int = table2.col_int)`)
|
||||
assertClauseSerialize(t, table1.
|
||||
RIGHT_JOIN(table2, table1ColInt.EQ(table2ColInt)).
|
||||
RIGHT_JOIN(table3, table1ColInt.EQ(table3ColInt)),
|
||||
`db.table1
|
||||
RIGHT JOIN db.table2 ON (table1.col_int = table2.col_int)
|
||||
RIGHT JOIN db.table3 ON (table1.col_int = table3.col_int)`)
|
||||
assertClauseSerialize(t, table1.
|
||||
RIGHT_JOIN(table2, table1ColInt.EQ(Int(1))).
|
||||
RIGHT_JOIN(table3, table1ColInt.EQ(Int(2))),
|
||||
`db.table1
|
||||
RIGHT JOIN db.table2 ON (table1.col_int = $1)
|
||||
RIGHT JOIN db.table3 ON (table1.col_int = $2)`, int64(1), int64(2))
|
||||
}
|
||||
|
||||
func TestFULL_JOIN(t *testing.T) {
|
||||
assertClauseSerialize(t, table1.
|
||||
FULL_JOIN(table2, table1ColInt.EQ(table2ColInt)),
|
||||
`db.table1
|
||||
FULL JOIN db.table2 ON (table1.col_int = table2.col_int)`)
|
||||
assertClauseSerialize(t, table1.
|
||||
FULL_JOIN(table2, table1ColInt.EQ(table2ColInt)).
|
||||
FULL_JOIN(table3, table1ColInt.EQ(table3ColInt)),
|
||||
`db.table1
|
||||
FULL JOIN db.table2 ON (table1.col_int = table2.col_int)
|
||||
FULL JOIN db.table3 ON (table1.col_int = table3.col_int)`)
|
||||
assertClauseSerialize(t, table1.
|
||||
FULL_JOIN(table2, table1ColInt.EQ(Int(1))).
|
||||
FULL_JOIN(table3, table1ColInt.EQ(Int(2))),
|
||||
`db.table1
|
||||
FULL JOIN db.table2 ON (table1.col_int = $1)
|
||||
FULL JOIN db.table3 ON (table1.col_int = $2)`, int64(1), int64(2))
|
||||
}
|
||||
|
||||
func TestCROSS_JOIN(t *testing.T) {
|
||||
assertClauseSerialize(t, table1.
|
||||
CROSS_JOIN(table2),
|
||||
`db.table1
|
||||
CROSS JOIN db.table2`)
|
||||
assertClauseSerialize(t, table1.
|
||||
CROSS_JOIN(table2).
|
||||
CROSS_JOIN(table3),
|
||||
`db.table1
|
||||
CROSS JOIN db.table2
|
||||
CROSS JOIN db.table3`)
|
||||
}
|
||||
128
internal/jet/testutils.go
Normal file
128
internal/jet/testutils.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gotest.tools/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var table1Col1 = IntegerColumn("col1")
|
||||
var table1ColInt = IntegerColumn("col_int")
|
||||
var table1ColFloat = FloatColumn("col_float")
|
||||
var table1Col3 = IntegerColumn("col3")
|
||||
var table1ColTime = TimeColumn("col_time")
|
||||
var table1ColTimez = TimezColumn("col_timez")
|
||||
var table1ColTimestamp = TimestampColumn("col_timestamp")
|
||||
var table1ColTimestampz = TimestampzColumn("col_timestampz")
|
||||
var table1ColBool = BoolColumn("col_bool")
|
||||
var table1ColDate = DateColumn("col_date")
|
||||
|
||||
var table1 = NewTable(
|
||||
ANSII,
|
||||
"db",
|
||||
"table1",
|
||||
table1Col1,
|
||||
table1ColInt,
|
||||
table1ColFloat,
|
||||
table1Col3,
|
||||
table1ColTime,
|
||||
table1ColTimez,
|
||||
table1ColBool,
|
||||
table1ColDate,
|
||||
table1ColTimestamp,
|
||||
table1ColTimestampz,
|
||||
)
|
||||
|
||||
var table2Col3 = IntegerColumn("col3")
|
||||
var table2Col4 = IntegerColumn("col4")
|
||||
var table2ColInt = IntegerColumn("col_int")
|
||||
var table2ColFloat = FloatColumn("col_float")
|
||||
var table2ColStr = StringColumn("col_str")
|
||||
var table2ColBool = BoolColumn("col_bool")
|
||||
var table2ColTime = TimeColumn("col_time")
|
||||
var table2ColTimez = TimezColumn("col_timez")
|
||||
var table2ColTimestamp = TimestampColumn("col_timestamp")
|
||||
var table2ColTimestampz = TimestampzColumn("col_timestampz")
|
||||
var table2ColDate = DateColumn("col_date")
|
||||
|
||||
var table2 = NewTable(
|
||||
ANSII,
|
||||
"db",
|
||||
"table2",
|
||||
table2Col3,
|
||||
table2Col4,
|
||||
table2ColInt,
|
||||
table2ColFloat,
|
||||
table2ColStr,
|
||||
table2ColBool,
|
||||
table2ColTime,
|
||||
table2ColTimez,
|
||||
table2ColDate,
|
||||
table2ColTimestamp,
|
||||
table2ColTimestampz,
|
||||
)
|
||||
|
||||
var table3Col1 = IntegerColumn("col1")
|
||||
var table3ColInt = IntegerColumn("col_int")
|
||||
var table3StrCol = StringColumn("col2")
|
||||
var table3 = NewTable(
|
||||
ANSII,
|
||||
"db",
|
||||
"table3",
|
||||
table3Col1,
|
||||
table3ColInt,
|
||||
table3StrCol)
|
||||
|
||||
func assertClauseSerialize(t *testing.T, clause Clause, query string, args ...interface{}) {
|
||||
out := SqlBuilder{Dialect: ANSII}
|
||||
err := clause.serialize(SelectStatementType, &out)
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.DeepEqual(t, out.DebugSQL(), query)
|
||||
|
||||
if len(args) > 0 {
|
||||
assert.DeepEqual(t, out.Args, args)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func assertClauseSerializeErr(t *testing.T, clause Clause, errString string) {
|
||||
out := SqlBuilder{Dialect: ANSII}
|
||||
err := clause.serialize(SelectStatementType, &out)
|
||||
|
||||
//fmt.Println(out.buff.String())
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Error(t, err, errString)
|
||||
}
|
||||
|
||||
func assertProjectionSerialize(t *testing.T, projection Projection, query string, args ...interface{}) {
|
||||
out := SqlBuilder{Dialect: ANSII}
|
||||
err := projection.serializeForProjection(SelectStatementType, &out)
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.DeepEqual(t, out.DebugSQL(), query)
|
||||
|
||||
if len(args) > 0 {
|
||||
assert.DeepEqual(t, out.Args, args)
|
||||
}
|
||||
}
|
||||
|
||||
func assertStatement(t *testing.T, query Statement, expectedQuery string, expectedArgs ...interface{}) {
|
||||
queryStr, err := query.DebugSql()
|
||||
assert.NilError(t, err)
|
||||
|
||||
fmt.Println(queryStr)
|
||||
|
||||
//fmt.Println(queryStr)
|
||||
assert.Equal(t, queryStr, expectedQuery)
|
||||
//assert.DeepEqual(t, args, expectedArgs)
|
||||
}
|
||||
|
||||
func assertStatementErr(t *testing.T, stmt Statement, errorStr string) {
|
||||
_, _, err := stmt.Sql(ANSII)
|
||||
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Error(t, err, errorStr)
|
||||
}
|
||||
90
internal/jet/time_expression.go
Normal file
90
internal/jet/time_expression.go
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package jet
|
||||
|
||||
// TimeExpression interface
|
||||
type TimeExpression interface {
|
||||
Expression
|
||||
|
||||
EQ(rhs TimeExpression) BoolExpression
|
||||
NOT_EQ(rhs TimeExpression) BoolExpression
|
||||
IS_DISTINCT_FROM(rhs TimeExpression) BoolExpression
|
||||
IS_NOT_DISTINCT_FROM(rhs TimeExpression) BoolExpression
|
||||
|
||||
LT(rhs TimeExpression) BoolExpression
|
||||
LT_EQ(rhs TimeExpression) BoolExpression
|
||||
GT(rhs TimeExpression) BoolExpression
|
||||
GT_EQ(rhs TimeExpression) BoolExpression
|
||||
}
|
||||
|
||||
type timeInterfaceImpl struct {
|
||||
parent TimeExpression
|
||||
}
|
||||
|
||||
func (t *timeInterfaceImpl) EQ(rhs TimeExpression) BoolExpression {
|
||||
return eq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timeInterfaceImpl) NOT_EQ(rhs TimeExpression) BoolExpression {
|
||||
return notEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timeInterfaceImpl) IS_DISTINCT_FROM(rhs TimeExpression) BoolExpression {
|
||||
return isDistinctFrom(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timeInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs TimeExpression) BoolExpression {
|
||||
return isNotDistinctFrom(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timeInterfaceImpl) LT(rhs TimeExpression) BoolExpression {
|
||||
return lt(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timeInterfaceImpl) LT_EQ(rhs TimeExpression) BoolExpression {
|
||||
return ltEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timeInterfaceImpl) GT(rhs TimeExpression) BoolExpression {
|
||||
return gt(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timeInterfaceImpl) GT_EQ(rhs TimeExpression) BoolExpression {
|
||||
return gtEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
type prefixTimeExpression struct {
|
||||
expressionInterfaceImpl
|
||||
timeInterfaceImpl
|
||||
|
||||
prefixOpExpression
|
||||
}
|
||||
|
||||
//func newPrefixTimeExpression(operator string, expression Expression) TimeExpression {
|
||||
// timeExpr := prefixTimeExpression{}
|
||||
// timeExpr.prefixOpExpression = newPrefixExpression(expression, operator)
|
||||
//
|
||||
// timeExpr.expressionInterfaceImpl.parent = &timeExpr
|
||||
// timeExpr.timeInterfaceImpl.parent = &timeExpr
|
||||
//
|
||||
// return &timeExpr
|
||||
//}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type timeExpressionWrapper struct {
|
||||
timeInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newTimeExpressionWrap(expression Expression) TimeExpression {
|
||||
timeExpressionWrap := timeExpressionWrapper{Expression: expression}
|
||||
timeExpressionWrap.timeInterfaceImpl.parent = &timeExpressionWrap
|
||||
return &timeExpressionWrap
|
||||
}
|
||||
|
||||
// TimeExp is time expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as time expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func TimeExp(expression Expression) TimeExpression {
|
||||
return newTimeExpressionWrap(expression)
|
||||
}
|
||||
53
internal/jet/time_expression_test.go
Normal file
53
internal/jet/time_expression_test.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var timeVar = Time(10, 20, 0, 0)
|
||||
|
||||
func TestTimeExpressionEQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTime.EQ(table2ColTime), "(table1.col_time = table2.col_time)")
|
||||
assertClauseSerialize(t, table1ColTime.EQ(timeVar), "(table1.col_time = $1)", "10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimeExpressionNOT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTime.NOT_EQ(table2ColTime), "(table1.col_time != table2.col_time)")
|
||||
assertClauseSerialize(t, table1ColTime.NOT_EQ(timeVar), "(table1.col_time != $1)", "10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimeExpressionIS_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTime.IS_DISTINCT_FROM(table2ColTime), "(table1.col_time IS DISTINCT FROM table2.col_time)")
|
||||
assertClauseSerialize(t, table1ColTime.IS_DISTINCT_FROM(timeVar), "(table1.col_time IS DISTINCT FROM $1)", "10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimeExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTime.IS_NOT_DISTINCT_FROM(table2ColTime), "(table1.col_time IS NOT DISTINCT FROM table2.col_time)")
|
||||
assertClauseSerialize(t, table1ColTime.IS_NOT_DISTINCT_FROM(timeVar), "(table1.col_time IS NOT DISTINCT FROM $1)", "10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimeExpressionLT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTime.LT(table2ColTime), "(table1.col_time < table2.col_time)")
|
||||
assertClauseSerialize(t, table1ColTime.LT(timeVar), "(table1.col_time < $1)", "10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimeExpressionLT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTime.LT_EQ(table2ColTime), "(table1.col_time <= table2.col_time)")
|
||||
assertClauseSerialize(t, table1ColTime.LT_EQ(timeVar), "(table1.col_time <= $1)", "10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimeExpressionGT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTime.GT(table2ColTime), "(table1.col_time > table2.col_time)")
|
||||
assertClauseSerialize(t, table1ColTime.GT(timeVar), "(table1.col_time > $1)", "10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimeExpressionGT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTime.GT_EQ(table2ColTime), "(table1.col_time >= table2.col_time)")
|
||||
assertClauseSerialize(t, table1ColTime.GT_EQ(timeVar), "(table1.col_time >= $1)", "10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimeExp(t *testing.T) {
|
||||
assertClauseSerialize(t, TimeExp(table1ColFloat), "table1.col_float")
|
||||
assertClauseSerialize(t, TimeExp(table1ColFloat).LT(Time(1, 1, 1, 1)),
|
||||
"(table1.col_float < $1)", string("01:01:01.001"))
|
||||
}
|
||||
72
internal/jet/timestamp_expression.go
Normal file
72
internal/jet/timestamp_expression.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package jet
|
||||
|
||||
// TimestampExpression interface
|
||||
type TimestampExpression interface {
|
||||
Expression
|
||||
|
||||
EQ(rhs TimestampExpression) BoolExpression
|
||||
NOT_EQ(rhs TimestampExpression) BoolExpression
|
||||
IS_DISTINCT_FROM(rhs TimestampExpression) BoolExpression
|
||||
IS_NOT_DISTINCT_FROM(rhs TimestampExpression) BoolExpression
|
||||
|
||||
LT(rhs TimestampExpression) BoolExpression
|
||||
LT_EQ(rhs TimestampExpression) BoolExpression
|
||||
GT(rhs TimestampExpression) BoolExpression
|
||||
GT_EQ(rhs TimestampExpression) BoolExpression
|
||||
}
|
||||
|
||||
type timestampInterfaceImpl struct {
|
||||
parent TimestampExpression
|
||||
}
|
||||
|
||||
func (t *timestampInterfaceImpl) EQ(rhs TimestampExpression) BoolExpression {
|
||||
return eq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampInterfaceImpl) NOT_EQ(rhs TimestampExpression) BoolExpression {
|
||||
return notEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampInterfaceImpl) IS_DISTINCT_FROM(rhs TimestampExpression) BoolExpression {
|
||||
return isDistinctFrom(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs TimestampExpression) BoolExpression {
|
||||
return isNotDistinctFrom(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampInterfaceImpl) LT(rhs TimestampExpression) BoolExpression {
|
||||
return lt(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampInterfaceImpl) LT_EQ(rhs TimestampExpression) BoolExpression {
|
||||
return ltEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampInterfaceImpl) GT(rhs TimestampExpression) BoolExpression {
|
||||
return gt(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampInterfaceImpl) GT_EQ(rhs TimestampExpression) BoolExpression {
|
||||
return gtEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
//-------------------------------------------------
|
||||
|
||||
type timestampExpressionWrapper struct {
|
||||
timestampInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newTimestampExpressionWrap(expression Expression) TimestampExpression {
|
||||
timestampExpressionWrap := timestampExpressionWrapper{Expression: expression}
|
||||
timestampExpressionWrap.timestampInterfaceImpl.parent = ×tampExpressionWrap
|
||||
return ×tampExpressionWrap
|
||||
}
|
||||
|
||||
// TimestampExp is timestamp expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as timestamp expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func TimestampExp(expression Expression) TimestampExpression {
|
||||
return newTimestampExpressionWrap(expression)
|
||||
}
|
||||
52
internal/jet/timestamp_expression_test.go
Normal file
52
internal/jet/timestamp_expression_test.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package jet
|
||||
|
||||
import "testing"
|
||||
|
||||
var timestamp = Timestamp(2000, 1, 31, 10, 20, 0, 0)
|
||||
|
||||
func TestTimestampExpressionEQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestamp.EQ(table2ColTimestamp), "(table1.col_timestamp = table2.col_timestamp)")
|
||||
assertClauseSerialize(t, table1ColTimestamp.EQ(timestamp),
|
||||
"(table1.col_timestamp = $1)", "2000-01-31 10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimestampExpressionNOT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestamp.NOT_EQ(table2ColTimestamp), "(table1.col_timestamp != table2.col_timestamp)")
|
||||
assertClauseSerialize(t, table1ColTimestamp.NOT_EQ(timestamp), "(table1.col_timestamp != $1)", "2000-01-31 10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimestampExpressionIS_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestamp.IS_DISTINCT_FROM(table2ColTimestamp), "(table1.col_timestamp IS DISTINCT FROM table2.col_timestamp)")
|
||||
assertClauseSerialize(t, table1ColTimestamp.IS_DISTINCT_FROM(timestamp), "(table1.col_timestamp IS DISTINCT FROM $1)", "2000-01-31 10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimestampExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestamp.IS_NOT_DISTINCT_FROM(table2ColTimestamp), "(table1.col_timestamp IS NOT DISTINCT FROM table2.col_timestamp)")
|
||||
assertClauseSerialize(t, table1ColTimestamp.IS_NOT_DISTINCT_FROM(timestamp), "(table1.col_timestamp IS NOT DISTINCT FROM $1)", "2000-01-31 10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimestampExpressionLT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestamp.LT(table2ColTimestamp), "(table1.col_timestamp < table2.col_timestamp)")
|
||||
assertClauseSerialize(t, table1ColTimestamp.LT(timestamp), "(table1.col_timestamp < $1)", "2000-01-31 10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimestampExpressionLT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestamp.LT_EQ(table2ColTimestamp), "(table1.col_timestamp <= table2.col_timestamp)")
|
||||
assertClauseSerialize(t, table1ColTimestamp.LT_EQ(timestamp), "(table1.col_timestamp <= $1)", "2000-01-31 10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimestampExpressionGT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestamp.GT(table2ColTimestamp), "(table1.col_timestamp > table2.col_timestamp)")
|
||||
assertClauseSerialize(t, table1ColTimestamp.GT(timestamp), "(table1.col_timestamp > $1)", "2000-01-31 10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimestampExpressionGT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestamp.GT_EQ(table2ColTimestamp), "(table1.col_timestamp >= table2.col_timestamp)")
|
||||
assertClauseSerialize(t, table1ColTimestamp.GT_EQ(timestamp), "(table1.col_timestamp >= $1)", "2000-01-31 10:20:00.000")
|
||||
}
|
||||
|
||||
func TestTimestampExp(t *testing.T) {
|
||||
assertClauseSerialize(t, TimestampExp(table1ColFloat), "table1.col_float")
|
||||
assertClauseSerialize(t, TimestampExp(table1ColFloat).LT(timestamp),
|
||||
"(table1.col_float < $1)", "2000-01-31 10:20:00.000")
|
||||
}
|
||||
72
internal/jet/timestampz_expression.go
Normal file
72
internal/jet/timestampz_expression.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package jet
|
||||
|
||||
// TimestampzExpression interface
|
||||
type TimestampzExpression interface {
|
||||
Expression
|
||||
|
||||
EQ(rhs TimestampzExpression) BoolExpression
|
||||
NOT_EQ(rhs TimestampzExpression) BoolExpression
|
||||
IS_DISTINCT_FROM(rhs TimestampzExpression) BoolExpression
|
||||
IS_NOT_DISTINCT_FROM(rhs TimestampzExpression) BoolExpression
|
||||
|
||||
LT(rhs TimestampzExpression) BoolExpression
|
||||
LT_EQ(rhs TimestampzExpression) BoolExpression
|
||||
GT(rhs TimestampzExpression) BoolExpression
|
||||
GT_EQ(rhs TimestampzExpression) BoolExpression
|
||||
}
|
||||
|
||||
type timestampzInterfaceImpl struct {
|
||||
parent TimestampzExpression
|
||||
}
|
||||
|
||||
func (t *timestampzInterfaceImpl) EQ(rhs TimestampzExpression) BoolExpression {
|
||||
return eq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampzInterfaceImpl) NOT_EQ(rhs TimestampzExpression) BoolExpression {
|
||||
return notEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampzInterfaceImpl) IS_DISTINCT_FROM(rhs TimestampzExpression) BoolExpression {
|
||||
return isDistinctFrom(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampzInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs TimestampzExpression) BoolExpression {
|
||||
return isNotDistinctFrom(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampzInterfaceImpl) LT(rhs TimestampzExpression) BoolExpression {
|
||||
return lt(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampzInterfaceImpl) LT_EQ(rhs TimestampzExpression) BoolExpression {
|
||||
return ltEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampzInterfaceImpl) GT(rhs TimestampzExpression) BoolExpression {
|
||||
return gt(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timestampzInterfaceImpl) GT_EQ(rhs TimestampzExpression) BoolExpression {
|
||||
return gtEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
//-------------------------------------------------
|
||||
|
||||
type timestampzExpressionWrapper struct {
|
||||
timestampzInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newTimestampzExpressionWrap(expression Expression) TimestampzExpression {
|
||||
timestampzExpressionWrap := timestampzExpressionWrapper{Expression: expression}
|
||||
timestampzExpressionWrap.timestampzInterfaceImpl.parent = ×tampzExpressionWrap
|
||||
return ×tampzExpressionWrap
|
||||
}
|
||||
|
||||
// TimestampzExp is timestamp with time zone expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as timestamp with time zone expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func TimestampzExp(expression Expression) TimestampzExpression {
|
||||
return newTimestampzExpressionWrap(expression)
|
||||
}
|
||||
54
internal/jet/timestampz_expression_test.go
Normal file
54
internal/jet/timestampz_expression_test.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// +build todo
|
||||
|
||||
package jet
|
||||
|
||||
import "testing"
|
||||
|
||||
var timestampz = Timestampz(2000, 1, 31, 10, 20, 0, 0, 2)
|
||||
|
||||
func TestTimestampzExpressionEQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestampz.EQ(table2ColTimestampz), "(table1.col_timestampz = table2.col_timestampz)")
|
||||
assertClauseSerialize(t, table1ColTimestampz.EQ(timestampz),
|
||||
"(table1.col_timestampz = $1::timestamp with time zone)", "2000-01-31 10:20:00.000 +002")
|
||||
}
|
||||
|
||||
func TestTimestampzExpressionNOT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestampz.NOT_EQ(table2ColTimestampz), "(table1.col_timestampz != table2.col_timestampz)")
|
||||
assertClauseSerialize(t, table1ColTimestampz.NOT_EQ(timestampz), "(table1.col_timestampz != $1::timestamp with time zone)", "2000-01-31 10:20:00.000 +002")
|
||||
}
|
||||
|
||||
func TestTimestampzExpressionIS_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestampz.IS_DISTINCT_FROM(table2ColTimestampz), "(table1.col_timestampz IS DISTINCT FROM table2.col_timestampz)")
|
||||
assertClauseSerialize(t, table1ColTimestampz.IS_DISTINCT_FROM(timestampz), "(table1.col_timestampz IS DISTINCT FROM $1::timestamp with time zone)", "2000-01-31 10:20:00.000 +002")
|
||||
}
|
||||
|
||||
func TestTimestampzExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestampz.IS_NOT_DISTINCT_FROM(table2ColTimestampz), "(table1.col_timestampz IS NOT DISTINCT FROM table2.col_timestampz)")
|
||||
assertClauseSerialize(t, table1ColTimestampz.IS_NOT_DISTINCT_FROM(timestampz), "(table1.col_timestampz IS NOT DISTINCT FROM $1::timestamp with time zone)", "2000-01-31 10:20:00.000 +002")
|
||||
}
|
||||
|
||||
func TestTimestampzExpressionLT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestampz.LT(table2ColTimestampz), "(table1.col_timestampz < table2.col_timestampz)")
|
||||
assertClauseSerialize(t, table1ColTimestampz.LT(timestampz), "(table1.col_timestampz < $1::timestamp with time zone)", "2000-01-31 10:20:00.000 +002")
|
||||
}
|
||||
|
||||
func TestTimestampzExpressionLT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestampz.LT_EQ(table2ColTimestampz), "(table1.col_timestampz <= table2.col_timestampz)")
|
||||
assertClauseSerialize(t, table1ColTimestampz.LT_EQ(timestampz), "(table1.col_timestampz <= $1::timestamp with time zone)", "2000-01-31 10:20:00.000 +002")
|
||||
}
|
||||
|
||||
func TestTimestampzExpressionGT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestampz.GT(table2ColTimestampz), "(table1.col_timestampz > table2.col_timestampz)")
|
||||
assertClauseSerialize(t, table1ColTimestampz.GT(timestampz), "(table1.col_timestampz > $1::timestamp with time zone)", "2000-01-31 10:20:00.000 +002")
|
||||
}
|
||||
|
||||
func TestTimestampzExpressionGT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimestampz.GT_EQ(table2ColTimestampz), "(table1.col_timestampz >= table2.col_timestampz)")
|
||||
assertClauseSerialize(t, table1ColTimestampz.GT_EQ(timestampz), "(table1.col_timestampz >= $1::timestamp with time zone)", "2000-01-31 10:20:00.000 +002")
|
||||
}
|
||||
|
||||
func TestTimestampzExp(t *testing.T) {
|
||||
assertClauseSerialize(t, TimestampzExp(table1ColFloat), "table1.col_float")
|
||||
assertClauseSerialize(t, TimestampzExp(table1ColFloat).LT(timestampz),
|
||||
"(table1.col_float < $1::timestamp with time zone)", "2000-01-31 10:20:00.000 +002")
|
||||
}
|
||||
98
internal/jet/timez_expression.go
Normal file
98
internal/jet/timez_expression.go
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package jet
|
||||
|
||||
// TimezExpression interface 'time with time zone'
|
||||
type TimezExpression interface {
|
||||
Expression
|
||||
|
||||
//EQ
|
||||
EQ(rhs TimezExpression) BoolExpression
|
||||
//NOT_EQ
|
||||
NOT_EQ(rhs TimezExpression) BoolExpression
|
||||
//IS_DISTINCT_FROM
|
||||
IS_DISTINCT_FROM(rhs TimezExpression) BoolExpression
|
||||
//IS_NOT_DISTINCT_FROM
|
||||
IS_NOT_DISTINCT_FROM(rhs TimezExpression) BoolExpression
|
||||
|
||||
//LT
|
||||
LT(rhs TimezExpression) BoolExpression
|
||||
//LT_EQ
|
||||
LT_EQ(rhs TimezExpression) BoolExpression
|
||||
//GT
|
||||
GT(rhs TimezExpression) BoolExpression
|
||||
//GT_EQ
|
||||
GT_EQ(rhs TimezExpression) BoolExpression
|
||||
}
|
||||
|
||||
type timezInterfaceImpl struct {
|
||||
parent TimezExpression
|
||||
}
|
||||
|
||||
func (t *timezInterfaceImpl) EQ(rhs TimezExpression) BoolExpression {
|
||||
return eq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timezInterfaceImpl) NOT_EQ(rhs TimezExpression) BoolExpression {
|
||||
return notEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timezInterfaceImpl) IS_DISTINCT_FROM(rhs TimezExpression) BoolExpression {
|
||||
return isDistinctFrom(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timezInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs TimezExpression) BoolExpression {
|
||||
return isNotDistinctFrom(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timezInterfaceImpl) LT(rhs TimezExpression) BoolExpression {
|
||||
return lt(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timezInterfaceImpl) LT_EQ(rhs TimezExpression) BoolExpression {
|
||||
return ltEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timezInterfaceImpl) GT(rhs TimezExpression) BoolExpression {
|
||||
return gt(t.parent, rhs)
|
||||
}
|
||||
|
||||
func (t *timezInterfaceImpl) GT_EQ(rhs TimezExpression) BoolExpression {
|
||||
return gtEq(t.parent, rhs)
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
type prefixTimezExpression struct {
|
||||
expressionInterfaceImpl
|
||||
timezInterfaceImpl
|
||||
|
||||
prefixOpExpression
|
||||
}
|
||||
|
||||
//func newPrefixTimezExpression(operator string, expression Expression) TimezExpression {
|
||||
// timeExpr := prefixTimezExpression{}
|
||||
// timeExpr.prefixOpExpression = newPrefixExpression(expression, operator)
|
||||
//
|
||||
// timeExpr.expressionInterfaceImpl.parent = &timeExpr
|
||||
// timeExpr.timezInterfaceImpl.parent = &timeExpr
|
||||
//
|
||||
// return &timeExpr
|
||||
//}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type timezExpressionWrapper struct {
|
||||
timezInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newTimezExpressionWrap(expression Expression) TimezExpression {
|
||||
timezExpressionWrap := timezExpressionWrapper{Expression: expression}
|
||||
timezExpressionWrap.timezInterfaceImpl.parent = &timezExpressionWrap
|
||||
return &timezExpressionWrap
|
||||
}
|
||||
|
||||
// TimezExp is time with time zone expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as time with time zone expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func TimezExp(expression Expression) TimezExpression {
|
||||
return newTimezExpressionWrap(expression)
|
||||
}
|
||||
53
internal/jet/timez_expression_test.go
Normal file
53
internal/jet/timez_expression_test.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// +build TODO
|
||||
|
||||
package jet
|
||||
|
||||
import "testing"
|
||||
|
||||
var timezVar = Timez(10, 20, 0, 0, 4)
|
||||
|
||||
func TestTimezExpressionEQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimez.EQ(table2ColTimez), "(table1.col_timez = table2.col_timez)")
|
||||
assertClauseSerialize(t, table1ColTimez.EQ(timezVar), "(table1.col_timez = $1::time with time zone)", "10:20:00.000 +04")
|
||||
}
|
||||
|
||||
func TestTimezExpressionNOT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimez.NOT_EQ(table2ColTimez), "(table1.col_timez != table2.col_timez)")
|
||||
assertClauseSerialize(t, table1ColTimez.NOT_EQ(timezVar), "(table1.col_timez != $1::time with time zone)", "10:20:00.000 +04")
|
||||
}
|
||||
|
||||
func TestTimezExpressionIS_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimez.IS_DISTINCT_FROM(table2ColTimez), "(table1.col_timez IS DISTINCT FROM table2.col_timez)")
|
||||
assertClauseSerialize(t, table1ColTimez.IS_DISTINCT_FROM(timezVar), "(table1.col_timez IS DISTINCT FROM $1::time with time zone)", "10:20:00.000 +04")
|
||||
}
|
||||
|
||||
func TestTimezExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimez.IS_NOT_DISTINCT_FROM(table2ColTimez), "(table1.col_timez IS NOT DISTINCT FROM table2.col_timez)")
|
||||
assertClauseSerialize(t, table1ColTimez.IS_NOT_DISTINCT_FROM(timezVar), "(table1.col_timez IS NOT DISTINCT FROM $1::time with time zone)", "10:20:00.000 +04")
|
||||
}
|
||||
|
||||
func TestTimezExpressionLT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimez.LT(table2ColTimez), "(table1.col_timez < table2.col_timez)")
|
||||
assertClauseSerialize(t, table1ColTimez.LT(timezVar), "(table1.col_timez < $1::time with time zone)", "10:20:00.000 +04")
|
||||
}
|
||||
|
||||
func TestTimezExpressionLT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimez.LT_EQ(table2ColTimez), "(table1.col_timez <= table2.col_timez)")
|
||||
assertClauseSerialize(t, table1ColTimez.LT_EQ(timezVar), "(table1.col_timez <= $1::time with time zone)", "10:20:00.000 +04")
|
||||
}
|
||||
|
||||
func TestTimezExpressionGT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimez.GT(table2ColTimez), "(table1.col_timez > table2.col_timez)")
|
||||
assertClauseSerialize(t, table1ColTimez.GT(timezVar), "(table1.col_timez > $1::time with time zone)", "10:20:00.000 +04")
|
||||
}
|
||||
|
||||
func TestTimezExpressionGT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColTimez.GT_EQ(table2ColTimez), "(table1.col_timez >= table2.col_timez)")
|
||||
assertClauseSerialize(t, table1ColTimez.GT_EQ(timezVar), "(table1.col_timez >= $1::time with time zone)", "10:20:00.000 +04")
|
||||
}
|
||||
|
||||
func TestTimezExp(t *testing.T) {
|
||||
assertClauseSerialize(t, TimezExp(table1ColFloat), "table1.col_float")
|
||||
assertClauseSerialize(t, TimezExp(table1ColFloat).LT(Timez(1, 1, 1, 1, 4)),
|
||||
"(table1.col_float < $1::time with time zone)", string("01:01:01.001 +04"))
|
||||
}
|
||||
130
internal/jet/update_statement.go
Normal file
130
internal/jet/update_statement.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/go-jet/jet/execution"
|
||||
"github.com/go-jet/jet/internal/utils"
|
||||
)
|
||||
|
||||
// UpdateStatement is interface of SQL UPDATE statement
|
||||
type UpdateStatement interface {
|
||||
Statement
|
||||
|
||||
SET(value interface{}, values ...interface{}) UpdateStatement
|
||||
MODEL(data interface{}) UpdateStatement
|
||||
|
||||
WHERE(expression BoolExpression) UpdateStatement
|
||||
RETURNING(projections ...Projection) UpdateStatement
|
||||
}
|
||||
|
||||
func newUpdateStatement(table WritableTable, columns []IColumn) UpdateStatement {
|
||||
return &updateStatementImpl{
|
||||
table: table,
|
||||
columns: columns,
|
||||
values: make([]Clause, 0, len(columns)),
|
||||
}
|
||||
}
|
||||
|
||||
type updateStatementImpl struct {
|
||||
table WritableTable
|
||||
columns []IColumn
|
||||
values []Clause
|
||||
where BoolExpression
|
||||
returning []Projection
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) SET(value interface{}, values ...interface{}) UpdateStatement {
|
||||
u.values = unwindRowFromValues(value, values)
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) MODEL(data interface{}) UpdateStatement {
|
||||
u.values = unwindRowFromModel(u.columns, data)
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) WHERE(expression BoolExpression) UpdateStatement {
|
||||
u.where = expression
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) RETURNING(projections ...Projection) UpdateStatement {
|
||||
u.returning = projections
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) accept(visitor visitor) {
|
||||
visitor.visit(u)
|
||||
u.table.accept(visitor)
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) Sql(dialect ...Dialect) (query string, args []interface{}, err error) {
|
||||
out := &SqlBuilder{
|
||||
Dialect: detectDialect(u, dialect...),
|
||||
}
|
||||
|
||||
out.newLine()
|
||||
out.WriteString("UPDATE")
|
||||
|
||||
if utils.IsNil(u.table) {
|
||||
return "", nil, errors.New("jet: table to update is nil")
|
||||
}
|
||||
|
||||
if err = u.table.serialize(UpdateStatementType, out); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(u.columns) == 0 {
|
||||
return "", nil, errors.New("jet: no columns selected")
|
||||
}
|
||||
|
||||
if len(u.values) == 0 {
|
||||
return "", nil, errors.New("jet: no values to updated")
|
||||
}
|
||||
|
||||
out.newLine()
|
||||
out.WriteString("SET")
|
||||
|
||||
if err = out.Dialect.UpdateAssigment()(u.columns, u.values, out); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if u.where == nil {
|
||||
return "", nil, errors.New("jet: WHERE clause not set")
|
||||
}
|
||||
|
||||
if err = out.writeWhere(UpdateStatementType, u.where); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = out.writeReturning(UpdateStatementType, u.returning); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
query, args = out.finalize()
|
||||
return
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) DebugSql(dialect ...Dialect) (query string, err error) {
|
||||
return debugSql(u, dialect...)
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) Query(db execution.DB, destination interface{}) error {
|
||||
return query(u, db, destination)
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) QueryContext(context context.Context, db execution.DB, destination interface{}) error {
|
||||
return queryContext(context, u, db, destination)
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) Exec(db execution.DB) (res sql.Result, err error) {
|
||||
return exec(u, db)
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) ExecContext(context context.Context, db execution.DB) (res sql.Result, err error) {
|
||||
return execContext(context, u, db)
|
||||
}
|
||||
76
internal/jet/update_statement_test.go
Normal file
76
internal/jet/update_statement_test.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUpdateWithOneValue(t *testing.T) {
|
||||
expectedSQL := `
|
||||
UPDATE db.table1
|
||||
SET col_int = $1
|
||||
WHERE table1.col_int >= $2;
|
||||
`
|
||||
stmt := table1.UPDATE(table1ColInt).
|
||||
SET(1).
|
||||
WHERE(table1ColInt.GT_EQ(Int(33)))
|
||||
|
||||
assertStatement(t, stmt, expectedSQL, 1, int64(33))
|
||||
}
|
||||
|
||||
func TestUpdateWithValues(t *testing.T) {
|
||||
expectedSQL := `
|
||||
UPDATE db.table1
|
||||
SET (col_int, col_float) = ($1, $2)
|
||||
WHERE table1.col_int >= $3;
|
||||
`
|
||||
stmt := table1.UPDATE(table1ColInt, table1ColFloat).
|
||||
SET(1, 22.2).
|
||||
WHERE(table1ColInt.GT_EQ(Int(33)))
|
||||
|
||||
assertStatement(t, stmt, expectedSQL, 1, 22.2, int64(33))
|
||||
}
|
||||
|
||||
func TestUpdateOneColumnWithSelect(t *testing.T) {
|
||||
expectedSQL := `
|
||||
UPDATE db.table1
|
||||
SET col_float = (
|
||||
SELECT table1.col_float AS "table1.col_float"
|
||||
FROM db.table1
|
||||
)
|
||||
WHERE table1.col1 = $1
|
||||
RETURNING table1.col1 AS "table1.col1";
|
||||
`
|
||||
stmt := table1.
|
||||
UPDATE(table1ColFloat).
|
||||
SET(
|
||||
table1.SELECT(table1ColFloat),
|
||||
).
|
||||
WHERE(table1Col1.EQ(Int(2))).
|
||||
RETURNING(table1Col1)
|
||||
|
||||
assertStatement(t, stmt, expectedSQL, int64(2))
|
||||
}
|
||||
|
||||
func TestUpdateColumnsWithSelect(t *testing.T) {
|
||||
expectedSQL := `
|
||||
UPDATE db.table1
|
||||
SET (col1, col_float) = (
|
||||
SELECT table1.col_float AS "table1.col_float",
|
||||
table2.col3 AS "table2.col3"
|
||||
FROM db.table1
|
||||
)
|
||||
WHERE table1.col1 = $1
|
||||
RETURNING table1.col1 AS "table1.col1";
|
||||
`
|
||||
stmt := table1.UPDATE(table1Col1, table1ColFloat).
|
||||
SET(table1.SELECT(table1ColFloat, table2Col3)).
|
||||
WHERE(table1Col1.EQ(Int(2))).
|
||||
RETURNING(table1Col1)
|
||||
|
||||
assertStatement(t, stmt, expectedSQL, int64(2))
|
||||
}
|
||||
|
||||
func TestInvalidInputs(t *testing.T) {
|
||||
assertStatementErr(t, table1.UPDATE(table1ColInt).SET(1, 2), "jet: WHERE clause not set")
|
||||
assertStatementErr(t, table1.UPDATE(nil).SET(1, 2), "jet: nil column in columns list")
|
||||
}
|
||||
205
internal/jet/utils.go
Normal file
205
internal/jet/utils.go
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
package jet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/go-jet/jet/internal/utils"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func serializeOrderByClauseList(statement StatementType, orderByClauses []orderByClause, out *SqlBuilder) error {
|
||||
|
||||
for i, value := range orderByClauses {
|
||||
if i > 0 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
|
||||
err := value.serializeForOrderBy(statement, out)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func serializeGroupByClauseList(statement StatementType, clauses []groupByClause, out *SqlBuilder) (err error) {
|
||||
|
||||
for i, c := range clauses {
|
||||
if i > 0 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
return errors.New("jet: nil clause")
|
||||
}
|
||||
|
||||
if err = c.serializeForGroupBy(statement, out); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SerializeClauseList(statement StatementType, clauses []Clause, out *SqlBuilder) (err error) {
|
||||
|
||||
for i, c := range clauses {
|
||||
if i > 0 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
return errors.New("jet: nil clause")
|
||||
}
|
||||
|
||||
if err = c.serialize(statement, out); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func serializeExpressionList(statement StatementType, expressions []Expression, separator string, out *SqlBuilder) error {
|
||||
|
||||
for i, value := range expressions {
|
||||
if i > 0 {
|
||||
out.WriteString(separator)
|
||||
}
|
||||
|
||||
err := value.serialize(statement, out)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SerializeProjectionList(statement StatementType, projections []Projection, out *SqlBuilder) error {
|
||||
for i, col := range projections {
|
||||
if i > 0 {
|
||||
out.WriteString(",")
|
||||
out.newLine()
|
||||
}
|
||||
|
||||
if col == nil {
|
||||
return errors.New("jet: Projection is nil")
|
||||
}
|
||||
|
||||
if err := col.serializeForProjection(statement, out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SerializeColumnNames(columns []IColumn, out *SqlBuilder) error {
|
||||
for i, col := range columns {
|
||||
if i > 0 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
|
||||
if col == nil {
|
||||
return errors.New("jet: nil column in columns list")
|
||||
}
|
||||
|
||||
out.WriteString(col.Name())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ColumnListToProjectionList(columns []Column) []Projection {
|
||||
var ret []Projection
|
||||
|
||||
for _, column := range columns {
|
||||
ret = append(ret, column)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func valueToClause(value interface{}) Clause {
|
||||
if clause, ok := value.(Clause); ok {
|
||||
return clause
|
||||
}
|
||||
|
||||
return literal(value)
|
||||
}
|
||||
|
||||
func unwindRowFromModel(columns []IColumn, data interface{}) []Clause {
|
||||
structValue := reflect.Indirect(reflect.ValueOf(data))
|
||||
|
||||
row := []Clause{}
|
||||
|
||||
mustBe(structValue, reflect.Struct)
|
||||
|
||||
for _, column := range columns {
|
||||
columnName := column.Name()
|
||||
structFieldName := utils.ToGoIdentifier(columnName)
|
||||
|
||||
structField := structValue.FieldByName(structFieldName)
|
||||
|
||||
if !structField.IsValid() {
|
||||
panic("missing struct field for column : " + column.Name())
|
||||
}
|
||||
|
||||
var field interface{}
|
||||
|
||||
if structField.Kind() == reflect.Ptr && structField.IsNil() {
|
||||
field = nil
|
||||
} else {
|
||||
field = reflect.Indirect(structField).Interface()
|
||||
}
|
||||
|
||||
row = append(row, literal(field))
|
||||
}
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
func unwindRowsFromModels(columns []IColumn, data interface{}) [][]Clause {
|
||||
sliceValue := reflect.Indirect(reflect.ValueOf(data))
|
||||
mustBe(sliceValue, reflect.Slice)
|
||||
|
||||
rows := [][]Clause{}
|
||||
|
||||
for i := 0; i < sliceValue.Len(); i++ {
|
||||
structValue := sliceValue.Index(i)
|
||||
|
||||
rows = append(rows, unwindRowFromModel(columns, structValue.Interface()))
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func unwindRowFromValues(value interface{}, values []interface{}) []Clause {
|
||||
row := []Clause{}
|
||||
|
||||
allValues := append([]interface{}{value}, values...)
|
||||
|
||||
for _, val := range allValues {
|
||||
row = append(row, valueToClause(val))
|
||||
}
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
func mustBe(v reflect.Value, expectedKinds ...reflect.Kind) {
|
||||
indirectV := reflect.Indirect(v)
|
||||
types := []string{}
|
||||
|
||||
for _, expectedKind := range expectedKinds {
|
||||
types = append(types, expectedKind.String())
|
||||
if k := indirectV.Kind(); k == expectedKind {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
panic("argument mismatch: expected " + strings.Join(types, " or ") + ", got " + v.Type().String())
|
||||
}
|
||||
63
internal/jet/visitor.go
Normal file
63
internal/jet/visitor.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package jet
|
||||
|
||||
type visitor interface {
|
||||
visit(element acceptsVisitor)
|
||||
}
|
||||
|
||||
type acceptsVisitor interface {
|
||||
accept(visitor visitor)
|
||||
}
|
||||
|
||||
type noOpVisitorImpl struct {
|
||||
}
|
||||
|
||||
func (n *noOpVisitorImpl) accept(visitor visitor) {
|
||||
// NO OP
|
||||
}
|
||||
|
||||
// --------------- dialect finder -----------------//
|
||||
|
||||
type DialectFinder struct {
|
||||
dialects map[string]Dialect
|
||||
}
|
||||
|
||||
func newDialectFinder() *DialectFinder {
|
||||
return &DialectFinder{
|
||||
dialects: make(map[string]Dialect),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *DialectFinder) mustGetDialect() Dialect {
|
||||
if len(f.dialects) == 0 {
|
||||
panic("jet: can't detect dialect")
|
||||
}
|
||||
|
||||
if len(f.dialects) > 1 {
|
||||
panic("jet: more than one dialect detected")
|
||||
}
|
||||
|
||||
for _, dialect := range f.dialects {
|
||||
return dialect
|
||||
}
|
||||
|
||||
panic("jet: internal error")
|
||||
}
|
||||
|
||||
func (f *DialectFinder) visit(element acceptsVisitor) {
|
||||
|
||||
if table, ok := element.(table); ok {
|
||||
dialect := table.dialect()
|
||||
f.dialects[dialect.Name()] = dialect
|
||||
}
|
||||
}
|
||||
|
||||
func detectDialect(element acceptsVisitor, dialectOverride ...Dialect) Dialect {
|
||||
if len(dialectOverride) > 0 {
|
||||
return dialectOverride[0]
|
||||
}
|
||||
|
||||
dialectFinder := newDialectFinder()
|
||||
element.accept(dialectFinder)
|
||||
|
||||
return dialectFinder.mustGetDialect()
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-jet/jet"
|
||||
"github.com/go-jet/jet/execution"
|
||||
"github.com/go-jet/jet/internal/jet"
|
||||
"gotest.tools/assert"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue