diff --git a/mysql/delete_statement.go b/mysql/delete_statement.go index 7f0fe6f..0d39cde 100644 --- a/mysql/delete_statement.go +++ b/mysql/delete_statement.go @@ -6,6 +6,7 @@ import "github.com/go-jet/jet/v2/internal/jet" type DeleteStatement interface { Statement + USING(tables ...ReadableTable) DeleteStatement WHERE(expression BoolExpression) DeleteStatement ORDER_BY(orderByClauses ...OrderByClause) DeleteStatement LIMIT(limit int64) DeleteStatement @@ -15,6 +16,7 @@ type deleteStatementImpl struct { jet.SerializerStatement Delete jet.ClauseStatementBegin + Using jet.ClauseFrom Where jet.ClauseWhere OrderBy jet.ClauseOrderBy Limit jet.ClauseLimit @@ -22,10 +24,15 @@ type deleteStatementImpl struct { func newDeleteStatement(table Table) DeleteStatement { newDelete := &deleteStatementImpl{} - newDelete.SerializerStatement = jet.NewStatementImpl(Dialect, jet.DeleteStatementType, newDelete, &newDelete.Delete, - &newDelete.Where, &newDelete.OrderBy, &newDelete.Limit) + newDelete.SerializerStatement = jet.NewStatementImpl(Dialect, jet.DeleteStatementType, newDelete, + &newDelete.Delete, + &newDelete.Using, + &newDelete.Where, + &newDelete.OrderBy, + &newDelete.Limit) newDelete.Delete.Name = "DELETE FROM" + newDelete.Using.Name = "USING" newDelete.Delete.Tables = append(newDelete.Delete.Tables, table) newDelete.Where.Mandatory = true newDelete.Limit.Count = -1 @@ -33,6 +40,11 @@ func newDeleteStatement(table Table) DeleteStatement { return newDelete } +func (d *deleteStatementImpl) USING(tables ...ReadableTable) DeleteStatement { + d.Using.Tables = readableTablesToSerializerList(tables) + return d +} + func (d *deleteStatementImpl) WHERE(expression BoolExpression) DeleteStatement { d.Where.Condition = expression return d diff --git a/mysql/select_statement.go b/mysql/select_statement.go index ffb8054..1c3a88a 100644 --- a/mysql/select_statement.go +++ b/mysql/select_statement.go @@ -58,7 +58,7 @@ type SelectStatement interface { AsTable(alias string) SelectTable } -//SELECT creates new SelectStatement with list of projections +// SELECT creates new SelectStatement with list of projections func SELECT(projection Projection, projections ...Projection) SelectStatement { return newSelectStatement(nil, append([]Projection{projection}, projections...)) } @@ -106,10 +106,7 @@ func (s *selectStatementImpl) DISTINCT() SelectStatement { } func (s *selectStatementImpl) FROM(tables ...ReadableTable) SelectStatement { - s.From.Tables = nil - for _, table := range tables { - s.From.Tables = append(s.From.Tables, table) - } + s.From.Tables = readableTablesToSerializerList(tables) return s } @@ -189,3 +186,11 @@ func toJetFrameOffset(offset interface{}) jet.Serializer { return jet.FixedLiteral(offset) } + +func readableTablesToSerializerList(tables []ReadableTable) []jet.Serializer { + var ret []jet.Serializer + for _, table := range tables { + ret = append(ret, table) + } + return ret +} diff --git a/postgres/delete_statement.go b/postgres/delete_statement.go index 2bfbd8c..e4ecc49 100644 --- a/postgres/delete_statement.go +++ b/postgres/delete_statement.go @@ -6,8 +6,8 @@ import "github.com/go-jet/jet/v2/internal/jet" type DeleteStatement interface { jet.SerializerStatement + USING(tables ...ReadableTable) DeleteStatement WHERE(expression BoolExpression) DeleteStatement - RETURNING(projections ...jet.Projection) DeleteStatement } @@ -15,22 +15,32 @@ type deleteStatementImpl struct { jet.SerializerStatement Delete jet.ClauseStatementBegin + Using jet.ClauseFrom Where jet.ClauseWhere Returning jet.ClauseReturning } func newDeleteStatement(table WritableTable) DeleteStatement { newDelete := &deleteStatementImpl{} - newDelete.SerializerStatement = jet.NewStatementImpl(Dialect, jet.DeleteStatementType, newDelete, &newDelete.Delete, - &newDelete.Where, &newDelete.Returning) + newDelete.SerializerStatement = jet.NewStatementImpl(Dialect, jet.DeleteStatementType, newDelete, + &newDelete.Delete, + &newDelete.Using, + &newDelete.Where, + &newDelete.Returning) newDelete.Delete.Name = "DELETE FROM" newDelete.Delete.Tables = append(newDelete.Delete.Tables, table) + newDelete.Using.Name = "USING" newDelete.Where.Mandatory = true return newDelete } +func (d *deleteStatementImpl) USING(tables ...ReadableTable) DeleteStatement { + d.Using.Tables = readableTablesToSerializerList(tables) + return d +} + func (d *deleteStatementImpl) WHERE(expression BoolExpression) DeleteStatement { d.Where.Condition = expression return d diff --git a/sqlite/delete_statement.go b/sqlite/delete_statement.go index dee85c0..e9c0610 100644 --- a/sqlite/delete_statement.go +++ b/sqlite/delete_statement.go @@ -9,7 +9,7 @@ type DeleteStatement interface { WHERE(expression BoolExpression) DeleteStatement ORDER_BY(orderByClauses ...OrderByClause) DeleteStatement LIMIT(limit int64) DeleteStatement - RETURNING(projections ...jet.Projection) DeleteStatement + RETURNING(projections ...Projection) DeleteStatement } type deleteStatementImpl struct { diff --git a/tests/mysql/delete_test.go b/tests/mysql/delete_test.go index cb7673c..a2a8e0b 100644 --- a/tests/mysql/delete_test.go +++ b/tests/mysql/delete_test.go @@ -4,6 +4,7 @@ import ( "context" "github.com/go-jet/jet/v2/internal/testutils" . "github.com/go-jet/jet/v2/mysql" + "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table" "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/model" . "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/table" "github.com/stretchr/testify/require" @@ -91,3 +92,29 @@ func initForDeleteTest(t *testing.T) { testutils.AssertExec(t, stmt, db, 2) } + +func TestDeleteWithUsing(t *testing.T) { + tx := beginTx(t) + defer tx.Rollback() + + stmt := table.Rental.DELETE(). + USING( + table.Rental. + INNER_JOIN(table.Staff, table.Rental.StaffID.EQ(table.Staff.StaffID)), + table.Actor, + ).WHERE( + table.Staff.StaffID.EQ(Int(2)). + AND(table.Rental.RentalID.LT(Int(10))), + ) + + testutils.AssertStatementSql(t, stmt, ` +DELETE FROM dvds.rental +USING dvds.rental + INNER JOIN dvds.staff ON (rental.staff_id = staff.staff_id), + dvds.actor +WHERE (staff.staff_id = ?) AND (rental.rental_id < ?); +`) + + _, err := stmt.Exec(tx) + require.NoError(t, err) +} diff --git a/tests/postgres/delete_test.go b/tests/postgres/delete_test.go index 5115a3f..c1b9c6a 100644 --- a/tests/postgres/delete_test.go +++ b/tests/postgres/delete_test.go @@ -4,6 +4,8 @@ import ( "context" "github.com/go-jet/jet/v2/internal/testutils" . "github.com/go-jet/jet/v2/postgres" + model2 "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model" + "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/table" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/model" . "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/table" "github.com/stretchr/testify/require" @@ -103,3 +105,70 @@ func TestDeleteExecContext(t *testing.T) { require.Error(t, err, "context deadline exceeded") requireLogged(t, deleteStmt) } + +func TestDeleteFrom(t *testing.T) { + tx := beginTx(t) + defer tx.Rollback() + + stmt := table.Rental.DELETE(). + USING( + table.Staff. + INNER_JOIN(table.Store, table.Store.StoreID.EQ(table.Staff.StaffID)), + table.Actor, + ).WHERE( + table.Staff.StaffID.EQ(table.Rental.StaffID). + AND(table.Staff.StaffID.EQ(Int(2))). + AND(table.Rental.RentalID.LT(Int(10))), + ).RETURNING( + table.Rental.AllColumns, + table.Store.AllColumns, + ) + + testutils.AssertStatementSql(t, stmt, ` +DELETE FROM dvds.rental +USING dvds.staff + INNER JOIN dvds.store ON (store.store_id = staff.staff_id), + dvds.actor +WHERE ((staff.staff_id = rental.staff_id) AND (staff.staff_id = $1)) AND (rental.rental_id < $2) +RETURNING rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update", + store.store_id AS "store.store_id", + store.manager_staff_id AS "store.manager_staff_id", + store.address_id AS "store.address_id", + store.last_update AS "store.last_update"; +`) + + var dest []struct { + Rental model2.Rental + Store model2.Store + } + + err := stmt.Query(tx, &dest) + + require.NoError(t, err) + require.Len(t, dest, 3) + testutils.AssertJSON(t, dest[0], ` +{ + "Rental": { + "RentalID": 4, + "RentalDate": "2005-05-24T23:04:41Z", + "InventoryID": 2452, + "CustomerID": 333, + "ReturnDate": "2005-06-03T01:43:41Z", + "StaffID": 2, + "LastUpdate": "2006-02-16T02:30:53Z" + }, + "Store": { + "StoreID": 2, + "ManagerStaffID": 2, + "AddressID": 2, + "LastUpdate": "2006-02-15T09:57:12Z" + } +} +`) +}