Permalink
Browse files

Foreign keys: fix bug in handling NO ACTION clauses

In foreign key definitions, all flavors of MySQL treat the following 3 clauses
equivalently:

* ON {UPDATE|DELETE} NO ACTION
* ON {UPDATE|DELETE} RESTRICT
* omitting an action clause from the FK definition entirely

In SHOW CREATE TABLE, all flavors omit ON {UPDATE|DELETE} RESTRICT clauses.
However, only MySQL 8+ also omits ON {UPDATE|DELETE} NO ACTION clauses, even
though they're entirely equivalent.

The previous logic in Go La Tengo assumed NO ACTION clauses were always
omitted, which was incorrect, and could erroneously flag tables with
UnsupportedDDL=true.

This commit also improves test coverage around this bug.
  • Loading branch information...
evanelias committed Dec 3, 2018
1 parent 89e3cb2 commit 9139dc8e1685380168f29478fe7048c074438330
Showing with 16 additions and 11 deletions.
  1. +6 −5 foreignkey.go
  2. +1 −1 instance_test.go
  3. +7 −3 tengo_test.go
  4. +2 −2 testdata/integration.sql
@@ -22,7 +22,7 @@ type ForeignKey struct {
// Definition returns this ForeignKey's definition clause, for use as part of a DDL
// statement.
func (fk *ForeignKey) Definition(_ Flavor) string {
func (fk *ForeignKey) Definition(flavor Flavor) string {
colParts := make([]string, len(fk.Columns))
for n, col := range fk.Columns {
colParts[n] = EscapeIdentifier(col.Name)
@@ -39,13 +39,14 @@ func (fk *ForeignKey) Definition(_ Flavor) string {
}
parentCols := strings.Join(colParts, ", ")
// MySQL does not output ON DELETE RESTRICT or ON UPDATE RESTRICT in its table create syntax.
// Ditto for equivalent ON DELETE NO ACTION or ON UPDATE NO ACTION.
// MySQL does not output ON DELETE RESTRICT or ON UPDATE RESTRICT in its table
// create syntax. Ditto for the completely-equivalent ON DELETE NO ACTION or ON
// UPDATE NO ACTION in MySQL 8+, but other flavors still display them.
var deleteRule, updateRule string
if fk.DeleteRule != "RESTRICT" && fk.DeleteRule != "NO ACTION" {
if fk.DeleteRule != "RESTRICT" && (fk.DeleteRule != "NO ACTION" || !flavor.HasDataDictionary()) {
deleteRule = fmt.Sprintf(" ON DELETE %s", fk.DeleteRule)
}
if fk.UpdateRule != "RESTRICT" && fk.UpdateRule != "NO ACTION" {
if fk.UpdateRule != "RESTRICT" && (fk.UpdateRule != "NO ACTION" || !flavor.HasDataDictionary()) {
updateRule = fmt.Sprintf(" ON UPDATE %s", fk.UpdateRule)
}
@@ -541,7 +541,7 @@ func (s TengoIntegrationSuite) TestInstanceSchemaIntrospection(t *testing.T) {
aTableFromDB = s.GetTable(t, "testing", "grab_bag")
aTableFromDB.SecondaryIndexes[0], aTableFromDB.SecondaryIndexes[1], aTableFromDB.SecondaryIndexes[2] = aTableFromDB.SecondaryIndexes[2], aTableFromDB.SecondaryIndexes[0], aTableFromDB.SecondaryIndexes[1]
fixIndexOrder(aTableFromDB)
if aTableFromDB.GeneratedCreateStatement(flavor) != aTableFromDB.CreateStatement {
if aTableFromDB.GeneratedCreateStatement(flavor) != aTableFromDB.CreateStatement && !aTableFromDB.UnsupportedDDL {
t.Error("fixIndexOrder did not behave as expected")
}
}
@@ -424,10 +424,14 @@ func foreignKeyTable() Table {
ReferencedTableName: "products",
ReferencedColumnNames: []string{"line", "model"},
DeleteRule: "CASCADE",
UpdateRule: "CASCADE",
UpdateRule: "NO ACTION",
},
}
// warning: haven't created Flavor-specific versions of this unit test fixture
// table yet because the need hasn't come up, but there are actual flavor-
// specific differences with FKs. In particular, MySQL 8+ squashes NO ACTION
// clauses from SHOW CREATE TABLE.
stmt := strings.Replace(`CREATE TABLE ~warranties~ (
~id~ int(10) unsigned NOT NULL,
~customer_id~ int(10) unsigned DEFAULT NULL,
@@ -436,8 +440,8 @@ func foreignKeyTable() Table {
PRIMARY KEY (~id~),
UNIQUE KEY ~product~ (~product_line~,~model~),
KEY ~customer~ (~customer_id~),
CONSTRAINT ~customer_fk~ FOREIGN KEY (~customer_id~) REFERENCES ~purchasing~.~customers~ (~id~),
CONSTRAINT ~product_fk~ FOREIGN KEY (~product_line~, ~model~) REFERENCES ~products~ (~line~, ~model~)
CONSTRAINT ~customer_fk~ FOREIGN KEY (~customer_id~) REFERENCES ~purchasing~.~customers~ (~id~) ON DELETE SET NULL,
CONSTRAINT ~product_fk~ FOREIGN KEY (~product_line~, ~model~) REFERENCES ~products~ (~line~, ~model~) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=latin1`, "~", "`", -1)
return Table{
@@ -49,8 +49,8 @@ CREATE TABLE warranties (
PRIMARY KEY (id),
UNIQUE KEY product (product_line,model),
KEY customer (customer_id),
CONSTRAINT customer_fk FOREIGN KEY (customer_id) REFERENCES purchasing.customers (id),
CONSTRAINT product FOREIGN KEY (product_line, model) REFERENCES products (line, model)
CONSTRAINT customer_fk FOREIGN KEY (customer_id) REFERENCES purchasing.customers (id) ON DELETE SET NULL,
CONSTRAINT product FOREIGN KEY (product_line, model) REFERENCES products (line, model) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE has_rows (

0 comments on commit 9139dc8

Please sign in to comment.