Remove wiki.

This commit is contained in:
go-jet 2019-07-17 14:52:21 +02:00
parent b817f57035
commit c97c6d1da4
13 changed files with 0 additions and 1680 deletions

View file

@ -1,58 +0,0 @@
DELETE statement deletes rows that satisfy the WHERE clause from the specified table. More about delete statement
in PostgreSQL: https://www.postgresql.org/docs/11/sql-delete.html
Following clauses are supported:
- WHERE(delete_condition) - Only rows for which delete condition returns true will be deleted.
- RETURNING(output_expression...) - An expressions to be computed and returned by the DELETE command after each row is deleted.
The expression can use any column names of the table. Write _TableName_.AllColumns to return all columns.
### Example
```
// delete all links with name 'Gmail' and 'Outlook'
deleteStmt := Link.
DELETE().
WHERE(Link.Name.IN(String("Gmail"), String("Outlook")))
```
Debug sql of above statement:
```sql
DELETE FROM test_sample.link -- test_sample is name of the schema
WHERE link.name IN ('Gmail', 'Outlook');
```
### Execute statement
To execute delete statement and get sql.Result:
```
res, err := deleteStmt.Exec(db)
```
To execute delete statement and return records deleted,
delete statement has to have RETURNING clause:
```
deleteStmt := Link.
DELETE().
WHERE(Link.Name.IN(String("Gmail"), String("Outlook"))).
RETURNING(Link.AllColumns)
dest := []model.Link{}
err := deleteStmt.Query(db, &dest)
```
Use `ExecContext` and `QueryContext` to provide context object to execution.
##### SQL table used for the example:
```sql
CREATE TABLE IF NOT EXISTS link (
id serial PRIMARY KEY,
url VARCHAR (255) NOT NULL,
name VARCHAR (255) NOT NULL,
description VARCHAR (255)
);
```

View file

