Skip to content

Commit

Permalink
feat(spanner/spansql): support Table rename & Table synonym (#9275)
Browse files Browse the repository at this point in the history
* feat(spanner/spansql): support Table rename & Table synonym

* Update spanner/spansql/parser.go

Co-authored-by: rahul2393 <irahul@google.com>

* go fmt

* don't use underscores in Go names

---------

Co-authored-by: rahul2393 <irahul@google.com>
  • Loading branch information
killah777 and rahul2393 authored Mar 10, 2024
1 parent f1bca4c commit 9b97ce7
Show file tree
Hide file tree
Showing 5 changed files with 365 additions and 6 deletions.
141 changes: 138 additions & 3 deletions spanner/spansql/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ func (p *parser) parseDDLStmt() (DDLStmt, *parseError) {

/*
statement:
{ create_database | create_table | create_index | alter_table | drop_table | drop_index | create_change_stream | alter_change_stream | drop_change_stream }
{ create_database | create_table | create_index | alter_table | drop_table | rename_table | drop_index | create_change_stream | alter_change_stream | drop_change_stream }
*/

// TODO: support create_database
Expand Down Expand Up @@ -1069,6 +1069,9 @@ func (p *parser) parseDDLStmt() (DDLStmt, *parseError) {
}
return &DropSequence{Name: name, IfExists: ifExists, Position: pos}, nil
}
} else if p.sniff("RENAME", "TABLE") {
a, err := p.parseRenameTable()
return a, err
} else if p.sniff("ALTER", "DATABASE") {
a, err := p.parseAlterDatabase()
return a, err
Expand Down Expand Up @@ -1106,9 +1109,12 @@ func (p *parser) parseCreateTable() (*CreateTable, *parseError) {

/*
CREATE TABLE [ IF NOT EXISTS ] table_name(
[column_def, ...] [ table_constraint, ...] )
[column_def, ...] [ table_constraint, ...] [ synonym ] )
primary_key [, cluster]
synonym:
SYNONYM (name)
primary_key:
PRIMARY KEY ( [key_part, ...] )
Expand Down Expand Up @@ -1143,6 +1149,15 @@ func (p *parser) parseCreateTable() (*CreateTable, *parseError) {
return nil
}

if p.sniffTableSynonym() {
ts, err := p.parseTableSynonym()
if err != nil {
return err
}
ct.Synonym = ts
return nil
}

cd, err := p.parseColumnDef()
if err != nil {
return err
Expand Down Expand Up @@ -1228,6 +1243,35 @@ func (p *parser) sniffTableConstraint() bool {
return p.sniff("FOREIGN") || p.sniff("CHECK")
}

func (p *parser) sniffTableSynonym() bool {
return p.sniff("SYNONYM")
}

func (p *parser) parseTableSynonym() (ID, *parseError) {
debugf("parseTableSynonym: %v", p)

/*
table_synonym:
SYNONYM ( name )
*/

if err := p.expect("SYNONYM"); err != nil {
return "", err
}
if err := p.expect("("); err != nil {
return "", err
}
name, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return "", err
}
if err := p.expect(")"); err != nil {
return "", err
}

return name, nil
}

func (p *parser) parseCreateIndex() (*CreateIndex, *parseError) {
debugf("parseCreateIndex: %v", p)

Expand Down Expand Up @@ -1580,7 +1624,10 @@ func (p *parser) parseAlterTable() (*AlterTable, *parseError) {
| DROP [ COLUMN ] column_name
| ADD table_constraint
| DROP CONSTRAINT constraint_name
| SET ON DELETE { CASCADE | NO ACTION } }
| SET ON DELETE { CASCADE | NO ACTION }
| ADD SYNONYM synonym_name
| DROP SYNONYM synonym_name
| RENAME TO new_table_name }
table_column_alteration:
ALTER [ COLUMN ] column_name { { scalar_type | array_type } [NOT NULL] | SET options_def }
Expand Down Expand Up @@ -1625,6 +1672,16 @@ func (p *parser) parseAlterTable() (*AlterTable, *parseError) {
return a, nil
}

// TODO: "COLUMN" is optional. A column named SYNONYM is allowed.
if p.eat("SYNONYM") {
synonym, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}
a.Alteration = AddSynonym{Name: synonym}
return a, nil
}

