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): support Table rename & Table synonym #9275

Merged
merged 12 commits into from
Mar 10, 2024
141 changes: 138 additions & 3 deletions spanner/spansql/parser.go
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_st ream | alter_change_stream | drop_change_stream }
killah777 marked this conversation as resolved.
Show resolved Hide resolved
*/

// 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 @@ -1563,7 +1607,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 @@ -1608,6 +1655,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 @@ -1637,6 +1694,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 @@ -1687,10 +1754,78 @@ func (p *parser) parseAlterTable() (*AlterTable, *parseError) {
a.Alteration = ReplaceRowDeletionPolicy{RowDeletionPolicy: rdp}
return a, nil
}
case tok.caseEqual("RENAME"):
if p.eat("TO") {
new_name, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}
rt := RenameTo{ToName: new_name}
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 {
from_name, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}
if err := p.expect("TO"); err != nil {
return nil, err
}
to_name, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}
renameOps = append(renameOps, TableRenameOp{FromName: from_name, ToName: to_name})

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
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 @@ -1163,6 +1190,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 @@ -1196,9 +1294,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
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 @@ -296,6 +299,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 @@ -363,6 +382,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