Skip to content

Commit 12996bd

Browse files
committed
Add support for the foreign key referential action: SET DEFAULT
This is not supported by MySQL, but needed for Postgres support in Doltgres.
1 parent 6abfb25 commit 12996bd

File tree

12 files changed

+211
-26
lines changed

12 files changed

+211
-26
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: 41 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,11 @@ func getForeignKeyRefActions(ctx *sql.Context, a *Analyzer, tbl sql.ForeignKeyTa
380382
if err != nil {
381383
return nil, err
382384
}
383-
childTblSch := childTbl.Schema()
385+
childTblSch, err := resolveSchemaDefaults(ctx, a.Catalog, childTbl)
386+
if err != nil {
387+
return nil, err
388+
}
389+
384390
childParentMapping, err := plan.GetChildParentMapping(tblSch, childTblSch, fk)
385391
if err != nil {
386392
return nil, err
@@ -429,6 +435,40 @@ func getForeignKeyRefActions(ctx *sql.Context, a *Analyzer, tbl sql.ForeignKeyTa
429435
return fkEditor, nil
430436
}
431437

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

sql/analyzer/rule_ids.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,18 @@ const (
6868
interpreterId // interpreter
6969

7070
// validate
71-
validateResolvedId // validateResolved
72-
validateOrderById // validateOrderBy
73-
validateGroupById // validateGroupBy
74-
validateSchemaSourceId // validateSchemaSource
75-
validateIndexCreationId // validateIndexCreation
76-
ValidateOperandsId // validateOperands
77-
validateIntervalUsageId // validateIntervalUsage
78-
validateSubqueryColumnsId // validateSubqueryColumns
79-
validateUnionSchemasMatchId // validateUnionSchemasMatch
80-
validateAggregationsId // validateAggregations
81-
validateDeleteFromId // validateDeleteFrom
71+
validateResolvedId // validateResolved
72+
validateOrderById // validateOrderBy
73+
validateGroupById // validateGroupBy
74+
validateSchemaSourceId // validateSchemaSource
75+
validateIndexCreationId // validateIndexCreation
76+
ValidateOperandsId // validateOperands
77+
validateIntervalUsageId // validateIntervalUsage
78+
validateSubqueryColumnsId // validateSubqueryColumns
79+
validateUnionSchemasMatchId // validateUnionSchemasMatch
80+
validateAggregationsId // validateAggregations
81+
validateDeleteFromId // validateDeleteFrom
82+
ValidateForeignKeyReferentialActionsId // validateForeignKeyReferentialActions
8283

8384
// after all
8485
cacheSubqueryAliasesInJoinsId // cacheSubqueryAliasesInJoins

sql/analyzer/ruleid_string.go

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sql/analyzer/rules.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ var DefaultValidationRules = []Rule{
107107
{validateSubqueryColumnsId, validateSubqueryColumns},
108108
{validateUnionSchemasMatchId, validateUnionSchemasMatch},
109109
{validateAggregationsId, validateAggregations},
110+
{ValidateForeignKeyReferentialActionsId, validateForeignKeyReferentialActions},
110111
}
111112

112113
var OnceAfterAll []Rule

sql/analyzer/validation_rules.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,27 @@ func validateOffsetAndLimit(ctx *sql.Context, a *Analyzer, n sql.Node, scope *pl
9595
return n, transform.SameTree, err
9696
}
9797

98+
// validateForeignKeyReferentialActions checks |n| for foreign keys being created and validates that
99+
// their referential actions are valid for MySQL. This currently only checks for use of SET DEFAULT,
100+
// which MySQL supports in SQL syntax, but does not actually support that referential action and will
101+
// always throw an error if it is specified.
102+
func validateForeignKeyReferentialActions(ctx *sql.Context, _ *Analyzer, n sql.Node, _ *plan.Scope, _ RuleSelector, _ *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
103+
span, ctx := ctx.Span("validate_fk_referential_actions")
104+
defer span.End()
105+
106+
return transform.Node(n, func(n sql.Node) (sql.Node, transform.TreeIdentity, error) {
107+
switch n := n.(type) {
108+
case *plan.CreateForeignKey:
109+
if n.FkDef.OnUpdate == sql.ForeignKeyReferentialAction_SetDefault ||
110+
n.FkDef.OnDelete == sql.ForeignKeyReferentialAction_SetDefault {
111+
return n, transform.SameTree, sql.ErrForeignKeySetDefault.New()
112+
}
113+
}
114+
115+
return n, transform.SameTree, nil
116+
})
117+
}
118+
98119
func validateResolved(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scope, sel RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
99120
span, ctx := ctx.Span("validate_is_resolved")
100121
defer span.End()

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)

0 commit comments

Comments
 (0)