// TODO: "COLUMN" is optional.
if err := p.expect("COLUMN"); err != nil {
return nil, err
Expand Down Expand Up @@ -1654,6 +1711,16 @@ func (p *parser) parseAlterTable() (*AlterTable, *parseError) {
return a, nil
}

// TODO: "COLUMN" is optional. A column named SYNONYM is allowed.
if p.eat("SYNONYM") {
synonym, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}
a.Alteration = DropSynonym{Name: synonym}
return a, nil
}

// TODO: "COLUMN" is optional.
if err := p.expect("COLUMN"); err != nil {
return nil, err
Expand Down Expand Up @@ -1704,10 +1771,78 @@ func (p *parser) parseAlterTable() (*AlterTable, *parseError) {
a.Alteration = ReplaceRowDeletionPolicy{RowDeletionPolicy: rdp}
return a, nil
}
case tok.caseEqual("RENAME"):
if p.eat("TO") {
newName, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}
rt := RenameTo{ToName: newName}
if p.eat(",", "ADD", "SYNONYM") {
synonym, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}
rt.Synonym = synonym
}
a.Alteration = rt
return a, nil
}
}
return a, nil
}

func (p *parser) parseRenameTable() (*RenameTable, *parseError) {
debugf("parseRenameTable: %v", p)

/*
RENAME TABLE table_name TO new_name [, table_name2 TO new_name2, ...]
*/

if err := p.expect("RENAME"); err != nil {
return nil, err
}
pos := p.Pos()
if err := p.expect("TABLE"); err != nil {
return nil, err
}
rt := &RenameTable{
Position: pos,
}

var renameOps []TableRenameOp
for {
fromName, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}
if err := p.expect("TO"); err != nil {
return nil, err
}
toName, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}
renameOps = append(renameOps, TableRenameOp{FromName: fromName, ToName: toName})

tok := p.next()
if tok.err != nil {
if tok.err == eof {
break
}
return nil, tok.err
} else if tok.value == "," {
continue
} else if tok.value == ";" {
break
} else {
return nil, p.errorf("unexpected token %q", tok.value)
}
}
rt.TableRenameOps = renameOps
return rt, nil
}

