diff --git a/clause.go b/clause.go index 6a3ddc4..ec6b014 100644 --- a/clause.go +++ b/clause.go @@ -124,6 +124,18 @@ func (q *queryData) writeHaving(statement statementType, having Expression) erro return err } +func (q *queryData) writeReturning(statement statementType, returning []projection) error { + if len(returning) == 0 { + return nil + } + + q.newLine() + q.writeString("RETURNING") + q.increaseIdent() + + return q.writeProjections(statement, returning) +} + func (q *queryData) newLine() { q.write([]byte{'\n'}) q.write(bytes.Repeat([]byte{' '}, q.ident)) diff --git a/delete_statement.go b/delete_statement.go index d04dced..882fb15 100644 --- a/delete_statement.go +++ b/delete_statement.go @@ -11,6 +11,8 @@ type DeleteStatement interface { Statement WHERE(expression BoolExpression) DeleteStatement + + RETURNING(projections ...projection) DeleteStatement } func newDeleteStatement(table WritableTable) DeleteStatement { @@ -20,8 +22,9 @@ func newDeleteStatement(table WritableTable) DeleteStatement { } type deleteStatementImpl struct { - table WritableTable - where BoolExpression + table WritableTable + where BoolExpression + returning []projection } func (d *deleteStatementImpl) WHERE(expression BoolExpression) DeleteStatement { @@ -29,6 +32,11 @@ func (d *deleteStatementImpl) WHERE(expression BoolExpression) DeleteStatement { return d } +func (d *deleteStatementImpl) RETURNING(projections ...projection) DeleteStatement { + d.returning = projections + return d +} + func (d *deleteStatementImpl) serializeImpl(out *queryData) error { if d == nil { return errors.New("delete statement is nil") @@ -52,6 +60,10 @@ func (d *deleteStatementImpl) serializeImpl(out *queryData) error { return err } + if err := out.writeReturning(delete_statement, d.returning); err != nil { + return err + } + return nil } diff --git a/delete_statement_test.go b/delete_statement_test.go index 2c37927..c9f724e 100644 --- a/delete_statement_test.go +++ b/delete_statement_test.go @@ -15,3 +15,11 @@ 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)) +} diff --git a/insert_statement.go b/insert_statement.go index c8e73e5..0dc58a6 100644 --- a/insert_statement.go +++ b/insert_statement.go @@ -142,15 +142,8 @@ func (i *insertStatementImpl) Sql() (sql string, args []interface{}, err error) } } - if len(i.returning) > 0 { - queryData.newLine() - queryData.writeString("RETURNING") - - err = queryData.writeProjections(insert_statement, i.returning) - - if err != nil { - return - } + if err = queryData.writeReturning(insert_statement, i.returning); err != nil { + return } sql, args = queryData.finalize() diff --git a/tests/delete_test.go b/tests/delete_test.go new file mode 100644 index 0000000..3cbe1e4 --- /dev/null +++ b/tests/delete_test.go @@ -0,0 +1,62 @@ +package tests + +import ( + . "github.com/go-jet/jet" + "github.com/go-jet/jet/tests/.gentestdata/jetdb/test_sample/model" + . "github.com/go-jet/jet/tests/.gentestdata/jetdb/test_sample/table" + "gotest.tools/assert" + "testing" +) + +func TestDeleteWithWhere(t *testing.T) { + initForDeleteTest(t) + + var expectedSql = ` +DELETE FROM test_sample.link +WHERE link.name IN ('Gmail', 'Outlook'); +` + deleteStmt := Link. + DELETE(). + WHERE(Link.Name.IN(String("Gmail"), String("Outlook"))) + + assertStatementSql(t, deleteStmt, expectedSql, "Gmail", "Outlook") + assertExec(t, deleteStmt, 2) +} + +func TestDeleteWithWhereAndReturning(t *testing.T) { + initForDeleteTest(t) + + var expectedSql = ` +DELETE FROM test_sample.link +WHERE link.name IN ('Gmail', 'Outlook') +RETURNING link.id AS "link.id", + link.url AS "link.url", + link.name AS "link.name", + link.description AS "link.description"; +` + deleteStmt := Link. + DELETE(). + WHERE(Link.Name.IN(String("Gmail"), String("Outlook"))). + RETURNING(Link.AllColumns) + + assertStatementSql(t, deleteStmt, expectedSql, "Gmail", "Outlook") + + dest := []model.Link{} + + err := deleteStmt.Query(db, &dest) + + assert.NilError(t, err) + + assert.Equal(t, len(dest), 2) + assert.DeepEqual(t, dest[0].Name, "Gmail") + assert.DeepEqual(t, dest[1].Name, "Outlook") +} + +func initForDeleteTest(t *testing.T) { + cleanUpLinkTable(t) + stmt := Link.INSERT(Link.URL, Link.Name, Link.Description). + VALUES("www.gmail.com", "Gmail", "Email service developed by Google"). + VALUES("www.outlook.live.com", "Outlook", "Email service developed by Microsoft") + + assertExec(t, stmt, 2) +} diff --git a/tests/insert_test.go b/tests/insert_test.go index 229277c..8a4d539 100644 --- a/tests/insert_test.go +++ b/tests/insert_test.go @@ -17,9 +17,9 @@ INSERT INTO test_sample.link (id, url, name, description) VALUES (101, 'http://www.google.com', 'Google', DEFAULT), (102, 'http://www.yahoo.com', 'Yahoo', NULL) RETURNING link.id AS "link.id", - link.url AS "link.url", - link.name AS "link.name", - link.description AS "link.description"; + link.url AS "link.url", + link.name AS "link.name", + link.description AS "link.description"; ` insertQuery := Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description). @@ -197,9 +197,9 @@ INSERT INTO test_sample.link (url, name) ( WHERE link.id = 0 ) RETURNING link.id AS "link.id", - link.url AS "link.url", - link.name AS "link.name", - link.description AS "link.description"; + link.url AS "link.url", + link.name AS "link.name", + link.description AS "link.description"; ` query := Link. diff --git a/update_statement.go b/update_statement.go index 4b5852d..c804901 100644 --- a/update_statement.go +++ b/update_statement.go @@ -118,19 +118,23 @@ func (u *updateStatementImpl) Sql() (sql string, args []interface{}, err error) return } - if len(u.returning) > 0 { - out.newLine() - out.writeString("RETURNING") - out.increaseIdent() - out.increaseIdent() - - err = serializeProjectionList(update_statement, u.returning, out) - - if err != nil { - return - } + if err = out.writeReturning(update_statement, u.returning); err != nil { + return } + //if len(u.returning) > 0 { + // out.newLine() + // out.writeString("RETURNING") + // out.increaseIdent() + // out.increaseIdent() + // + // err = serializeProjectionList(update_statement, u.returning, out) + // + // if err != nil { + // return + // } + //} + sql, args = out.finalize() return } diff --git a/wiki/DELETE.md b/wiki/DELETE.md new file mode 100644 index 0000000..996cf09 --- /dev/null +++ b/wiki/DELETE.md @@ -0,0 +1,58 @@ +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) +); +``` \ No newline at end of file diff --git a/wiki/INSERT.md b/wiki/INSERT.md index dd51e3d..488a64c 100644 --- a/wiki/INSERT.md +++ b/wiki/INSERT.md @@ -9,7 +9,9 @@ Following clauses are supported: - 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(columns...) - list of columns to return as statement result +- 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._ @@ -124,7 +126,7 @@ err := insertStmt.Query(db, &dest) Use `ExecContext` and `QueryContext` to provide context object to execution. -Insert example SQL table: +##### SQL table used for the example: ```sql CREATE TABLE IF NOT EXISTS link ( id serial PRIMARY KEY, diff --git a/wiki/UPDATE.md b/wiki/UPDATE.md index a8b4942..d7a21a4 100644 --- a/wiki/UPDATE.md +++ b/wiki/UPDATE.md @@ -1,12 +1,13 @@ 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 +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) - row condition to update -- RETURNING(columns...) - list of columns to return as statement result +- 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._ @@ -23,7 +24,7 @@ updateStmt := Link. Debug sql of above statement: ```sql UPDATE test_sample.link -- 'test_sample' is name of the schema -SET (name, url) = ('Bong', 'http://bong.com') +SET (name, url) = ('Yahoo', 'http://yahoo.com') WHERE link.name = 'Bing'; ``` @@ -41,8 +42,8 @@ updateStmt := Link. WHERE(Link.Name.EQ(String("Bing"))) ``` -`Link.Name, Link.URL, Link.Description` - can be replaced with Link.MutableColumns. All columns minus primary key columns. -Primary key columns are not updated usually. +`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. @@ -75,7 +76,7 @@ err := updateStmt.Query(db, &dest) Use `ExecContext` and `QueryContext` to provide context object to execution. -Update example SQL table: +##### SQL table used for the example: ```sql CREATE TABLE IF NOT EXISTS link ( id serial PRIMARY KEY,