From 67e6fca0ceb47c0bf4eeca30d9b0403d6d34a7b7 Mon Sep 17 00:00:00 2001 From: go-jet Date: Sat, 29 Jun 2019 16:58:41 +0200 Subject: [PATCH] Update wiki pages. --- README.md | 2 +- delete_statement.go | 10 +- insert_statement.go | 10 +- lock_statement.go | 10 +- select_statement.go | 10 +- set_statement.go | 10 +- statement.go | 23 ++-- tests/select_test.go | 17 +-- update_statement.go | 10 +- wiki/Expressions.md | 4 +- wiki/SELECT.md | 248 +++++++++++++++++++++++++++++++++++++++++++ wiki/Statements.md | 35 ++++++ wiki/_Sidebar.md | 12 +-- 13 files changed, 350 insertions(+), 51 deletions(-) create mode 100644 wiki/Statements.md diff --git a/README.md b/README.md index c3c77ea..36f605f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![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) Jet is Go SQL Builder for PostgreSQL(support for MySql and OracleSql will be added later). -Jet enables writing type safe SQL queries in Go, and ability to easily convert database query result to desired arbitrary structure. +Jet enables writing type safe SQL queries in Go, and has ability to convert database query result to desired arbitrary structure. ## Contents - [Getting Started](#getting-started) diff --git a/delete_statement.go b/delete_statement.go index 8259fec..d04dced 100644 --- a/delete_statement.go +++ b/delete_statement.go @@ -69,21 +69,21 @@ func (d *deleteStatementImpl) Sql() (query string, args []interface{}, err error } func (d *deleteStatementImpl) DebugSql() (query string, err error) { - return DebugSql(d) + return debugSql(d) } func (d *deleteStatementImpl) Query(db execution.DB, destination interface{}) error { - return Query(d, db, destination) + return query(d, db, destination) } func (d *deleteStatementImpl) QueryContext(db execution.DB, context context.Context, destination interface{}) error { - return QueryContext(d, db, context, destination) + return queryContext(d, db, context, destination) } func (d *deleteStatementImpl) Exec(db execution.DB) (res sql.Result, err error) { - return Exec(d, db) + return exec(d, db) } func (d *deleteStatementImpl) ExecContext(db execution.DB, context context.Context) (res sql.Result, err error) { - return ExecContext(d, db, context) + return execContext(d, db, context) } diff --git a/insert_statement.go b/insert_statement.go index 77d8f6d..ba3e39d 100644 --- a/insert_statement.go +++ b/insert_statement.go @@ -56,7 +56,7 @@ func (i *insertStatementImpl) QUERY(selectStatement SelectStatement) InsertState } func (i *insertStatementImpl) DebugSql() (query string, err error) { - return DebugSql(i) + return debugSql(i) } func (i *insertStatementImpl) Sql() (sql string, args []interface{}, err error) { @@ -147,17 +147,17 @@ func (i *insertStatementImpl) Sql() (sql string, args []interface{}, err error) } func (i *insertStatementImpl) Query(db execution.DB, destination interface{}) error { - return Query(i, db, destination) + return query(i, db, destination) } func (i *insertStatementImpl) QueryContext(db execution.DB, context context.Context, destination interface{}) error { - return QueryContext(i, db, context, destination) + return queryContext(i, db, context, destination) } func (i *insertStatementImpl) Exec(db execution.DB) (res sql.Result, err error) { - return Exec(i, db) + return exec(i, db) } func (i *insertStatementImpl) ExecContext(db execution.DB, context context.Context) (res sql.Result, err error) { - return ExecContext(i, db, context) + return execContext(i, db, context) } diff --git a/lock_statement.go b/lock_statement.go index 6f0997a..705a834 100644 --- a/lock_statement.go +++ b/lock_statement.go @@ -50,7 +50,7 @@ func (l *lockStatementImpl) NOWAIT() LockStatement { } func (l *lockStatementImpl) DebugSql() (query string, err error) { - return DebugSql(l) + return debugSql(l) } func (l *lockStatementImpl) Sql() (query string, args []interface{}, err error) { @@ -94,17 +94,17 @@ func (l *lockStatementImpl) Sql() (query string, args []interface{}, err error) } func (l *lockStatementImpl) Query(db execution.DB, destination interface{}) error { - return Query(l, db, destination) + return query(l, db, destination) } func (l *lockStatementImpl) QueryContext(db execution.DB, context context.Context, destination interface{}) error { - return QueryContext(l, db, context, destination) + return queryContext(l, db, context, destination) } func (l *lockStatementImpl) Exec(db execution.DB) (sql.Result, error) { - return Exec(l, db) + return exec(l, db) } func (l *lockStatementImpl) ExecContext(db execution.DB, context context.Context) (res sql.Result, err error) { - return ExecContext(l, db, context) + return execContext(l, db, context) } diff --git a/select_statement.go b/select_statement.go index 356a3c7..2a6670b 100644 --- a/select_statement.go +++ b/select_statement.go @@ -192,7 +192,7 @@ func (s *selectStatementImpl) Sql() (query string, args []interface{}, err error } func (s *selectStatementImpl) DebugSql() (query string, err error) { - return DebugSql(s) + return debugSql(s) } func (s *selectStatementImpl) projections() []projection { @@ -290,17 +290,17 @@ func (s *selectLockImpl) serialize(statement statementType, out *queryData, opti } func (s *selectStatementImpl) Query(db execution.DB, destination interface{}) error { - return Query(s, db, destination) + return query(s, db, destination) } func (s *selectStatementImpl) QueryContext(db execution.DB, context context.Context, destination interface{}) error { - return QueryContext(s, db, context, destination) + return queryContext(s, db, context, destination) } func (s *selectStatementImpl) Exec(db execution.DB) (res sql.Result, err error) { - return Exec(s, db) + return exec(s, db) } func (s *selectStatementImpl) ExecContext(db execution.DB, context context.Context) (res sql.Result, err error) { - return ExecContext(s, db, context) + return execContext(s, db, context) } diff --git a/set_statement.go b/set_statement.go index 46997c5..8efca36 100644 --- a/set_statement.go +++ b/set_statement.go @@ -200,21 +200,21 @@ func (s *setStatementImpl) Sql() (query string, args []interface{}, err error) { } func (s *setStatementImpl) DebugSql() (query string, err error) { - return DebugSql(s) + return debugSql(s) } func (s *setStatementImpl) Query(db execution.DB, destination interface{}) error { - return Query(s, db, destination) + return query(s, db, destination) } func (s *setStatementImpl) QueryContext(db execution.DB, context context.Context, destination interface{}) error { - return QueryContext(s, db, context, destination) + return queryContext(s, db, context, destination) } func (s *setStatementImpl) Exec(db execution.DB) (res sql.Result, err error) { - return Exec(s, db) + return exec(s, db) } func (s *setStatementImpl) ExecContext(db execution.DB, context context.Context) (res sql.Result, err error) { - return ExecContext(s, db, context) + return execContext(s, db, context) } diff --git a/statement.go b/statement.go index 339a3d6..a681525 100644 --- a/statement.go +++ b/statement.go @@ -9,19 +9,28 @@ import ( ) type Statement interface { - // String returns generated SQL as string. + // Sql returns parametrized sql query with list of arguments. + // err is returned if statement is not composed correctly Sql() (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() (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(db execution.DB, context context.Context, 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(db execution.DB, context context.Context) (sql.Result, error) } -func DebugSql(statement Statement) (string, error) { +func debugSql(statement Statement) (string, error) { sqlQuery, args, err := statement.Sql() if err != nil { @@ -38,7 +47,7 @@ func DebugSql(statement Statement) (string, error) { return debugSqlQuery, nil } -func Query(statement Statement, db execution.DB, destination interface{}) error { +func query(statement Statement, db execution.DB, destination interface{}) error { query, args, err := statement.Sql() if err != nil { @@ -48,7 +57,7 @@ func Query(statement Statement, db execution.DB, destination interface{}) error return execution.Query(db, context.Background(), query, args, destination) } -func QueryContext(statement Statement, db execution.DB, context context.Context, destination interface{}) error { +func queryContext(statement Statement, db execution.DB, context context.Context, destination interface{}) error { query, args, err := statement.Sql() if err != nil { @@ -58,7 +67,7 @@ func QueryContext(statement Statement, db execution.DB, context context.Context, return execution.Query(db, context, query, args, destination) } -func Exec(statement Statement, db execution.DB) (res sql.Result, err error) { +func exec(statement Statement, db execution.DB) (res sql.Result, err error) { query, args, err := statement.Sql() if err != nil { @@ -68,7 +77,7 @@ func Exec(statement Statement, db execution.DB) (res sql.Result, err error) { return db.Exec(query, args...) } -func ExecContext(statement Statement, db execution.DB, context context.Context) (res sql.Result, err error) { +func execContext(statement Statement, db execution.DB, context context.Context) (res sql.Result, err error) { query, args, err := statement.Sql() if err != nil { diff --git a/tests/select_test.go b/tests/select_test.go index 13cc679..4dfc337 100644 --- a/tests/select_test.go +++ b/tests/select_test.go @@ -67,8 +67,12 @@ ORDER BY payment.payment_id ASC LIMIT 30; ` - query := SELECT(Payment.AllColumns, Customer.AllColumns). - FROM(Payment.INNER_JOIN(Customer, Payment.CustomerID.EQ(Customer.CustomerID))). + query := SELECT( + Payment.AllColumns, + Customer.AllColumns, + ). + FROM(Payment. + INNER_JOIN(Customer, Payment.CustomerID.EQ(Customer.CustomerID))). ORDER_BY(Payment.PaymentID.ASC()). LIMIT(30) @@ -599,7 +603,9 @@ SELECT actor.actor_id AS "actor.actor_id", film_actor.actor_id AS "film_actor.actor_id", film_actor.film_id AS "film_actor.film_id", film_actor.last_update AS "film_actor.last_update", - "rFilms"."film.title" AS "film.title" + "rFilms"."film.film_id" AS "film.film_id", + "rFilms"."film.title" AS "film.title", + "rFilms"."film.rating" AS "film.rating" FROM dvds.actor INNER JOIN dvds.film_actor ON (actor.actor_id = film_actor.film_id) INNER JOIN ( @@ -621,7 +627,6 @@ FROM dvds.actor AsTable("rFilms") rFilmId := Film.FilmID.From(rRatingFilms) - rTitle := Film.Title.From(rRatingFilms) query := Actor. INNER_JOIN(FilmActor, Actor.ActorID.EQ(FilmActor.FilmID)). @@ -629,10 +634,10 @@ FROM dvds.actor SELECT( Actor.AllColumns, FilmActor.AllColumns, - rTitle.AS("film.title"), + rRatingFilms.AllColumns(), ) - fmt.Println(query.Sql()) + fmt.Println(query.DebugSql()) assertStatementSql(t, query, expectedQuery) diff --git a/update_statement.go b/update_statement.go index 32cbf67..0eae343 100644 --- a/update_statement.go +++ b/update_statement.go @@ -136,21 +136,21 @@ func (u *updateStatementImpl) Sql() (sql string, args []interface{}, err error) } func (u *updateStatementImpl) DebugSql() (query string, err error) { - return DebugSql(u) + return debugSql(u) } func (u *updateStatementImpl) Query(db execution.DB, destination interface{}) error { - return Query(u, db, destination) + return query(u, db, destination) } func (u *updateStatementImpl) QueryContext(db execution.DB, context context.Context, destination interface{}) error { - return QueryContext(u, db, context, destination) + return queryContext(u, db, context, destination) } func (u *updateStatementImpl) Exec(db execution.DB) (res sql.Result, err error) { - return Exec(u, db) + return exec(u, db) } func (u *updateStatementImpl) ExecContext(db execution.DB, context context.Context) (res sql.Result, err error) { - return ExecContext(u, db, context) + return execContext(u, db, context) } diff --git a/wiki/Expressions.md b/wiki/Expressions.md index 240a910..811b01f 100644 --- a/wiki/Expressions.md +++ b/wiki/Expressions.md @@ -137,7 +137,7 @@ Following operators are only available on string expressions: | NOT_SIMILAR_TO | table.Film.Name.NOT_SIMILAR_TO(String("%Wind%")) | staff.active NOT SIMILAR TO %Wind% | -## Cast operators +## SQL Cast operators Cast operators allow expressions to be casted to some other database type. SQL builder expression type changes accordingly to database type. @@ -158,4 +158,6 @@ SQL builder expression type changes accordingly to database type. | TO_TIMESTAMP | table.Film.Description.TO_TIMESTAMP() | film.description::timestamp | | TO_TIMESTAMPZ | table.Film.Description.TO_TIMESTAMPZ() | film.description::timestampz | +## SQL builder cast +TODO: diff --git a/wiki/SELECT.md b/wiki/SELECT.md index e69de29..e903190 100644 --- a/wiki/SELECT.md +++ b/wiki/SELECT.md @@ -0,0 +1,248 @@ + +SELECT statement is used to retrieve records from one or more tables in PostgreSQL. +More about SELECT statement in postgres can be found at: https://www.postgresql.org/docs/11/sql-select.html + + +## SELECT statement clauses + +Following clauses are supported - SELECT, DISTINCT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET and FOR. +_This list might be extended with feature Jet releases._ + +##### 1. SELECT clause + +Sample SELECT clause written in Go: +``` + jet.SELECT( + jet.Int(1).ADD(jet.Int(12)).SUB(jet.Int(21)), // arbitrary expression + table.Film.Name, // column + table.Customer.FirstName.CONCAT(table.Customer.LastName).AS("FullName") // alias + ) +``` + +If jet and autogenerated table package, is dot "." imported, above statement will look more like native SQL: + +``` + SELECT( + Int(1).ADD(Int(12)).SUB(Int(21)), // arbitrary expression + Film.Name, // column + Customer.FirstName.CONCAT(Customer.LastName).AS("FullName") // alias + ) +``` +*For the most of the wiki dot import is used.* + +Above SQL clause written in go will produce following raw SQL: + +``` +SELECT 1 + 12 - 21, + film.name AS "film.name", -- + customer.first_name || customer.last_name AS "FullName" +``` +`film.name AS "film.name"` - column names are aliased by default. Alias is used during execution to map row result to +appropriate `model` structure. + +##### 2. DISTINCT clause + +``` +SELECT(Film.Name). +.DISTINCT(). +``` + +Raw SQL: + +``` +SELECT DISTINCT film.name +``` + +##### 3. FROM clause + +``` +Go: +1).FROM(Film) +2).FROM( + Film. + INNER_JOIN(Language, Langauge.LanguageID.EQ(Film.FilmID)) + ) + +SQL: +1)FROM dvds.film +2)FROM Film + INNER JOIN Language ON (Language.LanguageID = Film.FilmID) +``` + +##### 4. WHERE clause + +``` +Go: +.WHERE(Film.Length.GT(Int(150))) + +SQL: +WHERE film.length > 150 +``` + +##### 5. GROUP BY clause +``` +Go: +.GROUP_BY(Film.Length) + +SQL: +GROUP BY film.length +``` + +##### 6. HAVING clause +``` +Go: +.HAVING(Film.Length.GT(Int(150)) + +SQL: +HAVING film.length > 150 +``` + +##### 7. ORDER BY clause +``` +Go: +.ORDER_BY(Film.Length) + +SQL: +ORDER BY film.length +``` +##### 8. LIMIT clause +``` +Go: +.LIMIT(11) + +SQL: +LIMIT 11 +``` + +##### 9. OFFSET clause +``` +Go: +.OFFSET(11) + +SQL: +OFFSET 11 +``` + +##### 9. FOR clause +``` +Go: +.FOR(jet.NO_KEY_UPDATE) + +SQL: +FOR NO KEY UPDATE +``` + +### Two forms of select statements in Jet + +#### 1. Classical select statement +Columns selected are before tables selected +``` +SELECT( + Payment.AllColumns, + Customer.AllColumns, +). +FROM(Payment. + INNER_JOIN(Customer, Payment.CustomerID.EQ(Customer.CustomerID))). +ORDER_BY(Payment.PaymentID.ASC()). +LIMIT(30) +``` + +#### 2. Jet select statement +Joined tables(or just one) are before columns selected. There is no FROM. +``` +Payment. + INNER_JOIN(Customer, Payment.CustomerID.EQ(Customer.CustomerID)) +SELECT( + Payment.AllColumns, + Customer.AllColumns, +). +ORDER_BY(Payment.PaymentID.ASC()). +LIMIT(30) +``` + +Second form is added, because sometimes feels more natural to first think about the tables +of interest, and than about the columns. +**Both forms produces exactly the same raw SQL.** + +## Sub-queries + +How to write SELECT statement with sub-queries? + +Sub-queries are composed first: +``` +// select film_id and title from film table that have 'R' rating. +rRatingFilms := Film. + SELECT( + Film.FilmID, + Film.Title, + ). + WHERE(Film.Rating.EQ(enum.MpaaRating.R)). + AsTable("rFilms") +``` +`AsTable("rFilms")` - allows SELECT statements to be seen as table. + +To use sub-query columns in SELECT statement expressions we have to export column from sub-query, +using `From` method. + +``` +rFilmId := Film.FilmID.From(rRatingFilms) //used for join condition +``` + +Now we can write: + +``` +query := Actor. + INNER_JOIN(FilmActor, Actor.ActorID.EQ(FilmActor.FilmID)). + INNER_JOIN(rRatingFilms, FilmActor.FilmID.EQ(rFilmId)). + SELECT( + Actor.AllColumns, + FilmActor.AllColumns, + rRatingFilms.AllColumns(), + ) +``` +`rRatingFilms.AllColumns(),` - sub-query columns needed for projection can be exported with AllColumns() method. +No need to export each one of them with `From`. + +Debug SQL of above example: +```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_actor.actor_id AS "film_actor.actor_id", + film_actor.film_id AS "film_actor.film_id", + film_actor.last_update AS "film_actor.last_update", + "rFilms"."film.film_id" AS "film.film_id", + "rFilms"."film.title" AS "film.title", + "rFilms"."film.rating" AS "film.rating" +FROM dvds.actor + INNER JOIN dvds.film_actor ON (actor.actor_id = film_actor.film_id) + INNER JOIN ( + SELECT film.film_id AS "film.film_id", + film.title AS "film.title", + film.rating AS "film.rating" + FROM dvds.film + WHERE film.rating = 'R' + ) AS "rFilms" ON (film_actor.film_id = "rFilms"."film.film_id"); +``` + + +#### What If sub-query projection is not a column? +For instance: +``` +customersPayments := Payment. + SELECT( + Payment.CustomerID, + SUMf(Payment.Amount).AS("amount_sum"), + ). + GROUP_BY(Payment.CustomerID). + AsTable("customer_payment_sum") + +customerId := Payment.CustomerID.From(customersPayments) +``` + +To export `"amount_sum"` from `customersPayments` sub-query we have to create column first with appropriate type and a name. +Because SUMf produces float expression we will create FloatColumn with name of the column alias `"amount_sum"` +``` +amountSum := FloatColumn("amount_sum").From(customersPayments) +``` \ No newline at end of file diff --git a/wiki/Statements.md b/wiki/Statements.md new file mode 100644 index 0000000..dfae709 --- /dev/null +++ b/wiki/Statements.md @@ -0,0 +1,35 @@ + +Following statements are supported: + +* [SELECT](https://github.com/go-jet/jet/wiki/SELECT) +* [INSERT](https://github.com/go-jet/jet/wiki/INSERT) +* [UPDATE](https://github.com/go-jet/jet/wiki/UPDATE) +* [DELETE](https://github.com/go-jet/jet/wiki/DELETE) +* [LOCK](https://github.com/go-jet/jet/wiki/LOCK) + +_This list might be extended with feature Jet releases._ + +There is a common set of action that can be performed for each statement type: + +- `Sql() (query string, args []interface{}, err error)` - retrieves parametrized sql query with list of arguments +- `DebugSql() (query string, err error)` - retrieves debug query where every parametrized placeholder is replaced with its argument. +- `Query(db execution.DB, destination interface{}) error` - executes statements over database connection db and stores row result in destination. +- `QueryContext(db execution.DB, context context.Context, destination interface{}) error` - executes statement with a context over database connection db and stores row result in destination. +- `Exec(db execution.DB) (sql.Result, error)` - executes statement over db connection without returning any rows. +- `ExecContext(db execution.DB, context context.Context) (sql.Result, error)` - executes statement with context over db connection without returning any rows. + +Database connection can be of any type that implements following interface: + +```go +type DB interface { + Exec(query string, args ...interface{}) (sql.Result, error) + ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) + Query(query string, args ...interface{}) (*sql.Rows, error) + QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) +} +``` + +These include but are not limited to: +- `sql.DB` +- `sql.Tx` +- `sql.Conn` \ No newline at end of file diff --git a/wiki/_Sidebar.md b/wiki/_Sidebar.md index 22a1fef..ef6ac47 100644 --- a/wiki/_Sidebar.md +++ b/wiki/_Sidebar.md @@ -3,9 +3,9 @@ * [Generator](https://github.com/go-jet/jet/wiki/Generator) * [Writing SQL in Go]() * [Expressions](https://github.com/go-jet/jet/wiki/Expressions) - * [Statements]() - * [SELECT]() - * [INSERT]() - * [UPDATE]() - * [DELETE]() - * [LOCK]() \ No newline at end of file + * [Statements](https://github.com/go-jet/jet/wiki/Statements) + * [SELECT](https://github.com/go-jet/jet/wiki/SELECT) + * [INSERT](https://github.com/go-jet/jet/wiki/INSERT) + * [UPDATE](https://github.com/go-jet/jet/wiki/UPDATE) + * [DELETE](https://github.com/go-jet/jet/wiki/DELETE) + * [LOCK](https://github.com/go-jet/jet/wiki/LOCK) \ No newline at end of file