diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index 5a763ff9217..f33cbbd690d 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -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 { @@ -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 } diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index 848960fa957..0f3a2ee13f8 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -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) diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index d59c7799ec0..39b317e9152 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -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 } diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index 82b1f188589..c43fc12d025 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -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", diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index 571a76bc9d5..b7be8f5aba1 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -461,6 +461,7 @@ type ForeignKey struct { Columns []ID RefTable ID RefColumns []ID + OnDelete OnDelete Position Position // position of the "FOREIGN" token }