@ -1,195 +0,0 @@
Jet sql builder supports following expression types:
- Bool expressions
- Integer expressions
- Float expressions
- String expressions
- Date expressions
- Time expressions
- Timez expressions (Time with time zone)
- Timestamp expressions
- Timestampz expressions (Timestamp with time zone)
_This list might be extended with feature Jet releases._
### Literal Types
For every expression type there is a method to create one expression literal type.
Literal type examples:
```
jet.Bool(true)
jet.Integer(11)
jet.Float(23.44)
jet.String("John Doe")
jet.Date(2010, 12, 3)
jet.Time(23, 6, 6, 1)
jet.Timez(23, 6, 6, 222, +200)
jet.Timestamp(2010, 10, 21, 15, 30, 12, 333)
jet.Timestampz(2010, 10, 21, 15, 30, 12, 444, 0)
```
There is also:
```
jet.NULL
jet.STAR (alias for *)
```
### Column Types
Every sql builder table column belongs to one expression type. There are following column types:
```
jet.ColumnBool
jet.ColumnInteger
jet.ColumnFloat
jet.ColumnString
jet.ColumnDate
jet.ColumnTime
jet.ColumnTimez
jet.ColumnTimestamp
jet.ColumnTimestampz
```
Columns and literals can form arbitrary expressions but have to follow valid SQL expression syntax.
For instance valid expressions are:
```
jet.Bool(true).AND(jet.Bool(false)).IS_FALSE()
(table.Film.Length.GT(jet.Int(100)).AND(table.Film.Length.LT(jet.Int(200))).IS_TRUE()
```
Some of the invalid expressions. These expressions will cause go build to break.
```
jet.Bool(true).ADD(jet.Int(11)) // can't add bool and integer
jet.Int(11).LIKE(jet.Float(22.2)) // integer expressions doesn't have LIKE method
```
## Comparision Operators
Jet supports following comparison operators for all expression types:
| Method | Example | Generated sql |
| ------------------------------ | ------------------------------------------------ |---------------------------- |
| EQ | jet.Int(1).EQ(table.Film.Length) | 1 = film.length |
| NOT_EQ | jet.Int(1).EQ(table.Film.Length) | 1 != film.length |
| IS_DISTINCT_FROM | jet.Int(1).IS_DISTINCT_FROM(table.Film.Length) | 1 IS DISTINCT FROM film.length |
| IS_NOT_DISTINCT_FROM | jet.Int(1).IS_NOT_DISTINCT_FROM(table.Film.Length) | 1 IS NOT DISTINCT FROM film.length |
| LT | jet.Int(1).LT(table.Film.Length) | 1 < film.length |
| LT_EQ | jet.Int(1).LT_EQ(table.Film.Length) | 1 <= film.length |
| GT | jet.Int(1).GT(table.Film.Length) | 1 > film.length |
| GT_EQ | jet.Int(1).GT_EQ(table.Film.Length) | 1 >= film.length |
*Left-hand side and right-hand side of operators have to be of the same type*
## Arithmetic Operators
Following arithmetic operators are supported for integer and float expressions.
If the first argument is float expression, second argument can be integer or float expression.
If the first argument is integer expression second argument can only be integer expression.
| Method | Example | Generated sql |
| ------------------------------ | ------------------------------------------------ |---------------------------- |
| ADD | jet.Int(1).ADD(table.Film.Length) | 1 + film.length |
| SUB | jet.Float(1.11).SUB(Int(1)) | 1.11 + 1 |
| MUL | jet.Int(1).MUL(table.Film.Length) | 1 * film.length |
| DIV | jet.Float(1.11).DIV(jet.Float(3.33) | 1.11 / 3.33 |
| MOD | jet.Int(10).MOD(table.Film.Length) | 10 % film.length |
| POW | jet.Float(10.01).POW(table.Film.Length) | 10.01 ^ film.length |
## Bit Operators
Following operators are only available on integer expressions:
| Method | Example | Generated sql |
| ------------------------------ | ------------------------------------------------ |---------------------------- |
| BIT_AND | jet.Int(11).BIT_AND(table.Film.Length) | 11 & film.length |
| BIT_OR | jet.Int(11).BIT_OR(table.Film.Length) | 11 \| film.length |
| BIT_XOR | jet.Int(11).BIT_XOR(table.Film.Length) | 11 # film.length |
| BIT_NOT | BIT_NOT(table.Film.Length) | ~ film.length |
| BIT_SHIFT_LEFT | jet.Int(11).BIT_SHIFT_LEFT(table.Film.Length) | 11 >> film.length |
| BIT_SHIFT_RIGHT | jet.Int(11).BIT_SHIFT_RIGHT(table.Film.Length) | 11 >> film.length |
## Logical Operators
Following operators are only available on boolean expressions:
| Method | Example | Generated sql |
| ------------------------------ | ---------------------------------------------------------|---------------------------- |
| IS_TRUE | table.Staff.Active.IS_TRUE() | staff.active IS TRUE |
| IS_NOT_TRUE | (table.Staff.Active.AND(jet.Bool(true))).IS_NOT_TRUE() | (staff.active AND true) IS NOT TRUE |
| IS_FALSE | jet.Bool(false).IS_FALSE() | false IS FALSE |
| IS_NOT_FALSE | jet.Bool(true).IS_NOT_FALSE() | true IS NOT FALSE |
| IS_UNKNOWN | table.Staff.Active.IS_UNKNOWN() | staff.active IS UNKNOWN |
| IS_NOT_UNKNOWN | table.Staff.Active.IS_NOT_UNKNOWN() | staff.active IS NOT UNKNOWN |
## String Operators
Following operators are only available on string expressions:
| Method | Example | Generated sql |
| ------------------------------ | ---------------------------------------------------------|---------------------------- |
| CONCAT | table.Film.Name.CONCAT(table.Film.Description) | film.name \|\| film.description |
| LIKE | table.Film.Name.LIKE(String("%Wind%")) | film.name LIKE %Wind% |
| NOT_LIKE | table.Film.Name.NOT_LIKE(String("%Wind%")) | staff.active NOT LIKE %Wind% |
| SIMILAR_TO | table.Film.Name.SIMILAR_TO(String("%Wind%")) | staff.active SIMILAR TO %Wind% |
| NOT_SIMILAR_TO | table.Film.Name.NOT_SIMILAR_TO(String("%Wind%")) | staff.active NOT SIMILAR TO %Wind% |
## SQL Cast Operators
Cast operators allow expressions to be casted to some other database type.
SQL builder expression type changes accordingly to database type.
| Method | Example | Generated sql |
| ------------------------------ | ----------------------------------------------- | --------------------------------------------- |
| CAST(exp).AS_BOOL() | CAST(table.Film.Description).AS_BOOL() | film.description::boolean |
| CAST(exp).AS_SMALLINT() | CAST(table.Film.Description).AS_SMALLINT() | film.description::smallint |
| CAST(exp).AS_INTEGER() | CAST(table.Film.Description).AS_INTEGER() | film.description::integer |
| CAST(exp).AS_BIGINT() | CAST(table.Film.Description).AS_BIGINT() | film.description::bigint |
| CAST(exp).AS_NUMERIC() | CAST(table.Film.Description).AS_NUMERIC(10, 6) | film.description::numeric(10,6) |
| CAST(exp).AS_REAL() | CAST(table.Film.Description).AS_REAL() | film.description::real |
| CAST(exp).AS_DOUBLE() | CAST(table.Film.Description).AS_DOUBLE() | film.description::double |
| CAST(exp).AS_TEXT() | CAST(table.Film.Description).AS_TEXT() | film.description::text |
| CAST(exp).AS_DATE() | CAST(table.Film.Description).AS_DATE() | film.description::date |
| CAST(exp).AS_TIME() | CAST(table.Film.Description).AS_TIME() | film.description::time without time zone |
| CAST(exp).AS_TIMEZ() | CAST(table.Film.Description).AS_TIMEZ() | film.description::time with time zone |
| CAST(exp).AS_TIMESTAMP() | CAST(table.Film.Description).AS_TIMESTAMP() | film.description::timestamp without time zone |
| CAST(exp).AS_TIMESTAMPZ() | CAST(table.Film.Description).AS_TIMESTAMPZ() | film.description::timestamp with time zone |
## SQL Builder Cast Wrapper
For some expressions sql builder can't deduce expression type directly. For instance scalar sub-query:
```
( SELECT(MAXf(Film.RentalRate)).
FROM(Film) ).LT(Float(11.1))
```
This expression would not compile, because sub-query, although calculates one scalar float value, it is not a float expression.
To fix this sub-query can be cast to some float type, or just wrapped as float expression:
```
FloatExp( SELECT(MAXf(Film.RentalRate)).
FROM(Film) ).LT(Float(11.1))
```
There are wrappers for all supported types:
```
- BoolExp(exp)
- IntExp(exp)
- FloatExp(exp)
- StringExp(exp)
- DateExp(exp)
- TimeExp(exp)
- TimezExp(exp)
- TimestampExp(exp)
- TimestampzExp(exp)
```
**Cast wrapper does NOT inject cast operator to generated SQL.**
## RAW Operator
There is a RAW operator expression, that accepts raw sql as a string. It can be used for any unsupported functions, operators or expressions.
For example:
```RAW("current_database()")``` _can be cast or wrapped, as needed._

View file

@ -1,107 +0,0 @@
Before we can write SQL queries in Go we have to generate necessary Go files.
To generate files we need running database instance containing already defined database schema.
This files can be generated in two ways:
#### 1) Generating from command line
Install jet 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.
Test jet generator can be found in the PATH.
```sh
jet -h
Usage of jet:
-host string
Database host path (Example: localhost)
-port string
Database port
-user string
Database user
-password string
The users password
-dbname string
name of the database
-schema string
Database schema name. (default "public")
-path string
Destination dir for generated files.
-sslmode string
Whether or not to use SSL(optional) (default "disable")
-params string
Additional connection string parameters(optional)
```
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 `jet`, database `jetdb` and schema `dvds` we will use this command:
```sh
jet -host=localhost -port=5432 -user=jet -password=jet -dbname=jetdb -schema=dvds -path=./gen
```
```sh
Connecting to postgres database: host=localhost port=5432 user=jet password=jet 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
```
#### 2) Generating from code
The same files can be generated manually from code.
```
import "github.com/go-jet/jet/generator/postgres"
...
err = postgres.Generate("./gen", postgres.DBConnection{
Host: "localhost",
Port: "5432",
User: "jet",
Password: "jet",
DBName: "jetdb",
SchemaName: "dvds",
SslMode: "disable",
})
```
In both ways, generator 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 tables and enums.
Generated files folder structure will look like this:
```
|-- gen # destination folder
| `-- 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
| |-- film.go
...
| |-- model # Plain Old Data for every enum and table
| | |-- actor.go
| | |-- address.go
| | |-- film.go
...
```
Table and enums from database schema are used as a template to generate two types of Go files:
* SQL Builder files - used to write type safe SQL statements in Go (`enum` and `table` package)
* Model files - used to combine and store result from database queries (`model` package)

View file

@ -1,137 +0,0 @@
INSERT statement is used to insert a single record or multiple records
into a table. More about PostgreSQL INSERT statement can be found here: https://www.postgresql.org/docs/11/sql-insert.html
Following clauses are supported:
- INSERT(columns...) - list of columns for insert
- VALUES(values...) - list of values
- MODEL(model) - list of values for columns will be extracted from model object
- MODELS([]model) - list of values for columns will be extracted from list of model objects
- QUERY(select) - select statement that supplies the rows to be inserted.
- RETURNING(output_expression...) - An expressions to be computed and returned by the INSERT statement after each row is inserted.
The expressions can use any column names of the table. Write _TableName_.AllColumns to return all columns.
_This list might be extended with feature Jet releases._
## Example
### Insert row by row
```
insertStmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
VALUES(101, "http://www.google.com", "Google", DEFAULT).
VALUES(102, "http://www.yahoo.com", "Yahoo", nil)
```
Debug SQL of above insert statement:
```sql
INSERT INTO test_sample.link (id, url, name, description) VALUES
(100, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT),
(101, 'http://www.google.com', 'Google', DEFAULT),
(102, 'http://www.yahoo.com', 'Yahoo', NULL)
```
There is also shorthand notation for inserting model data:
```
tutorial := model.Link{
ID: 100,
URL: "http://www.postgresqltutorial.com",
Name: "PostgreSQL Tutorial",
}
google := model.Link{
ID: 101,
URL: "http://www.google.com",
Name: "Google",
}
yahoo := model.Link{
ID: 102,
URL: "http://www.yahoo.com",
Name: "Yahoo",
}
insertStmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
MODEL(turorial).
MODEL(google).
MODEL(yahoo)
```
Or event shorter if model data is in the slice:
```
insertStmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
MODELS([]model.Link{turorial, google, yahoo})
```
`Link.ID, Link.URL, Link.Name, Link.Description` - is the same as Link.AllColumns
so above statement can be simplified to:
```
insertStmt := Link.INSERT(Link.AllColumns).
MODELS([]model.Link{turorial, google, yahoo})
```
`Link.ID` is a primary key autoincrement column so it can be omitted in INSERT statement.
`Link.MutableColumns` - is shorthand notation for list of all columns minus primary key columns.
```
insertStmt := Link.INSERT(Link.MutableColumns).
MODELS([]model.Link{turorial, google, yahoo})
```
Inserts using `VALUES`, `MODEL` and `MODELS` can appear as the part of the same insert statement.
```
insertStmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description, Link.Description).
VALUES(101, "http://www.google.com", "Google", DEFAULT, DEFAULT).
MODEL(turorial).
MODELS([]model.Link{yahoo})
```
### Insert using query
```
// duplicate first 10 entries
insertStmt := Link.
INSERT(Link.URL, Link.Name).
QUERY(
SELECT(Link.URL, Link.Name).
FROM(Link).
WHERE(Link.ID.GT(Int(0)).AND(Link.ID.LT_EQ(10))),
)
```
## Execute statement
To execute insert statement and get sql.Result:
```
res, err := insertStmt.Exec(db)
```
To execute insert statement and return row records inserted, insert statement has to have RETURNING clause:
```
insertStmt := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
VALUES(101, "http://www.google.com", "Google", DEFAULT).
RETURNING(Link.ID, Link.URL, Link.Name, Link.Description) // or RETURNING(Link.AllColumns)
dest := []model.Link{}
err := insertStmt.Query(db, &dest)
```
Use `ExecContext` and `QueryContext` to provide context object to execution.
##### SQL table used for the example:
```sql
CREATE TABLE IF NOT EXISTS link (
id serial PRIMARY KEY,
url VARCHAR (255) NOT NULL,
name VARCHAR (255) NOT NULL,
description VARCHAR (255)
);
```

View file

@ -1,13 +0,0 @@
### 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
```

