From 4a18c53e7de643613232893bcde7e2ecd662700e Mon Sep 17 00:00:00 2001 From: Seiichi Uchida Date: Thu, 9 Jun 2022 20:20:24 +0900 Subject: [PATCH 1/2] feat(spanner/spansql): add a support for parsing INSERT statement --- spanner/spansql/parser.go | 54 +++++++++++++++++++++++++++++++++- spanner/spansql/parser_test.go | 42 ++++++++++++++++++++++++++ spanner/spansql/sql.go | 32 ++++++++++++++++++++ spanner/spansql/types.go | 24 ++++++++++++++- 4 files changed, 150 insertions(+), 2 deletions(-) diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index 0ebefb1ddd9..baff83f5244 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -1442,7 +1442,16 @@ func (p *parser) parseDMLStmt() (DMLStmt, *parseError) { update_item: path_expression = expression | path_expression = DEFAULT - TODO: Insert. + INSERT [INTO] target_name + (column_name_1 [, ..., column_name_n] ) + input + + input: + VALUES (row_1_column_1_expr [, ..., row_1_column_n_expr ] ) + [, ..., (row_k_column_1_expr [, ..., row_k_column_n_expr ] ) ] + | select_query + + expr: value_expression | DEFAULT */ if p.eat("DELETE") { @@ -1499,6 +1508,49 @@ func (p *parser) parseDMLStmt() (DMLStmt, *parseError) { return u, nil } + if p.eat("INSERT") { + p.eat("INTO") // optional + tname, err := p.parseTableOrIndexOrColumnName() + if err != nil { + return nil, err + } + + columns, err := p.parseColumnNameList() + if err != nil { + return nil, err + } + + var input ValuesOrSelect + if p.eat("VALUES") { + values := make([][]Expr, 0) + if exprs, err := p.parseParenExprList(); err != nil { + return nil, err + } else { + values = append(values, exprs) + } + for p.eat(",") { + exprs, err := p.parseParenExprList() + if err != nil { + return nil, err + } + values = append(values, exprs) + } + + input = Values(values) + } else { + input, err = p.parseSelect() + if err != nil { + return nil, err + } + } + + return &Insert{ + Table: tname, + Columns: columns, + Input: input, + }, nil + } + return nil, p.errorf("unknown DML statement") } diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index 9b4565b42bd..d42dcf7427b 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -300,6 +300,48 @@ func TestParseQuery(t *testing.T) { } } +func TestParseDMLStmt(t *testing.T) { + tests := []struct { + in string + want DMLStmt + }{ + {"INSERT Singers (SingerId, FirstName, LastName) VALUES (1, 'Marc', 'Richards')", + &Insert{ + Table: "Singers", + Columns: []ID{ID("SingerId"), ID("FirstName"), ID("LastName")}, + Input: Values{{IntegerLiteral(1), StringLiteral("Marc"), StringLiteral("Richards")}}, + }, + }, + {"INSERT Singers (SingerId, FirstName, LastName) SELECT * FROM UNNEST ([1, 2, 3]) AS data", + &Insert{ + Table: "Singers", + Columns: []ID{ID("SingerId"), ID("FirstName"), ID("LastName")}, + Input: Select{ + List: []Expr{Star}, + From: []SelectFrom{SelectFromUnnest{ + Expr: Array{ + IntegerLiteral(1), + IntegerLiteral(2), + IntegerLiteral(3), + }, + Alias: ID("data"), + }}, + }, + }, + }, + } + for _, test := range tests { + got, err := ParseDMLStmt(test.in) + if err != nil { + t.Errorf("ParseDMLStmt(%q): %v", test.in, err) + continue + } + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ParseDMLStmt(%q) incorrect.\n got %#v\nwant %#v", test.in, got, test.want) + } + } +} + func TestParseExpr(t *testing.T) { tests := []struct { in string diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index 406b5a8648c..32e498d1ac6 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -260,6 +260,38 @@ func (u *Update) SQL() string { return str } +func (i *Insert) SQL() string { + str := "INSERT " + i.Table.SQL() + " INTO (" + for i, column := range i.Columns { + if i > 0 { + str += ", " + } + str += column.SQL() + } + str += ") " + str += i.Input.SQL() + return str +} + +func (v Values) SQL() string { + str := "VALUES " + for j, values := range v { + if j > 0 { + str += ", " + } + str += "(" + + for k, value := range values { + if k > 0 { + str += ", " + } + str += value.SQL() + } + str += ")" + } + return str +} + func (cd ColumnDef) SQL() string { str := cd.Name.SQL() + " " + cd.Type.SQL() if cd.NotNull { diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index 521df50b752..a9b335eee43 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -302,7 +302,29 @@ type Delete struct { func (d *Delete) String() string { return fmt.Sprintf("%#v", d) } func (*Delete) isDMLStmt() {} -// TODO: Insert. +// Insert represents an INSERT statement. +// https://cloud.google.com/spanner/docs/dml-syntax#insert-statement +type Insert struct { + Table ID + Columns []ID + Input ValuesOrSelect +} + +// Values represents one or more lists of expressions passed to an `INSERT` statement. +type Values [][]Expr + +func (v Values) isValuesOrSelect() {} +func (v Values) String() string { return fmt.Sprintf("%#v", v) } + +type ValuesOrSelect interface { + isValuesOrSelect() + SQL() string +} + +func (Select) isValuesOrSelect() {} + +func (i *Insert) String() string { return fmt.Sprintf("%#v", i) } +func (*Insert) isDMLStmt() {} // Update represents an UPDATE statement. // https://cloud.google.com/spanner/docs/dml-syntax#update-statement From a227596682454638749cc36cb63444c5238ad3a0 Mon Sep 17 00:00:00 2001 From: Seiichi Uchida Date: Mon, 13 Jun 2022 09:25:27 +0900 Subject: [PATCH 2/2] Clean up --- spanner/spansql/parser.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index baff83f5244..2f2d066a0c1 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -1523,19 +1523,16 @@ func (p *parser) parseDMLStmt() (DMLStmt, *parseError) { var input ValuesOrSelect if p.eat("VALUES") { values := make([][]Expr, 0) - if exprs, err := p.parseParenExprList(); err != nil { - return nil, err - } else { - values = append(values, exprs) - } - for p.eat(",") { + for { exprs, err := p.parseParenExprList() if err != nil { return nil, err } values = append(values, exprs) + if !p.eat(",") { + break + } } - input = Values(values) } else { input, err = p.parseSelect()