diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go index 4a7f07cd07..199dc8ecfd 100644 --- a/internal/compiler/output_columns.go +++ b/internal/compiler/output_columns.go @@ -404,9 +404,7 @@ func sourceTables(qc *QueryCatalog, node ast.Node) ([]*Table, error) { var list *ast.List switch n := node.(type) { case *ast.DeleteStmt: - list = &ast.List{ - Items: []ast.Node{n.Relation}, - } + list = n.Relations case *ast.InsertStmt: list = &ast.List{ Items: []ast.Node{n.Relation}, diff --git a/internal/endtoend/testdata/delete_inner_join/mysql/go/db.go b/internal/endtoend/testdata/delete_inner_join/mysql/go/db.go new file mode 100644 index 0000000000..02974bda59 --- /dev/null +++ b/internal/endtoend/testdata/delete_inner_join/mysql/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.17.2 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/delete_inner_join/mysql/go/models.go b/internal/endtoend/testdata/delete_inner_join/mysql/go/models.go new file mode 100644 index 0000000000..6faf5b02e1 --- /dev/null +++ b/internal/endtoend/testdata/delete_inner_join/mysql/go/models.go @@ -0,0 +1,22 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.17.2 + +package querytest + +import () + +type Author struct { + ID int32 + Name string +} + +type AuthorBook struct { + AuthorID int32 + BookID int32 +} + +type Book struct { + ID int32 + Title string +} diff --git a/internal/endtoend/testdata/delete_inner_join/mysql/go/query.sql.go b/internal/endtoend/testdata/delete_inner_join/mysql/go/query.sql.go new file mode 100644 index 0000000000..04dfa99e02 --- /dev/null +++ b/internal/endtoend/testdata/delete_inner_join/mysql/go/query.sql.go @@ -0,0 +1,24 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.17.2 +// source: query.sql + +package querytest + +import ( + "context" +) + +const removeAllAuthorsFromTheGreatGatsby = `-- name: RemoveAllAuthorsFromTheGreatGatsby :exec +DELETE author_book +FROM + author_book + INNER JOIN book ON book.id = author_book.book_id +WHERE + book.title = 'The Great Gatsby' +` + +func (q *Queries) RemoveAllAuthorsFromTheGreatGatsby(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, removeAllAuthorsFromTheGreatGatsby) + return err +} diff --git a/internal/endtoend/testdata/delete_inner_join/mysql/query.sql b/internal/endtoend/testdata/delete_inner_join/mysql/query.sql new file mode 100644 index 0000000000..833f952149 --- /dev/null +++ b/internal/endtoend/testdata/delete_inner_join/mysql/query.sql @@ -0,0 +1,25 @@ +CREATE TABLE author ( + id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL +); + +CREATE TABLE book ( + id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL +); + +CREATE TABLE author_book ( + author_id INT UNSIGNED NOT NULL, + book_id INT UNSIGNED NOT NULL, + CONSTRAINT `pk-author_book` PRIMARY KEY (author_id, book_id), + CONSTRAINT `fk-author_book-author-id` FOREIGN KEY (author_id) REFERENCES author (id), + CONSTRAINT `fk-author_book-book-id` FOREIGN KEY (book_id) REFERENCES book (id) +); + +/* name: RemoveAllAuthorsFromTheGreatGatsby :exec */ +DELETE author_book +FROM + author_book + INNER JOIN book ON book.id = author_book.book_id +WHERE + book.title = 'The Great Gatsby'; \ No newline at end of file diff --git a/internal/endtoend/testdata/delete_inner_join/mysql/sqlc.json b/internal/endtoend/testdata/delete_inner_join/mysql/sqlc.json new file mode 100644 index 0000000000..0657f4db83 --- /dev/null +++ b/internal/endtoend/testdata/delete_inner_join/mysql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "engine": "mysql", + "path": "go", + "name": "querytest", + "schema": "query.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/delete_join/mysql/db/db.go b/internal/endtoend/testdata/delete_join/mysql/db/db.go new file mode 100644 index 0000000000..35e5f4a4b6 --- /dev/null +++ b/internal/endtoend/testdata/delete_join/mysql/db/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.17.2 + +package db + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/delete_join/mysql/db/models.go b/internal/endtoend/testdata/delete_join/mysql/db/models.go new file mode 100644 index 0000000000..7f3e844b85 --- /dev/null +++ b/internal/endtoend/testdata/delete_join/mysql/db/models.go @@ -0,0 +1,19 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.17.2 + +package db + +import () + +type JoinTable struct { + ID int64 + PrimaryTableID int64 + OtherTableID int64 + IsActive bool +} + +type PrimaryTable struct { + ID int64 + UserID int64 +} diff --git a/internal/endtoend/testdata/delete_join/mysql/db/query.sql.go b/internal/endtoend/testdata/delete_join/mysql/db/query.sql.go new file mode 100644 index 0000000000..5af292a23c --- /dev/null +++ b/internal/endtoend/testdata/delete_join/mysql/db/query.sql.go @@ -0,0 +1,73 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.17.2 +// source: query.sql + +package db + +import ( + "context" +) + +const deleteJoin = `-- name: DeleteJoin :exec +DELETE jt.*, +pt.* +FROM + join_table as jt + JOIN primary_table as pt ON jt.primary_table_id = pt.id +WHERE + jt.id = ? + AND pt.user_id = ? +` + +type DeleteJoinParams struct { + ID int64 + UserID int64 +} + +func (q *Queries) DeleteJoin(ctx context.Context, arg DeleteJoinParams) error { + _, err := q.db.ExecContext(ctx, deleteJoin, arg.ID, arg.UserID) + return err +} + +const deleteLeftJoin = `-- name: DeleteLeftJoin :exec +DELETE jt.*, +pt.* +FROM + join_table as jt + LEFT JOIN primary_table as pt ON jt.primary_table_id = pt.id +WHERE + jt.id = ? + AND pt.user_id = ? +` + +type DeleteLeftJoinParams struct { + ID int64 + UserID int64 +} + +func (q *Queries) DeleteLeftJoin(ctx context.Context, arg DeleteLeftJoinParams) error { + _, err := q.db.ExecContext(ctx, deleteLeftJoin, arg.ID, arg.UserID) + return err +} + +const deleteRightJoin = `-- name: DeleteRightJoin :exec +DELETE jt.*, +pt.* +FROM + join_table as jt + RIGHT JOIN primary_table as pt ON jt.primary_table_id = pt.id +WHERE + jt.id = ? + AND pt.user_id = ? +` + +type DeleteRightJoinParams struct { + ID int64 + UserID int64 +} + +func (q *Queries) DeleteRightJoin(ctx context.Context, arg DeleteRightJoinParams) error { + _, err := q.db.ExecContext(ctx, deleteRightJoin, arg.ID, arg.UserID) + return err +} diff --git a/internal/endtoend/testdata/delete_join/mysql/query.sql b/internal/endtoend/testdata/delete_join/mysql/query.sql new file mode 100644 index 0000000000..863a6769bd --- /dev/null +++ b/internal/endtoend/testdata/delete_join/mysql/query.sql @@ -0,0 +1,43 @@ +CREATE TABLE primary_table ( + id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + user_id bigint(20) unsigned NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE join_table ( + id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + primary_table_id bigint(20) unsigned NOT NULL, + other_table_id bigint(20) unsigned NOT NULL, + is_active tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (id) +); + +-- name: DeleteJoin :exec +DELETE jt.*, +pt.* +FROM + join_table as jt + JOIN primary_table as pt ON jt.primary_table_id = pt.id +WHERE + jt.id = ? + AND pt.user_id = ?; + +-- name: DeleteLeftJoin :exec +DELETE jt.*, +pt.* +FROM + join_table as jt + LEFT JOIN primary_table as pt ON jt.primary_table_id = pt.id +WHERE + jt.id = ? + AND pt.user_id = ?; + +-- name: DeleteRightJoin :exec +DELETE jt.*, +pt.* +FROM + join_table as jt + RIGHT JOIN primary_table as pt ON jt.primary_table_id = pt.id +WHERE + jt.id = ? + AND pt.user_id = ?; \ No newline at end of file diff --git a/internal/endtoend/testdata/delete_join/mysql/sqlc.json b/internal/endtoend/testdata/delete_join/mysql/sqlc.json new file mode 100644 index 0000000000..b63437627d --- /dev/null +++ b/internal/endtoend/testdata/delete_join/mysql/sqlc.json @@ -0,0 +1,11 @@ +{ + "version": "1", + "packages": [ + { + "path": "db", + "engine": "mysql", + "schema": "query.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/engine/dolphin/convert.go b/internal/engine/dolphin/convert.go index e38aefe4a5..48a2714a5b 100644 --- a/internal/engine/dolphin/convert.go +++ b/internal/engine/dolphin/convert.go @@ -318,14 +318,11 @@ func (c *cc) convertDeleteStmt(n *pcast.DeleteStmt) *ast.DeleteStmt { if len(rels.Items) != 1 { panic("expected one range var") } - rel := rels.Items[0] - rangeVar, ok := rel.(*ast.RangeVar) - if !ok { - panic("expected range var") - } + relations := &ast.List{} + convertToRangeVarList(rels, relations) return &ast.DeleteStmt{ - Relation: rangeVar, + Relations: relations, WhereClause: c.convert(n.Where), ReturningList: &ast.List{}, WithClause: c.convertWithClause(n.With), @@ -1140,22 +1137,24 @@ func (c *cc) convertSetOprType(n *pcast.SetOprType) (op ast.SetOperation, all bo // into a tree. It is called for UNION, INTERSECT or EXCLUDE operation. // // Given an union with the following nodes: -// [Select{1}, Select{2}, Select{3}, Select{4}] +// +// [Select{1}, Select{2}, Select{3}, Select{4}] // // The function will return: -// Select{ -// Larg: Select{ -// Larg: Select{ -// Larg: Select{1}, -// Rarg: Select{2}, -// Op: Union -// }, -// Rarg: Select{3}, -// Op: Union, -// }, -// Rarg: Select{4}, -// Op: Union, -// } +// +// Select{ +// Larg: Select{ +// Larg: Select{ +// Larg: Select{1}, +// Rarg: Select{2}, +// Op: Union +// }, +// Rarg: Select{3}, +// Op: Union, +// }, +// Rarg: Select{4}, +// Op: Union, +// } func (c *cc) convertSetOprSelectList(n *pcast.SetOprSelectList) ast.Node { selectStmts := make([]*ast.SelectStmt, len(n.Selects)) for i, node := range n.Selects { diff --git a/internal/engine/postgresql/convert.go b/internal/engine/postgresql/convert.go index 617454875c..dd5b52066c 100644 --- a/internal/engine/postgresql/convert.go +++ b/internal/engine/postgresql/convert.go @@ -1435,7 +1435,9 @@ func convertDeleteStmt(n *pg.DeleteStmt) *ast.DeleteStmt { return nil } return &ast.DeleteStmt{ - Relation: convertRangeVar(n.Relation), + Relations: &ast.List{ + Items: []ast.Node{convertRangeVar(n.Relation)}, + }, UsingClause: convertSlice(n.UsingClause), WhereClause: convertNode(n.WhereClause), ReturningList: convertSlice(n.ReturningList), diff --git a/internal/engine/sqlite/convert.go b/internal/engine/sqlite/convert.go index c74ce41056..eac9eb3236 100644 --- a/internal/engine/sqlite/convert.go +++ b/internal/engine/sqlite/convert.go @@ -158,8 +158,12 @@ func (c *cc) convertDelete_stmtContext(n *parser.Delete_stmtContext) ast.Node { relation.Alias = &ast.Alias{Aliasname: &alias} } + relations := &ast.List{} + + relations.Items = append(relations.Items, relation) + delete := &ast.DeleteStmt{ - Relation: relation, + Relations: relations, ReturningList: c.convertReturning_caluseContext(n.Returning_clause()), WithClause: nil, } diff --git a/internal/sql/ast/delete_stmt.go b/internal/sql/ast/delete_stmt.go index 3e28add176..36403f134b 100644 --- a/internal/sql/ast/delete_stmt.go +++ b/internal/sql/ast/delete_stmt.go @@ -1,7 +1,7 @@ package ast type DeleteStmt struct { - Relation *RangeVar + Relations *List UsingClause *List WhereClause Node ReturningList *List diff --git a/internal/sql/astutils/rewrite.go b/internal/sql/astutils/rewrite.go index c4fea7da18..9a70eb46db 100644 --- a/internal/sql/astutils/rewrite.go +++ b/internal/sql/astutils/rewrite.go @@ -39,7 +39,6 @@ type ApplyFunc func(*Cursor) bool // Children are traversed in the order in which they appear in the // respective node's struct definition. A package's files are // traversed in the filenames' alphabetical order. -// func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) { parent := &struct{ ast.Node }{root} defer func() { @@ -63,8 +62,8 @@ var abort = new(int) // singleton, to signal termination of Apply // c.Parent(), and f is the field identifier with name c.Name(), // the following invariants hold: // -// p.f == c.Node() if c.Index() < 0 -// p.f[c.Index()] == c.Node() if c.Index() >= 0 +// p.f == c.Node() if c.Index() < 0 +// p.f[c.Index()] == c.Node() if c.Index() >= 0 // // The methods Replace, Delete, InsertBefore, and InsertAfter // can be used to change the AST without disrupting Apply. @@ -677,7 +676,7 @@ func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast. a.apply(n, "Definition", nil, n.Definition) case *ast.DeleteStmt: - a.apply(n, "Relation", nil, n.Relation) + a.apply(n, "Relations", nil, n.Relations) a.apply(n, "UsingClause", nil, n.UsingClause) a.apply(n, "WhereClause", nil, n.WhereClause) a.apply(n, "ReturningList", nil, n.ReturningList) diff --git a/internal/sql/astutils/walk.go b/internal/sql/astutils/walk.go index e38bbdfb3d..8628b776fc 100644 --- a/internal/sql/astutils/walk.go +++ b/internal/sql/astutils/walk.go @@ -1054,8 +1054,8 @@ func Walk(f Visitor, node ast.Node) { } case *ast.DeleteStmt: - if n.Relation != nil { - Walk(f, n.Relation) + if n.Relations != nil { + Walk(f, n.Relations) } if n.UsingClause != nil { Walk(f, n.UsingClause)