View file

@ -1,45 +0,0 @@
LOCK statement obtains a table-level lock, waiting if necessary for any conflicting locks to be released.
More about LOCK statement in PostgreSQL: https://www.postgresql.org/docs/11/sql-lock.html
Following clauses are supported:
- IN(mode) - mode specifies which locks this lock conflicts with.
Mode can be:
- jet.LOCK_ACCESS_SHARE
- jet.LOCK_ROW_SHARE
- jet.LOCK_ROW_EXCLUSIVE
- jet.LOCK_SHARE_UPDATE_EXCLUSIVE
- jet.LOCK_SHARE
- jet.LOCK_SHARE_ROW_EXCLUSIVE
- jet.LOCK_EXCLUSIVE
- jet.LOCK_ACCESS_EXCLUSIVE
- NOWAIT() - locked table should not wait for any conflicting locks to be released. If the specified lock(s)
cannot be acquired immediately without waiting, the transaction is aborted.
## Example
```
lockStmt := Address.
LOCK().
IN(jet.LOCK_ACCESS_SHARE).
NOWAIT()
```
Debug SQL of above statement:
```sql
LOCK TABLE dvds.address IN ACCESS SHARE MODE NOWAIT;
```
### Execute statement
To execute update statement and get sql.Result:
```
res, err := lockStmt.Exec(db)
```
Use `ExecContext` to provide context object to execution.

