Skip to content

Commit b02d6d5

Browse files
authored
Merge pull request #2944 from dolthub/fulghum/fk_ref_actions
Add support for the `SET DEFAULT` foreign key referential action
2 parents af677a1 + a3bedc3 commit b02d6d5

File tree

8 files changed

+200
-10
lines changed

8 files changed

+200
-10
lines changed

enginetest/queries/foreign_key_queries.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ var ForeignKeyTests = []ScriptTest{
134134
},
135135
},
136136
{
137+
// MySQL parses the SET DEFAULT referential action, but most engines, e.g. InnoDB, don't actually support it
137138
Name: "SET DEFAULT not supported",
138139
Assertions: []ScriptTestAssertion{
139140
{

sql/analyzer/apply_foreign_keys.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import (
2121
"github.com/dolthub/go-mysql-server/sql/transform"
2222

2323
"github.com/dolthub/go-mysql-server/sql"
24+
"github.com/dolthub/go-mysql-server/sql/expression"
2425
"github.com/dolthub/go-mysql-server/sql/plan"
26+
"github.com/dolthub/go-mysql-server/sql/planbuilder"
2527
)
2628

2729
// applyForeignKeys handles the application and resolution of foreign keys and their tables.
@@ -380,7 +382,19 @@ func getForeignKeyRefActions(ctx *sql.Context, a *Analyzer, tbl sql.ForeignKeyTa
380382
if err != nil {
381383
return nil, err
382384
}
383-
childTblSch := childTbl.Schema()
385+
386+
// TODO: Foreign key information is not fully added to the plan node until these FK rules in the analyzer,
387+
// but it should be added during the binding phase, in planbuilder. Because this information is added
388+
// late, we have to do extra work here to resolve the schema defaults, which normally would happen
389+
// during binding. This extra FK information also doesn't get its exec indexes fixed up, so we have to
390+
// manually do that here. Moving all the FK information into the binding, planbuilder, phase would
391+
// clean this up. We should also fix assignExecIndexes to find all these FK schema references and fix
392+
// their exec indexes.
393+
childTblSch, err := resolveSchemaDefaults(ctx, a.Catalog, childTbl)
394+
if err != nil {
395+
return nil, err
396+
}
397+
384398
childParentMapping, err := plan.GetChildParentMapping(tblSch, childTblSch, fk)
385399
if err != nil {
386400
return nil, err
@@ -429,6 +443,40 @@ func getForeignKeyRefActions(ctx *sql.Context, a *Analyzer, tbl sql.ForeignKeyTa
429443
return fkEditor, nil
430444
}
431445

446+
// resolveSchemaDefaults resolves the default values for the schema of |table|. This is primarily needed for column
447+
// default value expressions, since those don't get resolved during the planbuilder phase and assignExecIndexes
448+
// doesn't traverse through the ForeignKeyEditors and referential actions to find all of them. In addition to resolving
449+
// the expressions, this also ensures their GetField indexes are correct, knowing that those expressions will only
450+
// be evaluated in the context of a single table.
451+
func resolveSchemaDefaults(ctx *sql.Context, catalog *Catalog, table sql.Table) (sql.Schema, error) {
452+
// Resolve any column default expressions in tblSch
453+
builder := planbuilder.New(ctx, catalog, nil, sql.GlobalParser)
454+
childTblSch := builder.ResolveSchemaDefaults(ctx.GetCurrentDatabase(), table.Name(), table.Schema())
455+
456+
// Field Indexes are off by one initially and don't fixed by assignExecIndexes because it doesn't traverse through
457+
// the ForeignKeyEditors and referential actions, so we correct them here. This is safe because we know these fields
458+
// will only ever be accessed within the scope of a single table, so all we have to do is decrement the index by 1.
459+
for i, col := range childTblSch {
460+
if col.Default != nil {
461+
expr := col.Default.Expr
462+
expr, identity, err := transform.Expr(expr, func(e sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
463+
if gf, ok := e.(*expression.GetField); ok {
464+
return gf.WithIndex(gf.Index() - 1), transform.NewTree, nil
465+
}
466+
return e, transform.SameTree, nil
467+
})
468+
if err != nil {
469+
return nil, err
470+
}
471+
if identity == transform.NewTree {
472+
childTblSch[i].Default.Expr = expr
473+
}
474+
}
475+
}
476+
477+
return childTblSch, nil
478+
}
479+
432480
// foreignKeyTableName is the combination of a table's database along with their name, both lowercased.
433481
type foreignKeyTableName struct {
434482
dbName string

sql/constraints.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const (
3636
// a number of referential actions, the majority of them are functionally ignored and default to RESTRICT.
3737
func (f ForeignKeyReferentialAction) IsEquivalentToRestrict() bool {
3838
switch f {
39-
case ForeignKeyReferentialAction_Cascade, ForeignKeyReferentialAction_SetNull:
39+
case ForeignKeyReferentialAction_Cascade, ForeignKeyReferentialAction_SetNull, ForeignKeyReferentialAction_SetDefault:
4040
return false
4141
default:
4242
return true

sql/plan/ddl.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -368,11 +368,6 @@ func (c *CreateTable) CreateForeignKeys(ctx *sql.Context, tableNode sql.Table) e
368368
}
369369

370370
for i, fkDef := range c.fkDefs {
371-
if fkDef.OnUpdate == sql.ForeignKeyReferentialAction_SetDefault ||
372-
fkDef.OnDelete == sql.ForeignKeyReferentialAction_SetDefault {
373-
return sql.ErrForeignKeySetDefault.New()
374-
}
375-
376371
if fkChecks.(int8) == 1 {
377372
fkParentTbl := c.fkParentTbls[i]
378373
// If a foreign key is self-referential then the analyzer uses a nil since the table does not yet exist

sql/plan/delete.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ func (p *DeleteFrom) WithChildren(children ...sql.Node) (sql.Node, error) {
110110
if len(children) != 1 {
111111
return nil, sql.ErrInvalidChildrenNumber.New(p, len(children), 1)
112112
}
113+
113114
return NewDeleteFrom(children[0], p.explicitTargets), nil
114115
}
115116

sql/plan/foreign_key_editor.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ func (fkEditor *ForeignKeyEditor) Update(ctx *sql.Context, old sql.Row, new sql.
109109
}
110110
case sql.ForeignKeyReferentialAction_Cascade:
111111
case sql.ForeignKeyReferentialAction_SetNull:
112+
case sql.ForeignKeyReferentialAction_SetDefault:
112113
}
113114
}
114115
if err := fkEditor.Editor.Update(ctx, old, new); err != nil {
@@ -124,6 +125,10 @@ func (fkEditor *ForeignKeyEditor) Update(ctx *sql.Context, old sql.Row, new sql.
124125
if err := fkEditor.OnUpdateSetNull(ctx, refActionData, old, new, depth+1); err != nil {
125126
return err
126127
}
128+
case sql.ForeignKeyReferentialAction_SetDefault:
129+
if err := fkEditor.OnUpdateSetDefault(ctx, refActionData, old, new, depth+1); err != nil {
130+
return err
131+
}
127132
}
128133
}
129134
return nil
@@ -190,6 +195,58 @@ func (fkEditor *ForeignKeyEditor) OnUpdateCascade(ctx *sql.Context, refActionDat
190195
return err
191196
}
192197

198+
// OnUpdateSetDefault handles the ON UPDATE SET DEFAULT referential action.
199+
func (fkEditor *ForeignKeyEditor) OnUpdateSetDefault(ctx *sql.Context, refActionData ForeignKeyRefActionData, old sql.Row, new sql.Row, depth int) error {
200+
if ok, err := fkEditor.ColumnsUpdated(ctx, refActionData, old, new); err != nil {
201+
return err
202+
} else if !ok {
203+
return nil
204+
}
205+
206+
rowIter, err := refActionData.RowMapper.GetIter(ctx, old, false)
207+
if err != nil {
208+
return err
209+
}
210+
defer rowIter.Close(ctx)
211+
var rowToDefault sql.Row
212+
for rowToDefault, err = rowIter.Next(ctx); err == nil; rowToDefault, err = rowIter.Next(ctx) {
213+
// MySQL seems to have a bug where cyclical foreign keys return an error at a depth of 15 instead of 16.
214+
// This replicates the observed behavior, regardless of whether we're replicating a bug or intentional behavior.
215+
if depth >= 15 {
216+
if fkEditor.Cyclical {
217+
return sql.ErrForeignKeyDepthLimit.New()
218+
} else if depth > 15 {
219+
return sql.ErrForeignKeyDepthLimit.New()
220+
}
221+
}
222+
223+
modifiedRow := make(sql.Row, len(rowToDefault))
224+
for i := range rowToDefault {
225+
// Row contents are nil by default, so we only need to assign the non-affected values
226+
if refActionData.ChildParentMapping[i] == -1 {
227+
modifiedRow[i] = rowToDefault[i]
228+
} else {
229+
col := refActionData.Editor.Schema[i]
230+
if col.Default != nil {
231+
newVal, err := col.Default.Eval(ctx, rowToDefault)
232+
if err != nil {
233+
return err
234+
}
235+
modifiedRow[i] = newVal
236+
}
237+
}
238+
}
239+
err = refActionData.Editor.Update(ctx, rowToDefault, modifiedRow, depth)
240+
if err != nil {
241+
return err
242+
}
243+
}
244+
if err == io.EOF {
245+
return nil
246+
}
247+
return err
248+
}
249+
193250
// OnUpdateSetNull handles the ON UPDATE SET NULL referential action.
194251
func (fkEditor *ForeignKeyEditor) OnUpdateSetNull(ctx *sql.Context, refActionData ForeignKeyRefActionData, old sql.Row, new sql.Row, depth int) error {
195252
if ok, err := fkEditor.ColumnsUpdated(ctx, refActionData, old, new); err != nil {
@@ -237,6 +294,7 @@ func (fkEditor *ForeignKeyEditor) Delete(ctx *sql.Context, row sql.Row, depth in
237294
}
238295
case sql.ForeignKeyReferentialAction_Cascade:
239296
case sql.ForeignKeyReferentialAction_SetNull:
297+
case sql.ForeignKeyReferentialAction_SetDefault:
240298
}
241299
}
242300
if err := fkEditor.Editor.Delete(ctx, row); err != nil {
@@ -252,6 +310,10 @@ func (fkEditor *ForeignKeyEditor) Delete(ctx *sql.Context, row sql.Row, depth in
252310
if err := fkEditor.OnDeleteSetNull(ctx, refActionData, row, depth+1); err != nil {
253311
return err
254312
}
313+
case sql.ForeignKeyReferentialAction_SetDefault:
314+
if err := fkEditor.OnDeleteSetDefault(ctx, refActionData, row, depth+1); err != nil {
315+
return err
316+
}
255317
}
256318
}
257319
return nil
@@ -303,6 +365,52 @@ func (fkEditor *ForeignKeyEditor) OnDeleteCascade(ctx *sql.Context, refActionDat
303365
return err
304366
}
305367

368+
// OnDeleteSetDefault handles the ON DELETE SET DEFAULT referential action.
369+
func (fkEditor *ForeignKeyEditor) OnDeleteSetDefault(ctx *sql.Context, refActionData ForeignKeyRefActionData, row sql.Row, depth int) error {
370+
rowIter, err := refActionData.RowMapper.GetIter(ctx, row, false)
371+
if err != nil {
372+
return err
373+
}
374+
defer rowIter.Close(ctx)
375+
var rowToDefault sql.Row
376+
for rowToDefault, err = rowIter.Next(ctx); err == nil; rowToDefault, err = rowIter.Next(ctx) {
377+
// MySQL seems to have a bug where cyclical foreign keys return an error at a depth of 15 instead of 16.
378+
// This replicates the observed behavior, regardless of whether we're replicating a bug or intentional behavior.
379+
if depth >= 15 {
380+
if fkEditor.Cyclical {
381+
return sql.ErrForeignKeyDepthLimit.New()
382+
} else if depth > 15 {
383+
return sql.ErrForeignKeyDepthLimit.New()
384+
}
385+
}
386+
387+
modifiedRow := make(sql.Row, len(rowToDefault))
388+
for i := range rowToDefault {
389+
// Row contents are nil by default, so we only need to assign the non-affected values
390+
if refActionData.ChildParentMapping[i] == -1 {
391+
modifiedRow[i] = rowToDefault[i]
392+
} else {
393+
col := refActionData.Editor.Schema[i]
394+
if col.Default != nil {
395+
newVal, err := col.Default.Eval(ctx, rowToDefault)
396+
if err != nil {
397+
return err
398+
}
399+
modifiedRow[i] = newVal
400+
}
401+
}
402+
}
403+
err = refActionData.Editor.Update(ctx, rowToDefault, modifiedRow, depth)
404+
if err != nil {
405+
return err
406+
}
407+
}
408+
if err == io.EOF {
409+
return nil
410+
}
411+
return err
412+
}
413+
306414
// OnDeleteSetNull handles the ON DELETE SET NULL referential action.
307415
func (fkEditor *ForeignKeyEditor) OnDeleteSetNull(ctx *sql.Context, refActionData ForeignKeyRefActionData, row sql.Row, depth int) error {
308416
rowIter, err := refActionData.RowMapper.GetIter(ctx, row, false)

sql/planbuilder/ddl.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,9 @@ func (b *Builder) buildAlterConstraint(inScope *scope, ddl *ast.DDL, table *plan
715715
c.SchemaName = ds.SchemaName()
716716
}
717717

718+
if err := b.validateOnUpdateOnDeleteRefActions(c); err != nil {
719+
b.handleErr(err)
720+
}
718721
alterFk := plan.NewAlterAddForeignKey(c)
719722
alterFk.DbProvider = b.cat
720723
outScope.node = alterFk
@@ -782,6 +785,9 @@ func (b *Builder) buildConstraintsDefs(inScope *scope, tname ast.TableName, spec
782785
case *sql.ForeignKeyConstraint:
783786
constraint.Database = tname.DbQualifier.String()
784787
constraint.Table = tname.Name.String()
788+
if err := b.validateOnUpdateOnDeleteRefActions(constraint); err != nil {
789+
b.handleErr(err)
790+
}
785791
if constraint.Database == "" {
786792
constraint.Database = b.ctx.GetCurrentDatabase()
787793
}
@@ -1573,6 +1579,40 @@ func (b *Builder) modifySchemaTarget(inScope *scope, n sql.SchemaTarget, sch sql
15731579
return ret
15741580
}
15751581

1582+
// ResolveSchemaDefaults resolves any column default value expressions for the specified |schema|, for the table
1583+
// named |tableName| and returns the schema with the default value expressions resolved. Note that any GetField
1584+
// expressions in the column default value expressions have not had their indexes corrected yet.
1585+
func (b *Builder) ResolveSchemaDefaults(db string, tableName string, schema sql.Schema) sql.Schema {
1586+
tableScope := b.newScope()
1587+
for _, c := range schema {
1588+
tableScope.newColumn(scopeColumn{
1589+
table: strings.ToLower(tableName),
1590+
db: strings.ToLower(db),
1591+
col: strings.ToLower(c.Name),
1592+
originalCol: c.Name,
1593+
typ: c.Type,
1594+
nullable: c.Nullable,
1595+
})
1596+
}
1597+
1598+
return b.resolveSchemaDefaults(tableScope, schema)
1599+
}
1600+
1601+
// validateOnUpdateOnDeleteRefActions validates that the specified |constraint| is using referential actions
1602+
// supported by the current dialect. For example, MySQL parses the syntax for the SET DEFAULT referential action,
1603+
// but doesn't actually support it, so if the MySQL parser is in use, this method will return an error stating
1604+
// that SET DEFAULT is not supported.
1605+
func (b *Builder) validateOnUpdateOnDeleteRefActions(constraint *sql.ForeignKeyConstraint) error {
1606+
if _, ok := b.parser.(*sql.MysqlParser); ok {
1607+
if constraint.OnUpdate == sql.ForeignKeyReferentialAction_SetDefault ||
1608+
constraint.OnDelete == sql.ForeignKeyReferentialAction_SetDefault {
1609+
return sql.ErrForeignKeySetDefault.New()
1610+
}
1611+
}
1612+
1613+
return nil
1614+
}
1615+
15761616
func (b *Builder) resolveSchemaDefaults(inScope *scope, schema sql.Schema) sql.Schema {
15771617
if len(schema) == 0 {
15781618
return nil

sql/rowexec/ddl.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,9 +1184,6 @@ func (b *BaseBuilder) buildAlterTableCollation(ctx *sql.Context, n *plan.AlterTa
11841184
}
11851185

11861186
func (b *BaseBuilder) buildCreateForeignKey(ctx *sql.Context, n *plan.CreateForeignKey, row sql.Row) (sql.RowIter, error) {
1187-
if n.FkDef.OnUpdate == sql.ForeignKeyReferentialAction_SetDefault || n.FkDef.OnDelete == sql.ForeignKeyReferentialAction_SetDefault {
1188-
return nil, sql.ErrForeignKeySetDefault.New()
1189-
}
11901187
db, err := n.DbProvider.Database(ctx, n.FkDef.Database)
11911188
if err != nil {
11921189
return nil, err

0 commit comments

Comments
 (0)