diff --git a/internal/engine/dolphin/convert.go b/internal/engine/dolphin/convert.go index cfd83b5c4d..8638a2da00 100644 --- a/internal/engine/dolphin/convert.go +++ b/internal/engine/dolphin/convert.go @@ -397,13 +397,22 @@ func (c *cc) convertRenameTableStmt(n *pcast.RenameTableStmt) ast.Node { return list } -func (c *cc) convertExistsSubqueryExpr(n *pcast.ExistsSubqueryExpr) *ast.SubLink { +func (c *cc) convertExistsSubqueryExpr(n *pcast.ExistsSubqueryExpr) ast.Node { sublink := &ast.SubLink{ SubLinkType: ast.EXISTS_SUBLINK, } if n.Sel != nil { sublink.Subselect = c.convert(n.Sel) } + if n.Not { + return &ast.BoolExpr{ + Boolop: ast.BoolExprTypeNot, + Args: &ast.List{ + Items: []ast.Node{sublink}, + }, + Location: n.OriginTextPosition(), + } + } return sublink } @@ -1556,6 +1565,15 @@ func (c *cc) convertTruncateTableStmt(n *pcast.TruncateTableStmt) *ast.TruncateS } func (c *cc) convertUnaryOperationExpr(n *pcast.UnaryOperationExpr) ast.Node { + if n.Op == opcode.Not { + return &ast.BoolExpr{ + Boolop: ast.BoolExprTypeNot, + Args: &ast.List{ + Items: []ast.Node{c.convert(n.V)}, + }, + Location: n.OriginTextPosition(), + } + } return todo(n) } diff --git a/internal/engine/dolphin/convert_test.go b/internal/engine/dolphin/convert_test.go new file mode 100644 index 0000000000..e1561f5628 --- /dev/null +++ b/internal/engine/dolphin/convert_test.go @@ -0,0 +1,60 @@ +package dolphin + +import ( + "strings" + "testing" + + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +func TestConvertNotExistsSubquery(t *testing.T) { + const query = `UPDATE target_table +SET active = 0 +WHERE NOT ( + EXISTS ( + SELECT 1 + FROM source_table s + WHERE s.updated_at >= sqlc.arg(updated_after) + ) +)` + + parser := NewParser() + stmts, err := parser.Parse(strings.NewReader(query)) + if err != nil { + t.Fatal(err) + } + if len(stmts) != 1 { + t.Fatalf("expected one statement, got %d", len(stmts)) + } + + formatted := ast.Format(stmts[0].Raw, parser) + if !strings.Contains(formatted, "NOT EXISTS") { + t.Errorf("expected NOT EXISTS to be preserved, got:\n%s", formatted) + } + if !strings.Contains(formatted, "sqlc.arg(updated_after)") { + t.Errorf("expected named parameter to be preserved, got:\n%s", formatted) + } +} + +func TestConvertNotExistsSubqueryWithoutParentheses(t *testing.T) { + const query = `SELECT 1 +WHERE NOT EXISTS ( + SELECT 1 + FROM source_table s + WHERE s.updated_at >= sqlc.arg(updated_after) +)` + + parser := NewParser() + stmts, err := parser.Parse(strings.NewReader(query)) + if err != nil { + t.Fatal(err) + } + if len(stmts) != 1 { + t.Fatalf("expected one statement, got %d", len(stmts)) + } + + formatted := ast.Format(stmts[0].Raw, parser) + if !strings.Contains(formatted, "NOT EXISTS") { + t.Errorf("expected NOT EXISTS to be preserved, got:\n%s", formatted) + } +}