Skip to content

Commit

Permalink
Implement THEN RETURN clause in DML statements (#214)
Browse files Browse the repository at this point in the history
* Implement THEN RETURN

* Update testdata

* Fix Update.SQL()

* Fix Update.SQL()

* Fix needless sqlOpt
  • Loading branch information
apstndb authored Dec 3, 2024
1 parent 82ce77a commit 66dfc61
Show file tree
Hide file tree
Showing 37 changed files with 638 additions and 54 deletions.
58 changes: 45 additions & 13 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -3165,20 +3165,48 @@ type AlterSearchIndex struct {
//
// ================================================================================

// WithAction is WITH ACTION clause in ThenReturn.
//
// WITH ACTION {{.Alias | sqlOpt}}
type WithAction struct {
// pos = With
// end = Alias.end || Action + 6

With token.Pos // position of "WITH" keyword
Action token.Pos // position of "ACTION" keyword

Alias *AsAlias // optional
}

// ThenReturn is THEN RETURN clause in DML.
//
// THEN RETURN {{.WithAction | sqlOpt}} {{.Items | sqlJoin ", "}}
type ThenReturn struct {
// pos = Then
// end = Items[$].end

Then token.Pos // position of "THEN" keyword

WithAction *WithAction // optional
Items []SelectItem
}

// Insert is INSERT statement node.
//
// INSERT {{if .InsertOrType}}OR .InsertOrType{{end}}INTO {{.TableName | sql}} ({{.Columns | sqlJoin ","}}) {{.Input | sql}}
// {{.ThenReturn | sqlOpt}}
type Insert struct {
// pos = Insert
// end = Input.end
// end = (ThenReturn ?? Input).end

Insert token.Pos // position of "INSERT" keyword

InsertOrType InsertOrType

TableName *Ident
Columns []*Ident
Input InsertInput
TableName *Ident
Columns []*Ident
Input InsertInput
ThenReturn *ThenReturn // optional
}

// ValuesInput is VALUES clause in INSERT.
Expand Down Expand Up @@ -3231,31 +3259,35 @@ type SubQueryInput struct {
// Delete is DELETE statement.
//
// DELETE FROM {{.TableName | sql}} {{.As | sqlOpt}} {{.Where | sql}}
// {{.ThenReturn | sqlOpt}}
type Delete struct {
// pos = Delete
// end = Where.end
// end = (ThenReturn ?? Where).end

Delete token.Pos // position of "DELETE" keyword

TableName *Ident
As *AsAlias // optional
Where *Where
TableName *Ident
As *AsAlias // optional
Where *Where
ThenReturn *ThenReturn // optional
}

// Update is UPDATE statement.
//
// UPDATE {{.TableName | sql}} {{.As | sqlOpt}}
// SET {{.Updates | sqlJoin ","}} {{.Where | sql}}
// {{.ThenReturn | sqlOpt}}
type Update struct {
// pos = Update
// end = Where.end
// end = (ThenReturn ?? Where).end

Update token.Pos // position of "UPDATE" keyword

TableName *Ident
As *AsAlias // optional
Updates []*UpdateItem // len(Updates) > 0
Where *Where
TableName *Ident
As *AsAlias // optional
Updates []*UpdateItem // len(Updates) > 0
Where *Where
ThenReturn *ThenReturn // optional
}

// UpdateItem is SET clause items in UPDATE.
Expand Down
22 changes: 19 additions & 3 deletions ast/pos.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 26 additions & 29 deletions ast/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -1385,20 +1385,22 @@ func (a *AlterSearchIndex) SQL() string {
//
// ================================================================================

func (w *WithAction) SQL() string {
return "WITH ACTION" + sqlOpt(" ", w.Alias, "")
}

func (t *ThenReturn) SQL() string {
return "THEN RETURN " + sqlOpt("", t.WithAction, " ") + sqlJoin(t.Items, ", ")
}

func (i *Insert) SQL() string {
sql := "INSERT "
if i.InsertOrType != "" {
sql += "OR " + string(i.InsertOrType) + " "
}
sql += "INTO " + i.TableName.SQL() + " ("
for i, c := range i.Columns {
if i != 0 {
sql += ", "
}
sql += c.SQL()
}
sql += ") " + i.Input.SQL()
return sql
return "INSERT " +
strOpt(i.InsertOrType != "", "OR "+string(i.InsertOrType)+" ") +
"INTO " + i.TableName.SQL() + " (" +
sqlJoin(i.Columns, ", ") +
") " +
i.Input.SQL() +
sqlOpt(" ", i.ThenReturn, "")
}

func (v *ValuesInput) SQL() string {
Expand Down Expand Up @@ -1436,25 +1438,20 @@ func (s *SubQueryInput) SQL() string {
}

func (d *Delete) SQL() string {
sql := "DELETE FROM " + d.TableName.SQL()
if d.As != nil {
sql += " " + d.As.SQL()
}
sql += " " + d.Where.SQL()
return sql
return "DELETE FROM " +
d.TableName.SQL() + " " +
sqlOpt("", d.As, " ") +
d.Where.SQL() +
sqlOpt(" ", d.ThenReturn, "")
}

func (u *Update) SQL() string {
sql := "UPDATE " + u.TableName.SQL()
if u.As != nil {
sql += " " + u.As.SQL()
}
sql += " SET " + u.Updates[0].SQL()
for _, item := range u.Updates[1:] {
sql += ", " + item.SQL()
}
sql += " " + u.Where.SQL()
return sql
return "UPDATE " + u.TableName.SQL() + " " +
sqlOpt("", u.As, " ") +
"SET " +
sqlJoin(u.Updates, ", ") +
" " + u.Where.SQL() +
sqlOpt(" ", u.ThenReturn, "")
}

func (u *UpdateItem) SQL() string {
Expand Down
58 changes: 49 additions & 9 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -4124,6 +4124,39 @@ func (p *Parser) parseDML() ast.DML {
panic(p.errorfAtToken(id, "expect pseudo keyword: INSERT, DELETE, UPDATE but: %s", id.AsString))
}

func (p *Parser) tryParseWithAction() *ast.WithAction {
if p.Token.Kind != "WITH" {
return nil
}

with := p.expect("WITH").Pos
action := p.expectKeywordLike("ACTION").Pos
alias := p.tryParseAsAlias(withRequiredAs)

return &ast.WithAction{
With: with,
Action: action,
Alias: alias,
}
}

func (p *Parser) tryParseThenReturn() *ast.ThenReturn {
if p.Token.Kind != "THEN" {
return nil
}

then := p.expect("THEN").Pos
p.expectKeywordLike("RETURN")
withAction := p.tryParseWithAction()
items := parseCommaSeparatedList(p, p.parseSelectItem)

return &ast.ThenReturn{
Then: then,
WithAction: withAction,
Items: items,
}
}

func (p *Parser) parseInsert(pos token.Pos) *ast.Insert {
var insertOrType ast.InsertOrType
if p.Token.Kind == "OR" {
Expand Down Expand Up @@ -4165,12 +4198,15 @@ func (p *Parser) parseInsert(pos token.Pos) *ast.Insert {
input = p.parseSubQueryInput()
}

thenReturn := p.tryParseThenReturn()

return &ast.Insert{
Insert: pos,
InsertOrType: insertOrType,
TableName: name,
Columns: columns,
Input: input,
ThenReturn: thenReturn,
}
}

Expand Down Expand Up @@ -4238,12 +4274,14 @@ func (p *Parser) parseDelete(pos token.Pos) *ast.Delete {
name := p.parseIdent()
as := p.tryParseAsAlias(withOptionalAs)
where := p.parseWhere()
thenReturn := p.tryParseThenReturn()

return &ast.Delete{
Delete: pos,
TableName: name,
As: as,
Where: where,
Delete: pos,
TableName: name,
As: as,
Where: where,
ThenReturn: thenReturn,
}
}

Expand All @@ -4256,13 +4294,15 @@ func (p *Parser) parseUpdate(pos token.Pos) *ast.Update {
items := parseCommaSeparatedList(p, p.parseUpdateItem)

where := p.parseWhere()
thenReturn := p.tryParseThenReturn()

return &ast.Update{
Update: pos,
TableName: name,
As: as,
Updates: items,
Where: where,
Update: pos,
TableName: name,
As: as,
Updates: items,
Where: where,
ThenReturn: thenReturn,
}
}

Expand Down
1 change: 1 addition & 0 deletions testdata/input/dml/delete_then_return.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
delete foo where foo = 1 and bar = 2 then return *
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
INSERT OR UPDATE INTO foo
(foo, bar) VALUES (1, 2)
THEN RETURN WITH ACTION AS act *
1 change: 1 addition & 0 deletions testdata/input/dml/update_then_return_with_action.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 then return with action *
1 change: 1 addition & 0 deletions testdata/result/dml/delete _from.sql.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ delete from foo where foo = 1 and bar = 2
},
},
},
ThenReturn: (*ast.ThenReturn)(nil),
}

--- SQL
Expand Down
1 change: 1 addition & 0 deletions testdata/result/dml/delete.sql.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ delete foo where foo = 1 and bar = 2
},
},
},
ThenReturn: (*ast.ThenReturn)(nil),
}

--- SQL
Expand Down
1 change: 1 addition & 0 deletions testdata/result/dml/delete_as.sql.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ delete foo as F where F.foo = 1
},
},
},
ThenReturn: (*ast.ThenReturn)(nil),
}

--- SQL
Expand Down
Loading

0 comments on commit 66dfc61

Please sign in to comment.