Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(spanner/spansql): add support for IF NOT EXISTS and IF EXISTS clause #8245

Merged
merged 8 commits into from
Jul 20, 2023
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