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

  1. Change the foreign key definition to a supported action, e.g. ON DELETE CASCADE or ON DELETE SET NULL, then apply the migration
  2. Drop the FK constraint's cascade action (ON DELETE RESTRICT/NO ACTION) and perform the child-table updates explicitly in the same transaction
  3. 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

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.