Skip to content

Commit

Permalink
feat(spanner/spansql): add support for IF NOT EXISTS and IF EXISTS cl…
Browse files Browse the repository at this point in the history
…ause (#8245)

* feat(spanner/spansql): support CREATE TABLE IF NOT EXISTS

* feat(spanner/spansql): support CREATE INDEX IF NOT EXISTS

* feat(spanner/spansql): support ALTER TABLE ADD COLUMN IF NOT EXISTS

* feat(spanner/spansql): support DROP TABLE IF EXISTS, DROP INDEX IF EXISTS
  • Loading branch information
toga4 authored Jul 20, 2023
1 parent eca3c90 commit 96840ab
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 18 deletions.
40 changes: 30 additions & 10 deletions spanner/spansql/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -1081,7 +1089,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]
Expand All @@ -1091,6 +1099,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
Expand All @@ -1099,12 +1108,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()
Expand Down Expand Up @@ -1204,7 +1216,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:
Expand All @@ -1217,7 +1229,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
Expand All @@ -1232,6 +1244,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
Expand All @@ -1249,6 +1264,7 @@ func (p *parser) parseCreateIndex() (*CreateIndex, *parseError) {

Unique: unique,
NullFiltered: nullFiltered,
IfNotExists: ifNotExists,

Position: pos,
}
Expand Down Expand Up @@ -1527,7 +1543,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
Expand Down Expand Up @@ -1580,11 +1596,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") {
Expand Down
72 changes: 72 additions & 0 deletions spanner/spansql/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,78 @@ 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),
},
},
},
},
{
`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),
},
},
},
},
{
`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),
},
},
},
},
{
`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)
Expand Down
33 changes: 28 additions & 5 deletions spanner/spansql/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down Expand Up @@ -70,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 += ", "
Expand Down Expand Up @@ -138,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 {
Expand Down Expand Up @@ -258,7 +276,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 {
Expand Down
62 changes: 62 additions & 0 deletions spanner/spansql/sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,68 @@ 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,
},
{
&CreateIndex{
Name: "Ia",
Table: "Ta",
Columns: []KeyPart{
{Column: "Ca"},
},
IfNotExists: true,
Position: line(1),
},
"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,
},
{
&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",
Expand Down
13 changes: 10 additions & 3 deletions spanner/spansql/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -106,6 +107,7 @@ type CreateIndex struct {

Unique bool
NullFiltered bool
IfNotExists bool

Storing []ID
Interleave ID
Expand Down Expand Up @@ -149,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
}
Expand All @@ -162,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
}
Expand Down Expand Up @@ -284,7 +288,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 }
Expand Down

0 comments on commit 96840ab

Please sign in to comment.