feat(spanner/spansql): define structures and parse UPDATE DML statements (#3192)
Updates #3162.
diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go
index 3ca5fbf..e7c2793 100644
--- a/spanner/spansql/parser.go
+++ b/spanner/spansql/parser.go
@@ -1281,7 +1281,13 @@
DELETE [FROM] target_name [[AS] alias]
WHERE condition
- TODO: Insert, Update.
+ UPDATE target_name [[AS] alias]
+ SET update_item [, ...]
+ WHERE condition
+
+ update_item: path_expression = expression | path_expression = DEFAULT
+
+ TODO: Insert.
*/
if p.eat("DELETE") {
@@ -1304,9 +1310,64 @@
}, nil
}
+ if p.eat("UPDATE") {
+ tname, err := p.parseTableOrIndexOrColumnName()
+ if err != nil {
+ return nil, err
+ }
+ u := &Update{
+ Table: tname,
+ }
+ // TODO: parse alias.
+ if err := p.expect("SET"); err != nil {
+ return nil, err
+ }
+ for {
+ ui, err := p.parseUpdateItem()
+ if err != nil {
+ return nil, err
+ }
+ u.Items = append(u.Items, ui)
+ if p.eat(",") {
+ continue
+ }
+ break
+ }
+ if err := p.expect("WHERE"); err != nil {
+ return nil, err
+ }
+ where, err := p.parseBoolExpr()
+ if err != nil {
+ return nil, err
+ }
+ u.Where = where
+ return u, nil
+ }
+
return nil, p.errorf("unknown DML statement")
}
+func (p *parser) parseUpdateItem() (UpdateItem, *parseError) {
+ col, err := p.parseTableOrIndexOrColumnName()
+ if err != nil {
+ return UpdateItem{}, err
+ }
+ ui := UpdateItem{
+ Column: col,
+ }
+ if err := p.expect("="); err != nil {
+ return UpdateItem{}, err
+ }
+ if p.eat("DEFAULT") {
+ return ui, nil
+ }
+ ui.Value, err = p.parseExpr()
+ if err != nil {
+ return UpdateItem{}, err
+ }
+ return ui, nil
+}
+
func (p *parser) parseColumnDef() (ColumnDef, *parseError) {
debugf("parseColumnDef: %v", p)
diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go
index 7968232..0ac2b90 100644
--- a/spanner/spansql/sql.go
+++ b/spanner/spansql/sql.go
@@ -158,6 +158,23 @@
return "DELETE FROM " + d.Table.SQL() + " WHERE " + d.Where.SQL()
}
+func (u *Update) SQL() string {
+ str := "UPDATE " + u.Table.SQL() + " SET "
+ for i, item := range u.Items {
+ if i > 0 {
+ str += ", "
+ }
+ str += item.Column.SQL() + " = "
+ if item.Value != nil {
+ str += item.Value.SQL()
+ } else {
+ str += "DEFAULT"
+ }
+ }
+ str += " WHERE " + u.Where.SQL()
+ return str
+}
+
func (cd ColumnDef) SQL() string {
str := cd.Name.SQL() + " " + cd.Type.SQL()
if cd.NotNull {
diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go
index b72302c..beb92ec 100644
--- a/spanner/spansql/sql_test.go
+++ b/spanner/spansql/sql_test.go
@@ -233,6 +233,21 @@
reparseDML,
},
{
+ &Update{
+ Table: "Ta",
+ Items: []UpdateItem{
+ {Column: "Cb", Value: IntegerLiteral(4)},
+ {Column: "Ce", Value: StringLiteral("wow")},
+ {Column: "Cf", Value: ID("Cg")},
+ {Column: "Cg", Value: Null},
+ {Column: "Ch", Value: nil},
+ },
+ Where: ID("Ca"),
+ },
+ `UPDATE Ta SET Cb = 4, Ce = "wow", Cf = Cg, Cg = NULL, Ch = DEFAULT WHERE Ca`,
+ reparseDML,
+ },
+ {
Query{
Select: Select{
List: []Expr{ID("A"), ID("B")},
diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go
index d39400a..ece38b0 100644
--- a/spanner/spansql/types.go
+++ b/spanner/spansql/types.go
@@ -217,7 +217,25 @@
func (d *Delete) String() string { return fmt.Sprintf("%#v", d) }
func (*Delete) isDMLStmt() {}
-// TODO: Insert, Update.
+// TODO: Insert.
+
+// Update represents an UPDATE statement.
+// https://cloud.google.com/spanner/docs/dml-syntax#update-statement
+type Update struct {
+ Table ID
+ Items []UpdateItem
+ Where BoolExpr
+
+ // TODO: Alias
+}
+
+func (u *Update) String() string { return fmt.Sprintf("%#v", u) }
+func (*Update) isDMLStmt() {}
+
+type UpdateItem struct {
+ Column ID
+ Value Expr // or nil for DEFAULT
+}
// ColumnDef represents a column definition as part of a CREATE TABLE
// or ALTER TABLE statement.