feat(spanner/spansql): add support for foreign key actions (#8296)

Co-authored-by: rahul2393 <irahul@google.com>
diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go
index 5a763ff..f33cbbd 100644
--- a/spanner/spansql/parser.go
+++ b/spanner/spansql/parser.go
@@ -2227,7 +2227,7 @@
 
 	/*
 		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 @@
 	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 848960f..0f3a2ee 100644
--- a/spanner/spansql/parser_test.go
+++ b/spanner/spansql/parser_test.go
@@ -1555,6 +1555,59 @@
 				},
 			},
 		},
+		{
+			`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 d59c779..39b317e 100644
--- a/spanner/spansql/sql.go
+++ b/spanner/spansql/sql.go
@@ -546,6 +546,7 @@
 	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 82b1f18..c43fc12 100644
--- a/spanner/spansql/sql_test.go
+++ b/spanner/spansql/sql_test.go
@@ -718,6 +718,47 @@
 			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",
 				Columns: []ID{ID("SingerId"), ID("FirstName"), ID("LastName")},
diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go
index 571a76b..b7be8f5 100644
--- a/spanner/spansql/types.go
+++ b/spanner/spansql/types.go
@@ -461,6 +461,7 @@
 	Columns    []ID
 	RefTable   ID
 	RefColumns []ID
+	OnDelete   OnDelete
 
 	Position Position // position of the "FOREIGN" token
 }