{"record":{"id":"77f86a46da7f822a","repo":"vitessio/vitess","slug":"vt12002","errorCode":"VT12002","errorMessage":"VT12002(sqlparser.String(tblName), fk.Table.String())","messagePattern":"VT12002\\(sqlparser\\.String\\(tblName\\), fk\\.Table\\.String\\(\\)\\)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vtgate/planbuilder/operators/delete.go","lineNumber":351,"sourceCode":"\t\t\ttbl.Table, _ = tbl.Alias.TableName()\n\t\t}\n\t\treturn op, Rewrote(\"change query table point to source table\")\n\t}, func(operator Operator) VisitRule {\n\t\t_, ok := operator.(*QueryGraph)\n\t\treturn VisitRule(ok)\n\t})\n\treturn vTbl\n}\n\nfunc createFkCascadeOpForDelete(ctx *plancontext.PlanningContext, parentOp Operator, delStmt *sqlparser.Delete, childFks []vindexes.ChildFKInfo, deletedTbl *vindexes.BaseTable) Operator {\n\tfkChildren := make([]*FkChild, 0, len(childFks))\n\tvar selectExprs []sqlparser.SelectExpr\n\ttblName := delStmt.Targets[0]\n\tfor _, fk := range childFks {\n\t\t// Any RESTRICT type foreign keys that arrive here,\n\t\t// are cross-shard/cross-keyspace RESTRICT cases, which we don't currently support.\n\t\tif fk.OnDelete.IsRestrict() {\n\t\t\tpanic(vterrors.VT12002(sqlparser.String(tblName), fk.Table.String()))\n\t\t}\n\n\t\t// We need to select all the parent columns for the foreign key constraint, to use in the update of the child table.\n\t\tvar offsets []int\n\t\toffsets, selectExprs = addColumns(ctx, fk.ParentColumns, selectExprs, tblName)\n\n\t\tfkChildren = append(fkChildren,\n\t\t\tcreateFkChildForDelete(ctx, fk, offsets))\n\t}\n\tselectionOp := createSelectionOp(ctx, selectExprs, delStmt.TableExprs, delStmt.Where, nil, nil, getUpdateLock(deletedTbl))\n\n\treturn &FkCascade{\n\t\tSelection: selectionOp,\n\t\tChildren:  fkChildren,\n\t\tParent:    parentOp,\n\t}\n}\n","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtgate/planbuilder/operators/delete.go#L333-L369","documentation":"VT12002 (unsupported: cross-shard foreign key constraint) is raised when a DELETE on a parent table has a child foreign key with ON DELETE RESTRICT that Vitess must emulate itself — specifically the cross-shard/cross-keyspace RESTRICT cases that reach createFkCascadeOpForDelete. Since RESTRICT semantics (reject if children exist) cannot be enforced efficiently across shards in this code path, the planner rejects the delete.","triggerScenarios":"DELETE from a parent table that has a child FK with `ON DELETE RESTRICT` where parent and child are in different keyspaces or the FK is managed by Vitess (managed foreign key mode) rather than pushed down to MySQL.","commonSituations":"Applications with legacy schemas using ON DELETE RESTRICT migrated to sharded Vitess; cross-keyspace FK relationships; enabling `--foreign_key_mode=managed`/`unmanaged` boundaries where RESTRICT cannot be honored sharded.","solutions":["Change the FK to ON DELETE CASCADE or ON DELETE SET NULL if business logic allows, so Vitess can emulate it.","Enforce the delete guard in application logic: check for and delete/NULL child rows before deleting the parent.","Co-locate parent and child tables in the same keyspace/shard (via VReplication move or redesign) so MySQL can enforce RESTRICT natively.","Set foreign_key_mode appropriately (e.g. `disallow` to reject cross-shard FK DDL up front) so the situation is surfaced during migration rather than at delete time."],"exampleFix":"// before\nCREATE TABLE child (id INT, parent_id INT, FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE RESTRICT);\n\n// after\nCREATE TABLE child (id INT, parent_id INT, FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE);","handlingStrategy":"validation","validationCode":"-- detect unsupported RESTRICT child FKs on a parent table before issuing DELETE\nSELECT child.table_name AS child_table\nFROM information_schema.referential_constraints rc\nJOIN information_schema.key_column_usage kcu ON kcu.constraint_name = rc.constraint_name\nWHERE rc.unique_constraint_schema = ? AND rc.referenced_table_name = ?\n  AND rc.delete_rule = 'RESTRICT';","typeGuard":"func fkDeleteIsSupported(fk vindexes.ChildFKInfo) bool {\n    return !fk.OnDelete.IsRestrict()\n}","tryCatchPattern":"if err := execDelete(sql); err != nil {\n    if strings.Contains(err.Error(), \"VT12002\") {\n        return fmt.Errorf(\"cross-shard RESTRICT FK blocks this DELETE; delete child rows first or use CASCADE: %w\", err)\n    }\n    return err\n}","preventionTips":["Prefer ON DELETE CASCADE or SET NULL in sharded schemas","Delete child rows explicitly before parent rows when RESTRICT is required","Avoid cross-keyspace foreign keys in sharded deployments","Choose the right --foreign_key_mode at deployment and enforce it in schema review"],"tags":["vitess","planbuilder","delete","vt12002","foreign-key","cross-shard"],"backgroundTag":"unsupported-cross-shard-foreign-key","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}