Package structure refactor.

This commit is contained in:
go-jet 2019-08-03 14:10:47 +02:00
parent 3d8e872336
commit 23fd973699
125 changed files with 2401 additions and 1818 deletions

547
internal/jet/README.md Normal file
View file

@ -0,0 +1,547 @@
# Jet
[![CircleCI](https://circleci.com/gh/go-jet/jet/tree/develop.svg?style=svg&circle-token=97f255c6a4a3ab6590ea2e9195eb3ebf9f97b4a7)](https://circleci.com/gh/go-jet/jet/tree/develop)
[![codecov](https://codecov.io/gh/go-jet/jet/branch/develop/graph/badge.svg)](https://codecov.io/gh/go-jet/jet)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-jet/jet)](https://goreportcard.com/report/github.com/go-jet/jet)
[![Documentation](https://godoc.org/github.com/go-jet/jet?status.svg)](http://godoc.org/github.com/go-jet/jet)
[![GitHub release](https://img.shields.io/github/release/go-jet/jet.svg)](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
View 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
}

View 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)
}

View 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
View 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
}

View 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
View 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) + `'`
}

View 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
View 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 "" }

View 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"`)
}

View 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 := &timestampColumnImpl{}
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 := &timestampzColumnImpl{}
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
}

View 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"`)
}

View 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)
}

View 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)
}

View 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
View 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
}

View 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
View 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
}

View 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))
}

View 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)
}

View 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))
}

View 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 := &timestampFunc{}
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 := &timestampzFunc{}
timestampzFunc.funcExpressionImpl = *newFunc(name, expressions, timestampzFunc)
timestampzFunc.timestampzInterfaceImpl.parent = timestampzFunc
return timestampzFunc
}

View 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")
}

View file

@ -0,0 +1,5 @@
package jet
type groupByClause interface {
serializeForGroupBy(statement StatementType, out *SqlBuilder) error
}

View 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)
}

View 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")
}

View 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)
}

View 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
View 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
}

View 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
}

View file

@ -0,0 +1,7 @@
package jet
import "testing"
func TestRawExpression(t *testing.T) {
assertClauseSerialize(t, RAW("current_database()"), "current_database()")
}

View 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)
}

View 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
View 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
}

View 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))
}

View 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}
}

View 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
}

View 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
}

View 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
)
);
`)
}

View 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
}

View 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
}

View 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
View 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...)
}

View 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)
}

View 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
View 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
View 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
View 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)
}

View 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)
}

View 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"))
}

View 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 = &timestampExpressionWrap
return &timestampExpressionWrap
}
// 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)
}

View 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")
}

View 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 = &timestampzExpressionWrap
return &timestampzExpressionWrap
}
// 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)
}

View 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")
}

View 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)
}

View 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"))
}

View 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)
}

View 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
View 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
View 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()
}

View file

@ -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"