func (p *parser) parseAlterDatabase() (*AlterDatabase, *parseError) {
debugf("parseAlterDatabase: %v", p)

Expand Down
103 changes: 102 additions & 1 deletion spanner/spansql/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,33 @@ func TestParseDDL(t *testing.T) {
);
DROP SEQUENCE MySequence;
-- Table with a synonym.
CREATE TABLE TableWithSynonym (
Name STRING(MAX) NOT NULL,
SYNONYM(AnotherName),
) PRIMARY KEY (Name);
ALTER TABLE TableWithSynonym DROP SYNONYM AnotherName;
ALTER TABLE TableWithSynonym ADD SYNONYM YetAnotherName;
-- Table rename.
CREATE TABLE OldName (
Name STRING(MAX) NOT NULL,
) PRIMARY KEY (Name);
ALTER TABLE OldName RENAME TO NewName;
ALTER TABLE NewName RENAME TO OldName, ADD SYNONYM NewName;
-- Table rename chain.
CREATE TABLE Table1 (
Name STRING(MAX) NOT NULL,
) PRIMARY KEY (Name);
CREATE TABLE Table2 (
Name STRING(MAX) NOT NULL,
) PRIMARY KEY (Name);
RENAME TABLE Table1 TO temp, Table2 TO Table1, temp TO Table2;
-- Trailing comment at end of file.
`, &DDL{Filename: "filename", List: []DDLStmt{
&CreateTable{
Expand Down Expand Up @@ -1164,6 +1191,77 @@ func TestParseDDL(t *testing.T) {
Position: line(114),
},
&DropSequence{Name: "MySequence", Position: line(120)},

&CreateTable{
Name: "TableWithSynonym",
Columns: []ColumnDef{
{Name: "Name", Type: Type{Base: String, Len: MaxLen}, NotNull: true, Position: line(124)},
},
Synonym: "AnotherName",
PrimaryKey: []KeyPart{{Column: "Name"}},
Position: line(123),
},
&AlterTable{
Name: "TableWithSynonym",
Alteration: DropSynonym{
Name: "AnotherName",
},
Position: line(128),
},
&AlterTable{
Name: "TableWithSynonym",
Alteration: AddSynonym{
Name: "YetAnotherName",
},
Position: line(129),
},
&CreateTable{
Name: "OldName",
Columns: []ColumnDef{
{Name: "Name", Type: Type{Base: String, Len: MaxLen}, NotNull: true, Position: line(133)},
},
PrimaryKey: []KeyPart{{Column: "Name"}},
Position: line(132),
},
&AlterTable{
Name: "OldName",
Alteration: RenameTo{
ToName: "NewName",
},
Position: line(136),
},
&AlterTable{
Name: "NewName",
Alteration: RenameTo{
ToName: "OldName",
Synonym: "NewName",
},
Position: line(137),
},
&CreateTable{
Name: "Table1",
Columns: []ColumnDef{
{Name: "Name", Type: Type{Base: String, Len: MaxLen}, NotNull: true, Position: line(141)},
},
PrimaryKey: []KeyPart{{Column: "Name"}},
Position: line(140),
},
&CreateTable{
Name: "Table2",
Columns: []ColumnDef{
{Name: "Name", Type: Type{Base: String, Len: MaxLen}, NotNull: true, Position: line(144)},
},
PrimaryKey: []KeyPart{{Column: "Name"}},
Position: line(143),
},
&RenameTable{
TableRenameOps: []TableRenameOp{
{FromName: "Table1", ToName: "temp"},
{FromName: "Table2", ToName: "Table1"},
{FromName: "temp", ToName: "Table2"},
},
Position: line(147),
},
}, Comments: []*Comment{
{
Marker: "#", Start: line(2), End: line(2),
Expand Down Expand Up @@ -1197,9 +1295,12 @@ func TestParseDDL(t *testing.T) {
{Marker: "--", Isolated: true, Start: line(43), End: line(43), Text: []string{"Table with generated column."}},
{Marker: "--", Isolated: true, Start: line(49), End: line(49), Text: []string{"Table with row deletion policy."}},
{Marker: "--", Isolated: true, Start: line(75), End: line(75), Text: []string{"Table has a column with a default value."}},
{Marker: "--", Isolated: true, Start: line(122), End: line(122), Text: []string{"Table with a synonym."}},
{Marker: "--", Isolated: true, Start: line(131), End: line(131), Text: []string{"Table rename."}},
{Marker: "--", Isolated: true, Start: line(139), End: line(139), Text: []string{"Table rename chain."}},

// Comment after everything else.
{Marker: "--", Isolated: true, Start: line(122), End: line(122), Text: []string{"Trailing comment at end of file."}},
{Marker: "--", Isolated: true, Start: line(149), End: line(149), Text: []string{"Trailing comment at end of file."}},
}}},
// No trailing comma:
{`ALTER TABLE T ADD COLUMN C2 INT64`, &DDL{Filename: "filename", List: []DDLStmt{
Expand Down
30 changes: 30 additions & 0 deletions spanner/spansql/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ func (ct CreateTable) SQL() string {
for _, tc := range ct.Constraints {
str += " " + tc.SQL() + ",\n"
}
if len(ct.Synonym) > 0 {
str += " SYNONYM(" + ct.Synonym.SQL() + "),\n"
}
str += ") PRIMARY KEY("
for i, c := range ct.PrimaryKey {
if i > 0 {
Expand Down Expand Up @@ -306,6 +309,22 @@ func (dc DropConstraint) SQL() string {
return "DROP CONSTRAINT " + dc.Name.SQL()
}

func (rt RenameTo) SQL() string {
str := "RENAME TO " + rt.ToName.SQL()
if len(rt.Synonym) > 0 {
str += ", ADD SYNONYM " + rt.Synonym.SQL()
}
return str
}

func (as AddSynonym) SQL() string {
return "ADD SYNONYM " + as.Name.SQL()
}

func (ds DropSynonym) SQL() string {
return "DROP SYNONYM " + ds.Name.SQL()
}

func (sod SetOnDelete) SQL() string {
return "SET ON DELETE " + sod.Action.SQL()
}
Expand Down Expand Up @@ -373,6 +392,17 @@ func (co ColumnOptions) SQL() string {
return str
}

func (rt RenameTable) SQL() string {
str := "RENAME TABLE "
for i, op := range rt.TableRenameOps {
if i > 0 {
str += ", "
}
str += op.FromName.SQL() + " TO " + op.ToName.SQL()
}
return str
}

func (ad AlterDatabase) SQL() string {
return "ALTER DATABASE " + ad.Name.SQL() + " " + ad.Alteration.SQL()
}
Expand Down
Loading

0 comments on commit 9b97ce7

Please sign in to comment.