From 5d548a79ea2766231ae4b60cee2f124dab6e6f4c Mon Sep 17 00:00:00 2001 From: toga4 <81744248+toga4@users.noreply.github.com> Date: Tue, 11 Jul 2023 20:00:13 +0900 Subject: [PATCH 1/4] feat(spanner/spansql): support CREATE TABLE IF NOT EXISTS --- spanner/spansql/parser.go | 8 ++++++-- spanner/spansql/parser_test.go | 20 ++++++++++++++++++++ spanner/spansql/sql.go | 6 +++++- spanner/spansql/sql_test.go | 19 +++++++++++++++++++ spanner/spansql/types.go | 1 + 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index 1a6741e8aeb1..5ce5740c429c 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -1081,7 +1081,7 @@ func (p *parser) parseCreateTable() (*CreateTable, *parseError) { debugf("parseCreateTable: %v", p) /* - CREATE TABLE table_name( + CREATE TABLE [ IF NOT EXISTS ] table_name( [column_def, ...] [ table_constraint, ...] ) primary_key [, cluster] @@ -1091,6 +1091,7 @@ func (p *parser) parseCreateTable() (*CreateTable, *parseError) { cluster: INTERLEAVE IN PARENT table_name [ ON DELETE { CASCADE | NO ACTION } ] */ + var ifNotExists bool if err := p.expect("CREATE"); err != nil { return nil, err @@ -1099,12 +1100,15 @@ func (p *parser) parseCreateTable() (*CreateTable, *parseError) { if err := p.expect("TABLE"); err != nil { return nil, err } + if p.eat("IF", "NOT", "EXISTS") { + ifNotExists = true + } tname, err := p.parseTableOrIndexOrColumnName() if err != nil { return nil, err } - ct := &CreateTable{Name: tname, Position: pos} + ct := &CreateTable{Name: tname, Position: pos, IfNotExists: ifNotExists} err = p.parseCommaList("(", ")", func(p *parser) *parseError { if p.sniffTableConstraint() { tc, err := p.parseTableConstraint() diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index bf71f8dcf221..d1ba84550191 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -1483,6 +1483,26 @@ func TestParseDDL(t *testing.T) { }, }, }, + { + `CREATE TABLE IF NOT EXISTS tname (id INT64, name STRING(64)) PRIMARY KEY (id)`, + &DDL{ + Filename: "filename", + List: []DDLStmt{ + &CreateTable{ + Name: "tname", + IfNotExists: true, + Columns: []ColumnDef{ + {Name: "id", Type: Type{Base: Int64}, Position: line(1)}, + {Name: "name", Type: Type{Base: String, Len: 64}, Position: line(1)}, + }, + PrimaryKey: []KeyPart{ + {Column: "id"}, + }, + Position: line(1), + }, + }, + }, + }, } for _, test := range tests { got, err := ParseDDL("filename", test.in) diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index 70c69638da8e..5ef7125676b2 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -38,7 +38,11 @@ func buildSQL(x interface{ addSQL(*strings.Builder) }) string { } func (ct CreateTable) SQL() string { - str := "CREATE TABLE " + ct.Name.SQL() + " (\n" + str := "CREATE TABLE " + if ct.IfNotExists { + str += "IF NOT EXISTS " + } + str += ct.Name.SQL() + " (\n" for _, c := range ct.Columns { str += " " + c.SQL() + ",\n" } diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index ca4045c7c84d..d9caebd96bf8 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -655,6 +655,25 @@ func TestSQL(t *testing.T) { "ALTER INDEX iname DROP STORED COLUMN cname", reparseDDL, }, + { + &CreateTable{ + Name: "tname", + IfNotExists: true, + Columns: []ColumnDef{ + {Name: "id", Type: Type{Base: Int64}, Position: line(2)}, + {Name: "name", Type: Type{Base: String, Len: 64}, Position: line(3)}, + }, + PrimaryKey: []KeyPart{ + {Column: "id"}, + }, + Position: line(1), + }, + `CREATE TABLE IF NOT EXISTS tname ( + id INT64, + name STRING(64), +) PRIMARY KEY(id)`, + reparseDDL, + }, { &Insert{ Table: "Singers", diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index 7a0dbfaeae50..5a08f283d257 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -34,6 +34,7 @@ import ( // https://cloud.google.com/spanner/docs/data-definition-language#create_table type CreateTable struct { Name ID + IfNotExists bool Columns []ColumnDef Constraints []TableConstraint PrimaryKey []KeyPart From 08f74725cdb6f260c074278af069872ad2ffc35c Mon Sep 17 00:00:00 2001 From: toga4 <81744248+toga4@users.noreply.github.com> Date: Tue, 11 Jul 2023 20:14:05 +0900 Subject: [PATCH 2/4] feat(spanner/spansql): support CREATE INDEX IF NOT EXISTS --- spanner/spansql/parser.go | 8 ++++++-- spanner/spansql/parser_test.go | 17 +++++++++++++++++ spanner/spansql/sql.go | 6 +++++- spanner/spansql/sql_test.go | 13 +++++++++++++ spanner/spansql/types.go | 1 + 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index 5ce5740c429c..0860fb275fab 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -1208,7 +1208,7 @@ func (p *parser) parseCreateIndex() (*CreateIndex, *parseError) { debugf("parseCreateIndex: %v", p) /* - CREATE [UNIQUE] [NULL_FILTERED] INDEX index_name + CREATE [UNIQUE] [NULL_FILTERED] INDEX [IF NOT EXISTS] index_name ON table_name ( key_part [, ...] ) [ storing_clause ] [ , interleave_clause ] index_name: @@ -1221,7 +1221,7 @@ func (p *parser) parseCreateIndex() (*CreateIndex, *parseError) { INTERLEAVE IN table_name */ - var unique, nullFiltered bool + var unique, nullFiltered, ifNotExists bool if err := p.expect("CREATE"); err != nil { return nil, err @@ -1236,6 +1236,9 @@ func (p *parser) parseCreateIndex() (*CreateIndex, *parseError) { if err := p.expect("INDEX"); err != nil { return nil, err } + if p.eat("IF", "NOT", "EXISTS") { + ifNotExists = true + } iname, err := p.parseTableOrIndexOrColumnName() if err != nil { return nil, err @@ -1253,6 +1256,7 @@ func (p *parser) parseCreateIndex() (*CreateIndex, *parseError) { Unique: unique, NullFiltered: nullFiltered, + IfNotExists: ifNotExists, Position: pos, } diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index d1ba84550191..e70f6f717e1c 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -1503,6 +1503,23 @@ func TestParseDDL(t *testing.T) { }, }, }, + { + `CREATE INDEX IF NOT EXISTS iname ON tname (cname)`, + &DDL{ + Filename: "filename", + List: []DDLStmt{ + &CreateIndex{ + Name: "iname", + IfNotExists: true, + Table: "tname", + Columns: []KeyPart{ + {Column: "cname"}, + }, + Position: line(1), + }, + }, + }, + }, } for _, test := range tests { got, err := ParseDDL("filename", test.in) diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index 5ef7125676b2..cae4af857db3 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -74,7 +74,11 @@ func (ci CreateIndex) SQL() string { if ci.NullFiltered { str += " NULL_FILTERED" } - str += " INDEX " + ci.Name.SQL() + " ON " + ci.Table.SQL() + "(" + str += " INDEX " + if ci.IfNotExists { + str += "IF NOT EXISTS " + } + str += ci.Name.SQL() + " ON " + ci.Table.SQL() + "(" for i, c := range ci.Columns { if i > 0 { str += ", " diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index d9caebd96bf8..2de1cb8efa25 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -674,6 +674,19 @@ func TestSQL(t *testing.T) { ) PRIMARY KEY(id)`, reparseDDL, }, + { + &CreateIndex{ + Name: "Ia", + Table: "Ta", + Columns: []KeyPart{ + {Column: "Ca"}, + }, + IfNotExists: true, + Position: line(1), + }, + "CREATE INDEX IF NOT EXISTS Ia ON Ta(Ca)", + reparseDDL, + }, { &Insert{ Table: "Singers", diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index 5a08f283d257..3275be7e2b55 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -107,6 +107,7 @@ type CreateIndex struct { Unique bool NullFiltered bool + IfNotExists bool Storing []ID Interleave ID From 7f17a857c6faea3fda4a43add38a1523ed117b74 Mon Sep 17 00:00:00 2001 From: toga4 <81744248+toga4@users.noreply.github.com> Date: Tue, 11 Jul 2023 21:02:14 +0900 Subject: [PATCH 3/4] feat(spanner/spansql): support ALTER TABLE ADD COLUMN IF NOT EXISTS --- spanner/spansql/parser.go | 8 ++++++-- spanner/spansql/parser_test.go | 16 ++++++++++++++++ spanner/spansql/sql.go | 7 ++++++- spanner/spansql/sql_test.go | 12 ++++++++++++ spanner/spansql/types.go | 5 ++++- 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index 0860fb275fab..7a5dd299b87d 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -1535,7 +1535,7 @@ func (p *parser) parseAlterTable() (*AlterTable, *parseError) { ALTER TABLE table_name { table_alteration | table_column_alteration } table_alteration: - { ADD [ COLUMN ] column_def + { ADD [ COLUMN ] [ IF NOT EXISTS ] column_def | DROP [ COLUMN ] column_name | ADD table_constraint | DROP CONSTRAINT constraint_name @@ -1588,11 +1588,15 @@ func (p *parser) parseAlterTable() (*AlterTable, *parseError) { if err := p.expect("COLUMN"); err != nil { return nil, err } + var ifNotExists bool + if p.eat("IF", "NOT", "EXISTS") { + ifNotExists = true + } cd, err := p.parseColumnDef() if err != nil { return nil, err } - a.Alteration = AddColumn{Def: cd} + a.Alteration = AddColumn{Def: cd, IfNotExists: ifNotExists} return a, nil case tok.caseEqual("DROP"): if p.eat("CONSTRAINT") { diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index e70f6f717e1c..89aa7f1f495b 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -1520,6 +1520,22 @@ func TestParseDDL(t *testing.T) { }, }, }, + { + `ALTER TABLE tname ADD COLUMN IF NOT EXISTS cname STRING(64)`, + &DDL{ + Filename: "filename", + List: []DDLStmt{ + &AlterTable{ + Name: "tname", + Alteration: AddColumn{ + IfNotExists: true, + Def: ColumnDef{Name: "cname", Type: Type{Base: String, Len: 64}, Position: line(1)}, + }, + Position: line(1), + }, + }, + }, + }, } for _, test := range tests { got, err := ParseDDL("filename", test.in) diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index cae4af857db3..e54e09215f16 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -266,7 +266,12 @@ func (at AlterTable) SQL() string { } func (ac AddColumn) SQL() string { - return "ADD COLUMN " + ac.Def.SQL() + str := "ADD COLUMN " + if ac.IfNotExists { + str += "IF NOT EXISTS " + } + str += ac.Def.SQL() + return str } func (dc DropColumn) SQL() string { diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index 2de1cb8efa25..00d068901587 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -687,6 +687,18 @@ func TestSQL(t *testing.T) { "CREATE INDEX IF NOT EXISTS Ia ON Ta(Ca)", reparseDDL, }, + { + &AlterTable{ + Name: "tname", + Alteration: AddColumn{ + IfNotExists: true, + Def: ColumnDef{Name: "cname", Type: Type{Base: String, Len: 64}, Position: line(1)}, + }, + Position: line(1), + }, + "ALTER TABLE tname ADD COLUMN IF NOT EXISTS cname STRING(64)", + reparseDDL, + }, { &Insert{ Table: "Singers", diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index 3275be7e2b55..49925d11d953 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -286,7 +286,10 @@ func (ReplaceRowDeletionPolicy) isTableAlteration() {} func (DropRowDeletionPolicy) isTableAlteration() {} type ( - AddColumn struct{ Def ColumnDef } + AddColumn struct { + IfNotExists bool + Def ColumnDef + } DropColumn struct{ Name ID } AddConstraint struct{ Constraint TableConstraint } DropConstraint struct{ Name ID } From c4d21da88ae6613c5999ef29ab8319102b0fdcfc Mon Sep 17 00:00:00 2001 From: toga4 <81744248+toga4@users.noreply.github.com> Date: Tue, 11 Jul 2023 21:17:27 +0900 Subject: [PATCH 4/4] feat(spanner/spansql): support DROP TABLE IF EXISTS, DROP INDEX IF EXISTS --- spanner/spansql/parser.go | 16 ++++++++++++---- spanner/spansql/parser_test.go | 19 +++++++++++++++++++ spanner/spansql/sql.go | 14 ++++++++++++-- spanner/spansql/sql_test.go | 18 ++++++++++++++++++ spanner/spansql/types.go | 6 ++++-- 5 files changed, 65 insertions(+), 8 deletions(-) diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index 7a5dd299b87d..5a763ff92177 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -1005,8 +1005,8 @@ func (p *parser) parseDDLStmt() (DDLStmt, *parseError) { } else if p.eat("DROP") { pos := p.Pos() // These statements are simple. - // DROP TABLE table_name - // DROP INDEX index_name + // DROP TABLE [ IF EXISTS ] table_name + // DROP INDEX [ IF EXISTS ] index_name // DROP VIEW view_name // DROP ROLE role_name // DROP CHANGE STREAM change_stream_name @@ -1018,17 +1018,25 @@ func (p *parser) parseDDLStmt() (DDLStmt, *parseError) { default: return nil, p.errorf("got %q, want TABLE, VIEW, INDEX or CHANGE", tok.value) case tok.caseEqual("TABLE"): + var ifExists bool + if p.eat("IF", "EXISTS") { + ifExists = true + } name, err := p.parseTableOrIndexOrColumnName() if err != nil { return nil, err } - return &DropTable{Name: name, Position: pos}, nil + return &DropTable{Name: name, IfExists: ifExists, Position: pos}, nil case tok.caseEqual("INDEX"): + var ifExists bool + if p.eat("IF", "EXISTS") { + ifExists = true + } name, err := p.parseTableOrIndexOrColumnName() if err != nil { return nil, err } - return &DropIndex{Name: name, Position: pos}, nil + return &DropIndex{Name: name, IfExists: ifExists, Position: pos}, nil case tok.caseEqual("VIEW"): name, err := p.parseTableOrIndexOrColumnName() if err != nil { diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index 89aa7f1f495b..848960fa9576 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -1536,6 +1536,25 @@ func TestParseDDL(t *testing.T) { }, }, }, + { + `DROP TABLE IF EXISTS tname; + DROP INDEX IF EXISTS iname;`, + &DDL{ + Filename: "filename", + List: []DDLStmt{ + &DropTable{ + Name: "tname", + IfExists: true, + Position: line(1), + }, + &DropIndex{ + Name: "iname", + IfExists: true, + Position: line(2), + }, + }, + }, + }, } for _, test := range tests { got, err := ParseDDL("filename", test.in) diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index e54e09215f16..d59c7799ec0b 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -146,11 +146,21 @@ func (w WatchDef) SQL() string { } func (dt DropTable) SQL() string { - return "DROP TABLE " + dt.Name.SQL() + str := "DROP TABLE " + if dt.IfExists { + str += "IF EXISTS " + } + str += dt.Name.SQL() + return str } func (di DropIndex) SQL() string { - return "DROP INDEX " + di.Name.SQL() + str := "DROP INDEX " + if di.IfExists { + str += "IF EXISTS " + } + str += di.Name.SQL() + return str } func (dv DropView) SQL() string { diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index 00d068901587..82b1f1885893 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -699,6 +699,24 @@ func TestSQL(t *testing.T) { "ALTER TABLE tname ADD COLUMN IF NOT EXISTS cname STRING(64)", reparseDDL, }, + { + &DropTable{ + Name: "tname", + IfExists: true, + Position: line(1), + }, + "DROP TABLE IF EXISTS tname", + reparseDDL, + }, + { + &DropIndex{ + Name: "iname", + IfExists: true, + Position: line(1), + }, + "DROP INDEX IF EXISTS iname", + reparseDDL, + }, { &Insert{ Table: "Singers", diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index 49925d11d953..571a76bc9d5d 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -151,7 +151,8 @@ func (cr *CreateRole) clearOffset() { cr.Position.Offset = 0 } // DropTable represents a DROP TABLE statement. // https://cloud.google.com/spanner/docs/data-definition-language#drop_table type DropTable struct { - Name ID + Name ID + IfExists bool Position Position // position of the "DROP" token } @@ -164,7 +165,8 @@ func (dt *DropTable) clearOffset() { dt.Position.Offset = 0 } // DropIndex represents a DROP INDEX statement. // https://cloud.google.com/spanner/docs/data-definition-language#drop-index type DropIndex struct { - Name ID + Name ID + IfExists bool Position Position // position of the "DROP" token }