View file

@ -1,141 +0,0 @@
## Model
Model files are simple Go struct types used to combine and store result of SQL queries. They are
autogenerated from database tables and enums.
### Table model files
Following rules are applied to generate model types from database tables:
- for every table there is one Go file generated. File name is in snake case of the table name.
- every model file contains one struct type. Type name is a camel case of table name. Package name
is always `model`.
- for every column of table there is a field in model struct type. Field name is camel case of column name.
See below table for type mapping.
- fields are pointer types, if they relate to column that can be NULL.
- fields corresponds to primary key columns are tagged with `sql:"primary_key"`.
_This tag is used during query execution to group row results into desired arbitrary structure.
See more at [Execution](https://github.com/go-jet/jet/wiki/Execution)_
##### Mappings of database types to Go types
| Database type(postgres) | Go type |
| ----------------------------------------------- | -------------------------------------------------- |
| boolean | bool |
| smallint | int16 |
| integer | int32 |
| bigint | int64 |
| real | float32 |
| numeric, decimal, double precision | float64 |
| date, timestamp, time(with or without timezone) | time.Time |
| bytea | []byte |
| uuid | uuid.UUID |
| enum | enum name |
| text, character, character varying, | |
| and all remaining types | string |
#### Example:
Sql table `address`:
```
CREATE TABLE dvds.address
(
address_id serial NOT NULL DEFAULT,
address character varying(50) NOT NULL,
address2 character varying(50),
district character varying(20) NOT NULL,
city_id smallint NOT NULL,
postal_code character varying(10),
phone character varying(20) NOT NULL,
last_update timestamp without time zone NOT NULL DEFAULT now(),
CONSTRAINT address_pkey PRIMARY KEY (address_id)
)
```
Autogenerated model file `address.go`:
```
package model
import (
"time"
)
type Address struct {
AddressID int32 `sql:"primary_key"`
Address string
Address2 *string
District string
CityID int16
PostalCode *string
Phone string
LastUpdate time.Time
}
```
### Enum model files
Following rules are applied to generate model files from database enums:
- for every enum there is one Go file generated. File name is a snake case of enum name.
- every file contains one renamed string type. Type name is a camel case of enum name.
Package name is always `model`.
Enum type has two helper methods to:
- initialize correctly from database query result
- easily convert enum to string.
- for every enum value there is one constant defined.
Name of the constant is in format `{CamelCase(enum_name)}_{CamelCase(enum_value_name)}`.
#### Example
Enum `mpaa_rating`:
```
CREATE TYPE dvds.mpaa_rating AS ENUM
('G', 'PG', 'PG-13', 'R', 'NC-17');
```
Autogenerated model file `mpaa_rating.go`
```
package model
import "errors"
type MpaaRating string
const (
MpaaRating_G MpaaRating = "G"
MpaaRating_Pg MpaaRating = "PG"
MpaaRating_Pg13 MpaaRating = "PG-13"
MpaaRating_R MpaaRating = "R"
MpaaRating_Nc17 MpaaRating = "NC-17"
)
func (e *MpaaRating) Scan(value interface{}) error {
if v, ok := value.(string); !ok {
return errors.New("jet: Invalid data for MpaaRating enum")
} else {
switch string(v) {
case "G":
*e = MpaaRating_G
case "PG":
*e = MpaaRating_Pg
case "PG-13":
*e = MpaaRating_Pg13
case "R":
*e = MpaaRating_R
case "NC-17":
*e = MpaaRating_Nc17
default:
return errors.New("Inavlid data " + string(v) + "for MpaaRating enum")
}
return nil
}
}
func (e MpaaRating) String() string {
return string(e)
}
```

View file

@ -1,293 +0,0 @@
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
Following clauses are supported:
- SELECT(expressions...) - expressions to form output rows of the SELECT statement.
- DISTINCT() - remove all duplicate rows from result set
- FROM(tableSource...) - specifies one or more source tables for the SELECT.
- WHERE(condition) - only rows for which condition returns true will be selected.
- GROUP BY(groupingElement, ...) - will condense into a single row all selected rows that share the same values for the grouped expressions.
- HAVING(condition) - eliminates group rows that do not satisfy the condition
- ORDER BY(orderBy, ...) - causes the result rows to be sorted according to the specified expression(s)
- LIMIT(count) - specifies the maximum number of rows to return
- OFFSET(start) - specifies the number of rows to skip before starting to return rows
- FOR(lockMode) - how SELECT will lock rows as they are obtained from the table
Lock mode can be:
- jet.UPDATE()
- jet.NO_KEY_UPDATE()
- jet.SHARE()
- jet.KEY_SHARE()
Lock mode can be extended with following clauses:
- NOWAIT()
- SKIP_LOCKED()
- UNION(select) / UNION_ALL(select) - computes the set union of the rows returned by the involved SELECT statements
- INTERSECT(select) / INTERSECT_ALL(select) - computes the set intersection of the rows returned by the involved SELECT statements
- EXCEPT(select) / EXCEPT_ALL(select) - computes the set of rows that are in the result of the left SELECT statement but not in the result of the right one
_This list might be extended with feature Jet releases._
### Examples per clause
##### 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().SKIP_LOCKED())
SQL:
FOR NO KEY UPDATE SKIP LOCKED
```
##### 10. Set clauses (UNION, UNION_ALL, INTERSECT, INTERSECT_ALL, EXCEPT, EXCEPT_ALL)
```
Go:
SELECT(Payment.Amount).FROM(Payment)
UNION_ALL(SELECT(Payment.Amount).FROM(Payment))
Sql:
(
(
SELECT payment.amount AS "payment.amount"
FROM dvds.payment
)
UNION
(
SELECT payment.amount AS "payment.amount"
FROM dvds.payment
)
);
```
### Two forms of select statements in Jet
#### 1. Classical select statement
Columns selected are before table source 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
Table sources 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 source 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)
```

View file

@ -1,117 +0,0 @@
## SQL Builder
SQL Builder files are Go files, containing types necessary to write type safe SQL queries in Go. They are
autogenerated from database tables and enums. File names is always snake case of the table or enum name.
### Table SQL Builder files
Following rules are applied to generate table SQL Builder files:
- for every table there is one Go SQL Builder file generated.
- every file contains one type - struct with nested jet.Table.
- for every column of table there is a field column in SQL Builder table type.
Field name is camel case of column name. See below table for type mapping.
- `AllColumns` is used as shorthand notation for list of all columns.
- `MutableColumns` are all columns minus primary key columns _(Useful in INSERT or UPDATE statements)_.
##### Mappings of database types to sql builder column types:
| Database type(postgres) | Sql builder column type |
| ----------------------------------------------- | -------------------------------------------------- |
| boolean | ColumnBool |
| smallint, integer, bigint | ColumnInteger |
| real, numeric, decimal, double precision | ColumnFloat |
| date | ColumnDate |
| timestamp without time zone | ColumnTimestamp |
| timestamp with time zone | ColumnTimestampz |
| time without time zone | ColumnTime |
| time with time zone | ColumnTimez |
| enums, text, character, character varying | |
| bytea, uuid | |
| and all remaining types | ColumnString |
#### Example
Sql table `address`:
```
CREATE TABLE dvds.address
(
address_id serial NOT NULL DEFAULT,
address character varying(50) NOT NULL,
address2 character varying(50),
district character varying(20) NOT NULL,
city_id smallint NOT NULL,
postal_code character varying(10),
phone character varying(20) NOT NULL,
last_update timestamp without time zone NOT NULL DEFAULT now(),
CONSTRAINT address_pkey PRIMARY KEY (address_id)
)
```
Part of the table sql builder file for table `address`.
```
package table
import (
"github.com/go-jet/jet"
)
var Address = newAddressTable()
type AddressTable struct {
jet.Table
//Columns
AddressID jet.ColumnInteger
Address jet.ColumnString
Address2 jet.ColumnString
District jet.ColumnString
CityID jet.ColumnInteger
PostalCode jet.ColumnString
Phone jet.ColumnString
LastUpdate jet.ColumnTimestamp
AllColumns jet.ColumnList
MutableColumns jet.ColumnList
}
```
### Enum SQL Builder files
Following rules are applied to generate enum SQL Builder files:
- for every enum there is one Go SQL Builder file generated.
- every file contains one type. Type name is a camel case of enum name.
- for every enum value there is a field in SQL Builder enum struct. Field name is camel case of enum value. Type is
jet.StringExpression, meaning it can be used by string expressions methods.
#### Example
Enum `mpaa_rating`:
```
CREATE TYPE dvds.mpaa_rating AS ENUM
('G', 'PG', 'PG-13', 'R', 'NC-17');
```
Enum SQL Builder file for `mpaa_rating`:
```
package enum
import "github.com/go-jet/jet"
var MpaaRating = &struct {
G jet.StringExpression
PG jet.StringExpression
PG13 jet.StringExpression
R jet.StringExpression
NC17 jet.StringExpression
}{
G: jet.NewEnumValue("G"),
PG: jet.NewEnumValue("PG"),
PG13: jet.NewEnumValue("PG-13"),
R: jet.NewEnumValue("R"),
NC17: jet.NewEnumValue("NC-17"),
}
```

View file

@ -1,423 +0,0 @@
## Scan to arbitrary destination
Statements `Query` and `QueryContext` methods perform scan and grouping of row result to arbitrary `destination` structure.
- `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.
### How scan works?
The easiest way to understand how scan works is by an example.
Lets say we want to retrieve list of cities, with list of customers for each city, and address for each customer.
For simplicity we will narrow the choice to 'London' and 'York'.
Go SQL builder select statement:
```
stmt := City.
INNER_JOIN(Address, Address.CityID.EQ(City.CityID)).
INNER_JOIN(Customer, Customer.AddressID.EQ(Address.AddressID)).
SELECT(
City.CityID,
City.City,
Address.AddressID,
Address.Address,
Customer.CustomerID,
Customer.LastName,
).
WHERE(City.City.EQ(String("London")).OR(City.City.EQ(String("York")))).
ORDER_BY(City.CityID, Address.AddressID, Customer.CustomerID)
```
_Note that we are using jet select statement format([TODO]())_
Debug sql of above statement:
```
SELECT city.city_id AS "city.city_id",
city.city AS "city.city",
address.address_id AS "address.address_id",
address.address AS "address.address",
customer.customer_id AS "customer.customer_id",
customer.last_name AS "customer.last_name"
FROM dvds.city
INNER JOIN dvds.address ON (address.city_id = city.city_id)
INNER JOIN dvds.customer ON (customer.address_id = address.address_id)
WHERE (city.city = 'London') OR (city.city = 'York')
ORDER BY city.city_id, address.address_id, customer.customer_id;
```
Note that every column is aliased by default. Format is "`table_name`.`column_name`"
Above statement will produce following result set:
|_row_| city.city_id | city.city | address.address_id | address.address | customer.customer_id | customer.last_name |
|--- | ------------ | ------------- | ------------------- | -------------------- | -------------------- | ------------------ |
| _1_| 312 | "London" | 256 | "1497 Yuzhou Drive" | 252 | "Hoffman"|
| _2_| 312 | "London" | 517 | "548 Uruapan Street" | 512 | "Vines" |
| _3_| 589 | "York" | 502 | "1515 Korla Way" | 497 | "Sledge" |
Lets execute statement and scan result set to destination `dest`:
```
var dest []struct {
model.City
Customers []struct{
model.Customer
Address model.Address
}
}
err := stmt.Query(db, &dest)
```
Note that camel case of result set column names(aliases) is the same as `model type name`.`field name`.
For instance `city.city_id` -> `City.CityID`. This is being used to find appropriate column for each destination model field.
It is not an error if there is not a column for each destination model field. Table and column names does not have
to be in snake case.
`Query` uses reflection to introspect destination type structure, and result set column names(aliases), to find appropriate destination field for result set column.
Every new destination struct object is cached by his and all the parents primary key. So grouping to work correctly at least table primary keys has to appear in query result set. If there is no primary key in a result set
row number is used as grouping condition(which is always unique).
For instance, after row 1 is processed, two objects are stored to cache:
```
Key: Object:
(City(312)) -> (*struct { model.City; Customers []struct { model.Customer; Address model.Address } })
(City(312)),(Customer(252),Address(256)) -> (*struct { model.Customer; Address model.Address })
```
After row 2 processing only one object is stored to cache, because city with city_id 312 is already in cache.
```
Key: Object:
(City(312)) -> pulled from cache
(City(312)),(Customer(512),Address(517)) -> (*struct { model.Customer; Address model.Address })
```
Lets print `dest` as a json, to visualize `Query` result:
```
[
{
"CityID": 312,
"City": "London",
"CountryID": 0,
"LastUpdate": "0001-01-01T00:00:00Z",
"Customers": [
{
"CustomerID": 252,
"StoreID": 0,
"FirstName": "",
"LastName": "Hoffman",
"Email": null,
"AddressID": 0,
"Activebool": false,
"CreateDate": "0001-01-01T00:00:00Z",
"LastUpdate": null,
"Active": null,
"Address": {
"AddressID": 256,
"Address": "1497 Yuzhou Drive",
"Address2": null,
"District": "",
"CityID": 0,
"PostalCode": null,
"Phone": "",
"LastUpdate": "0001-01-01T00:00:00Z"
}
},
{
"CustomerID": 512,
"StoreID": 0,
"FirstName": "",
"LastName": "Vines",
"Email": null,
"AddressID": 0,
"Activebool": false,
"CreateDate": "0001-01-01T00:00:00Z",
"LastUpdate": null,
"Active": null,
"Address": {
"AddressID": 517,
"Address": "548 Uruapan Street",
"Address2": null,
"District": "",
"CityID": 0,
"PostalCode": null,
"Phone": "",
"LastUpdate": "0001-01-01T00:00:00Z"
}
}
]
},
{
"CityID": 589,
"City": "York",
"CountryID": 0,
"LastUpdate": "0001-01-01T00:00:00Z",
"Customers": [
{
"CustomerID": 497,
"StoreID": 0,
"FirstName": "",
"LastName": "Sledge",
"Email": null,
"AddressID": 0,
"Activebool": false,
"CreateDate": "0001-01-01T00:00:00Z",
"LastUpdate": null,
"Active": null,
"Address": {
"AddressID": 502,
"Address": "1515 Korla Way",
"Address2": null,
"District": "",
"CityID": 0,
"PostalCode": null,
"Phone": "",
"LastUpdate": "0001-01-01T00:00:00Z"
}
}
]
}
]
```
All the fields missing source column in result set are initialized with empty value.
City of `London` has two customers, which is the product of object reuse in `ROW 2` processing.
### Custom model files
Destinations are not limited to just model files, any destination will work, as long as camel case of result set column
is equal to `model type name`.`field name`.
Custom model type can have field of any type listed in [Mappings of database types to Go types](),
plus any type that implements `sql.Scanner` interface.
#### Named types
Lets rewrite above example to use custom named model files:
```
type MyAddress struct {
ID int32 `sql:"primary_key"`
AddressLine string
}
type MyCustomer struct {
ID int32 `sql:"primary_key"`
LastName *string
Address MyAddress
}
type MyCity struct {
ID int32 `sql:"primary_key"`
Name string
Customers []MyCustomer
}
dest2 := []MyCity{}
stmt2 := City.
INNER_JOIN(Address, Address.CityID.EQ(City.CityID)).
INNER_JOIN(Customer, Customer.AddressID.EQ(Address.AddressID)).
SELECT(
City.CityID.AS("my_city.id"), //snake case
City.City.AS("myCity.Name"), //camel case
Address.AddressID.AS("My_Address.id"), //mixed case
Address.Address.AS("my address.address line"), //with spaces
Customer.CustomerID.AS("my_customer.id"),
Customer.LastName.AS("my_customer.last_name"),
).
WHERE(City.City.EQ(String("London")).OR(City.City.EQ(String("York")))).
ORDER_BY(City.CityID, Address.AddressID, Customer.CustomerID)
err := stmt2.Query(db, &dest2)
```
Destination type names and field names are now changed. Every type has 'My' prefix, every primary key column is named `ID`,
`LastName` is now string pointer etc.
Because we are using custom types with changed identifier, every column has to be aliased.
For instance: `City.CityID.AS("my_city.id")`, ` City.City.AS("myCity.Name")` etc.
**Table names, column names and aliases doesn't have to be in a snake case. CamelCase, PascalCase or some other mixed space is also supported,
but it is strongly recommended to use snake case for database identifiers.**
Json of new destination is also changed:
```
[
{
"ID": 312,
"Name": "London",
"Customers": [
{
"ID": 252,
"LastName": "Hoffman",
"Address": {
"ID": 256,
"AddressLine": "1497 Yuzhou Drive"
}
},
{
"ID": 512,
"LastName": "Vines",
"Address": {
"ID": 517,
"AddressLine": "548 Uruapan Street"
}
}
]
},
{
"ID": 589,
"Name": "York",
"Customers": [
{
"ID": 497,
"LastName": "Sledge",
"Address": {
"ID": 502,
"AddressLine": "1515 Korla Way"
}
}
]
}
]
```
#### Anonymous custom types
There is no need to create new named type for every custom model.
Destination type can be declared inline without naming any new type.
```
var dest []struct {
CityID int32 `sql:"primary_key"`
CityName string
Customers []struct {
CustomerID int32 `sql:"primary_key"`
LastName string
Address struct {
AddressID int32 `sql:"primary_key"`
AddressLine string
}
}
}
stmt := City.
INNER_JOIN(Address, Address.CityID.EQ(City.CityID)).
INNER_JOIN(Customer, Customer.AddressID.EQ(Address.AddressID)).
SELECT(
City.CityID.AS("city_id"),
City.City.AS("city_name"),
Customer.CustomerID.AS("customer_id"),
Customer.LastName.AS("last_name"),
Address.AddressID.AS("address_id"),
Address.Address.AS("address_line"),
).
WHERE(City.City.EQ(String("London")).OR(City.City.EQ(String("York")))).
ORDER_BY(City.CityID, Address.AddressID, Customer.CustomerID)
err := stmt.Query(db, &dest)
```
Aliasing is now simplified. Alias contains only (column/field) name.
On the other hand, we can not have 3 fields named `ID`, because aliases have to be unique.
### Tagging model files
Desired mapping can be set the other way around as well, by tagging destination fields and types.
```
var dest []struct {
CityID int32 `sql:"primary_key" alias:"city.city_id"`
CityName string `alias:"city.city"`
Customers []struct {
// because the whole struct is refering to 'customer.*' (see below tag),
// we can just use 'alias:"customer_id"`' instead of 'alias:"customer.customer_id"`'
CustomerID int32 `sql:"primary_key" alias:"customer_id"`
LastName *string `alias:"last_name"`
Address struct {
AddressID int32 `sql:"primary_key" alias:"AddressId"` // cammel case for alias will work as well
AddressLine string `alias:"address.address"` // full alias will work as well
} `alias:"address.*"` // struct is now refering to all address.* columns
} `alias:"customer.*"` // struct is now refering to all customer.* columns
}
stmt := City.
INNER_JOIN(Address, Address.CityID.EQ(City.CityID)).
INNER_JOIN(Customer, Customer.AddressID.EQ(Address.AddressID)).
SELECT(
City.CityID,
City.City,
Customer.CustomerID,
Customer.LastName,
Address.AddressID,
Address.Address,
).
WHERE(City.City.EQ(String("London")).OR(City.City.EQ(String("York")))).
ORDER_BY(City.CityID, Address.AddressID, Customer.CustomerID)
err := stmt.Query(db, &dest)
```
This kind of mapping is more complicated than in previous examples, and it should avoided and used
only when there is no alternative. Usually this is the case in two scenarios:
1) Self join
```
var dest []struct{
model.Employee
Manager *model.Employee `alias:"Manager.*"` //or just `alias:"Manager"
}
manager := Employee.AS("Manager")
stmt := Employee.
LEFT_JOIN(manager, Employee.ReportsTo.EQ(manager.EmployeeId)).
SELECT(
Employee.EmployeeId,
Employee.FirstName,
manager.EmployeeId,
manager.FirstName,
)
```
_This example could also be written without tag alias, by just introducing of a new type `type Manager model.Employee`._
2) Slices of go base types (int32, float64, string, ...)
```
var dest struct {
model.Film
InventoryIDs []int32 `alias:"inventory.inventory_id"`
}
```
### Combining autogenerated and custom model files
It is allowed to combine autogenerated and custom model files.
For instance:
```
type MyCustomer struct {
ID int32 `sql:"primary_key"`
LastName string
Address model.Address //model.Address is autogenerated model type
}
type MyCity struct {
ID int32 `sql:"primary_key"`
Name string
Customers []MyCustomer
}
```

View file

@ -1,51 +0,0 @@
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._
### Executing statements
Statements can be executed with following methods:
- `Query(db execution.DB, destination interface{}) error` - executes statements over database connection db and maps
row result result set to destination.
- `QueryContext(db execution.DB, context context.Context, destination interface{}) error` - executes statement with a
context over database connection db and maps row result result set to destination.
- `Exec(db execution.DB) (sql.Result, error)` - executes statement over db connection and returns `sql.Result`.
- `ExecContext(db execution.DB, context context.Context) (sql.Result, error)` - executes statement with context over
db connection and returns `sql.Result`.
Each execution method first creates parametrized sql query with list of arguments and then initiates query on database connection.
Exec and ExecContext are just a wrappers around database `Exec` and `ExecContext`.
`Query` and `QueryContext` are bit more complex, the are wrappers around database `Query` and `QueryContext`,
but they also perform grouping of row result to arbitrary `destination` structure.
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`
### Debugging statements
Statements SQL can be debugged in two ways:
- `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.

View file

@ -1,87 +0,0 @@
UPDATE changes the values of the specified columns in all rows that satisfy the condition.
More about UPDATE statement in PostgreSQL: https://www.postgresql.org/docs/11/sql-update.html
Following clauses are supported:
- UPDATE(columns...) - list of columns to update
- SET(values...) - list of values for columns
- MODEL(model) - list of values for columns will be extracted from model object
- WHERE(condition) - only rows for which condition returns true will be updated.
- RETURNING(output_expression...) - An expressions to be computed and returned by the UPDATE statement after each row is updated.
The expressions can use any column names of the table. Write _TableName_.AllColumns to return all columns.
_This list might be extended with feature Jet releases._
## Example
```
// replace all Bing links with Yahoo
updateStmt := Link.
UPDATE(Link.Name, Link.URL).
SET("Yahoo", "http://yahoo.com").
WHERE(Link.Name.EQ(String("Bing")))
```
Debug sql of above statement:
```sql
UPDATE test_sample.link -- 'test_sample' is name of the schema
SET (name, url) = ('Yahoo', 'http://yahoo.com')
WHERE link.name = 'Bing';
```
Short-hand notation to update values from model data:
```
yahoo := model.Link{
URL: "http://www.yahoo.com",
Name: "Yahoo",
}
updateStmt := Link.
UPDATE(Link.Name, Link.URL, Link.Description).
MODEL(yahoo).
WHERE(Link.Name.EQ(String("Bing")))
```
`Link.Name, Link.URL, Link.Description` - can be replaced with Link.MutableColumns(all columns minus primary key column).
Primary key columns usually are not updated.
```
updateStmt := Link.
UPDATE(Link.MutableColumns).
MODEL(yahoo).
WHERE(Link.Name.EQ(String("Bing")))
```
### Execute statement
To execute update statement and get sql.Result:
```
res, err := updateStmt.Exec(db)
```
To execute update statement and return row records updated, statement has to have RETURNING clause:
```
updateStmt := Link.
UPDATE(Link.MutableColumns).
MODEL(yahoo).
WHERE(Link.Name.EQ(String("Bing"))).
RETURNING(Link.AllColumns)
dest := []model.Link{}
err := updateStmt.Query(db, &dest)
```
Use `ExecContext` and `QueryContext` to provide context object to execution.
##### SQL table used for the example:
```sql
CREATE TABLE IF NOT EXISTS link (
id serial PRIMARY KEY,
url VARCHAR (255) NOT NULL,
name VARCHAR (255) NOT NULL,
description VARCHAR (255)
);
```

View file

@ -1,13 +0,0 @@
* [Installation](https://github.com/go-jet/jet/wiki/Installation)
* [Generator](https://github.com/go-jet/jet/wiki/Generator)
* [Model files](https://github.com/go-jet/jet/wiki/Model-data.md)
* [SQL Builder](https://github.com/go-jet/jet/wiki/SQL-Builder.md)
* [Expressions](https://github.com/go-jet/jet/wiki/Expressions)
* [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)
* [Scan to arbitrary destination](https://github.com/go-jet/jet/wiki/Scan-to-arbitrary-destination.md)