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 foreign key actions #8296

Merged
merged 3 commits into from Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 9 additions & 1 deletion spanner/spansql/parser.go
Expand Up @@ -2227,7 +2227,7 @@ func (p *parser) parseForeignKey() (ForeignKey, *parseError) {

/*
foreign_key:
FOREIGN KEY ( column_name [, ... ] ) REFERENCES ref_table ( ref_column [, ... ] )
FOREIGN KEY ( column_name [, ... ] ) REFERENCES ref_table ( ref_column [, ... ] ) [ ON DELETE { CASCADE | NO ACTION } ]
*/

if err := p.expect("FOREIGN"); err != nil {
Expand All @@ -2253,6 +2253,14 @@ func (p *parser) parseForeignKey() (ForeignKey, *parseError) {
if err != nil {
return ForeignKey{}, err
}
// The ON DELETE clause is optional; it defaults to NoActionOnDelete.
fk.OnDelete = NoActionOnDelete
if p.eat("ON", "DELETE") {
fk.OnDelete, err = p.parseOnDelete()
if err != nil {
return ForeignKey{}, err
}
}
return fk, nil
}

Expand Down
53 changes: 53 additions & 0 deletions spanner/spansql/parser_test.go
Expand Up @@ -1555,6 +1555,59 @@ func TestParseDDL(t *testing.T) {
},
},
},
{
`CREATE TABLE tname1 (col1 INT64, col2 INT64, CONSTRAINT con1 FOREIGN KEY (col2) REFERENCES tname2 (col3) ON DELETE CASCADE) PRIMARY KEY (col1);
CREATE TABLE tname1 (col1 INT64, col2 INT64, CONSTRAINT con1 FOREIGN KEY (col2) REFERENCES tname2 (col3) ON DELETE NO ACTION) PRIMARY KEY (col1);
ALTER TABLE tname1 ADD CONSTRAINT con1 FOREIGN KEY (col2) REFERENCES tname2 (col3) ON DELETE CASCADE;
ALTER TABLE tname1 ADD CONSTRAINT con1 FOREIGN KEY (col2) REFERENCES tname2 (col3) ON DELETE NO ACTION;`,
&DDL{
Filename: "filename",
List: []DDLStmt{
&CreateTable{
Name: "tname1",
Columns: []ColumnDef{
{Name: "col1", Type: Type{Base: Int64}, Position: line(1)},
{Name: "col2", Type: Type{Base: Int64}, Position: line(1)},
},
Constraints: []TableConstraint{
{Name: "con1", Constraint: ForeignKey{Columns: []ID{"col2"}, RefTable: "tname2", RefColumns: []ID{"col3"}, OnDelete: CascadeOnDelete, Position: line(1)}, Position: line(1)},
},
PrimaryKey: []KeyPart{
{Column: "col1"},
},
Position: line(1),
},
&CreateTable{
Name: "tname1",
Columns: []ColumnDef{
{Name: "col1", Type: Type{Base: Int64}, Position: line(2)},
{Name: "col2", Type: Type{Base: Int64}, Position: line(2)},
},
Constraints: []TableConstraint{
{Name: "con1", Constraint: ForeignKey{Columns: []ID{"col2"}, RefTable: "tname2", RefColumns: []ID{"col3"}, OnDelete: NoActionOnDelete, Position: line(2)}, Position: line(2)},
},
PrimaryKey: []KeyPart{
{Column: "col1"},
},
Position: line(2),
},
&AlterTable{
Name: "tname1",
Alteration: AddConstraint{
Constraint: TableConstraint{Name: "con1", Constraint: ForeignKey{Columns: []ID{"col2"}, RefTable: "tname2", RefColumns: []ID{"col3"}, OnDelete: CascadeOnDelete, Position: line(3)}, Position: line(3)},
},
Position: line(3),
},
&AlterTable{
Name: "tname1",
Alteration: AddConstraint{
Constraint: TableConstraint{Name: "con1", Constraint: ForeignKey{Columns: []ID{"col2"}, RefTable: "tname2", RefColumns: []ID{"col3"}, OnDelete: NoActionOnDelete, Position: line(4)}, Position: line(4)},
},
Position: line(4),
},
},
},
},
}
for _, test := range tests {
got, err := ParseDDL("filename", test.in)
Expand Down
1 change: 1 addition & 0 deletions spanner/spansql/sql.go
Expand Up @@ -546,6 +546,7 @@ func (fk ForeignKey) SQL() string {
str := "FOREIGN KEY (" + idList(fk.Columns, ", ")
str += ") REFERENCES " + fk.RefTable.SQL() + " ("
str += idList(fk.RefColumns, ", ") + ")"
str += " ON DELETE " + fk.OnDelete.SQL()
return str
}

Expand Down
41 changes: 41 additions & 0 deletions spanner/spansql/sql_test.go
Expand Up @@ -717,6 +717,47 @@ func TestSQL(t *testing.T) {
"DROP INDEX IF EXISTS iname",
reparseDDL,
},
{
&CreateTable{
Name: "tname1",
Columns: []ColumnDef{
{Name: "cname1", Type: Type{Base: Int64}, NotNull: true, Position: line(2)},
{Name: "cname2", Type: Type{Base: Int64}, NotNull: true, Position: line(3)},
},
Constraints: []TableConstraint{
{
Name: "con1",
Constraint: ForeignKey{Columns: []ID{"cname2"}, RefTable: "tname2", RefColumns: []ID{"cname3"}, OnDelete: NoActionOnDelete, Position: line(4)},
Position: line(4),
},
},
PrimaryKey: []KeyPart{
{Column: "cname1"},
},
Position: line(1),
},
`CREATE TABLE tname1 (
cname1 INT64 NOT NULL,
cname2 INT64 NOT NULL,
CONSTRAINT con1 FOREIGN KEY (cname2) REFERENCES tname2 (cname3) ON DELETE NO ACTION,
) PRIMARY KEY(cname1)`,
reparseDDL,
},
{
&AlterTable{
Name: "tname1",
Alteration: AddConstraint{
Constraint: TableConstraint{
Name: "con1",
Constraint: ForeignKey{Columns: []ID{"cname2"}, RefTable: "tname2", RefColumns: []ID{"cname3"}, OnDelete: CascadeOnDelete, Position: line(1)},
Position: line(1),
},
},
Position: line(1),
},
`ALTER TABLE tname1 ADD CONSTRAINT con1 FOREIGN KEY (cname2) REFERENCES tname2 (cname3) ON DELETE CASCADE`,
reparseDDL,
},
{
&Insert{
Table: "Singers",
Expand Down
1 change: 1 addition & 0 deletions spanner/spansql/types.go
Expand Up @@ -461,6 +461,7 @@ type ForeignKey struct {
Columns []ID
RefTable ID
RefColumns []ID
OnDelete OnDelete

Position Position // position of the "FOREIGN" token
}
Expand Down