diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index 3ca5fbf02c4..e7c27937b55 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -1281,7 +1281,13 @@ func (p *parser) parseDMLStmt() (DMLStmt, *parseError) { 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 @@ func (p *parser) parseDMLStmt() (DMLStmt, *parseError) { }, 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 7968232dbed..0ac2b90f26b 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -158,6 +158,23 @@ func (d *Delete) SQL() string { 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 b72302c041a..beb92ec47a0 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -232,6 +232,21 @@ func TestSQL(t *testing.T) { "DELETE FROM Ta WHERE C > 2", 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{ diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index d39400a0ce5..ece38b084cc 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -217,7 +217,25 @@ type Delete struct { 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.