vitessio/vitess · error
VT09016
VT09016
Error message
VT09016
What it means
VT09016 is thrown when an ON DELETE foreign-key action of SET DEFAULT is encountered while building the cascade plan for a DELETE in VTGate. Vitess's planner does not support cascading a delete to child rows by setting their FK columns to their default values, so it panics with this unsupported-feature error during plan construction in createFkChildForDelete.
Source
Thrown at go/vt/vtgate/planbuilder/operators/delete.go:408
// The query looks something like this - `UPDATE <child_table> SET <child_column_in_fk> = NULL [AND <another_child_column_in_fk> = NULL]... WHERE <child_columns_in_fk> IN (<bind variable for the output from SELECT>)`
valTuple := make(sqlparser.ValTuple, 0, len(fk.ChildColumns))
updExprs := make(sqlparser.UpdateExprs, 0, len(fk.ChildColumns))
for _, column := range fk.ChildColumns {
valTuple = append(valTuple, sqlparser.NewColName(column.String()))
updExprs = append(updExprs, &sqlparser.UpdateExpr{
Name: sqlparser.NewColName(column.String()),
Expr: &sqlparser.NullVal{},
})
}
compExpr := sqlparser.NewComparisonExpr(sqlparser.InOp, valTuple, sqlparser.NewListArg(bvName), nil)
childStmt = &sqlparser.Update{
Exprs: updExprs,
Comments: parsedComments,
TableExprs: []sqlparser.TableExpr{sqlparser.NewAliasedTableExpr(fk.Table.GetTableName(), "")},
Where: &sqlparser.Where{Type: sqlparser.WhereClause, Expr: compExpr},
}
case sqlparser.SetDefault:
panic(vterrors.VT09016())
}
// For the child statement of a DELETE query, we don't need to verify all the FKs on VTgate or ignore any foreign key explicitly.
childOp := createOpFromStmt(ctx, childStmt, false /* verifyAllFKs */, "" /* fkToIgnore */)
return &FkChild{
BVName: bvName,
Cols: cols,
Op: childOp,
}
}
View on GitHub (pinned to 01a25a7d17)
Solutions
- Change the foreign key definition to a supported action, e.g. ON DELETE CASCADE or ON DELETE SET NULL, then apply the migration
- Drop the FK constraint's cascade action (ON DELETE RESTRICT/NO ACTION) and perform the child-table updates explicitly in the same transaction
- If the default is never relied upon, use ON DELETE CASCADE instead so child rows are removed rather than defaulted
Example fix
// before CREATE TABLE child (id INT PRIMARY KEY, parent_id INT DEFAULT 0, FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE SET DEFAULT); // after CREATE TABLE child (id INT PRIMARY KEY, parent_id INT DEFAULT 0, FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE);
Defensive patterns
Strategy: validation
Validate before calling
-- before enabling cascading deletes, audit schema SELECT TABLE_NAME, CONSTRAINT_NAME, DELETE_RULE FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE DELETE_RULE = 'SET DEFAULT';
Prevention
- Ban ON DELETE SET DEFAULT in schema review/migration linting
- Add an information_schema check to CI migration tests for unsupported FK actions
- Prefer ON DELETE CASCADE or explicit application-level child updates
When it happens
Trigger: A DELETE statement targets a parent table that has a foreign key declared with ON DELETE SET DEFAULT, and FK cascade mode is enabled so planbuilder calls createFkCascadeOpForDelete, which reaches the sqlparser.SetDefault case in createFkChildForDelete.
Common situations: Schemas migrated from a plain MySQL deployment where ON DELETE SET DEFAULT was valid; ORMs or migration tools generating SET DEFAULT FK actions; users enabling foreign_key_mode/cascading DML in Vitess against such a schema.
Related errors
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/c497bd86e9636990.
Report an issue: GitHub.