{"record":{"id":"584073738fbc8c8e","repo":"vitessio/vitess","slug":"vt12002-584073","errorCode":"VT12002","errorMessage":"unsupported: cross-shard foreign keys between table '%s' and '%s'","messagePattern":"unsupported: cross-shard foreign keys between table '(.+?)' and '(.+?)'","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vtgate/planbuilder/operators/update.go","lineNumber":942,"sourceCode":"\tif len(vTbl.UniqueKeys) > 0 {\n\t\treturn sqlparser.ForUpdateLockNoWait\n\t}\n\treturn sqlparser.ForUpdateLock\n}\n\n// Each child foreign key constraint is verified by a join query of the form:\n// select 1 from child_tbl join parent_tbl on <columns in fk> where <clause same as original update> [AND ({<bind variables in the SET clause of the original update> IS NULL OR}... <child_columns_in_fk> NOT IN (<bind variables in the SET clause of the original update>))] limit 1\n// E.g:\n// Child (c1, c2) references Parent (p1, p2)\n// update Parent set p1 = col + 1 where id = 1\n// verify query:\n// select 1 from Child join Parent on Parent.p1 = Child.c1 and Parent.p2 = Child.c2\n// where Parent.id = 1 and ((Parent.col + 1) IS NULL OR (child.c1) NOT IN ((Parent.col + 1))) limit 1\nfunc createFkVerifyOpForChildFKForUpdate(ctx *plancontext.PlanningContext, updatedTable *vindexes.BaseTable, updStmt *sqlparser.Update, cFk vindexes.ChildFKInfo) Operator {\n\t// ON UPDATE RESTRICT foreign keys that require validation, should only be allowed in the case where we\n\t// are verifying all the FKs on vtgate level.\n\tif !ctx.VerifyAllFKs {\n\t\tpanic(vterrors.VT12002(updatedTable.String(), cFk.Table.String()))\n\t}\n\n\tparentTblExpr := sqlparser.NewAliasedTableExpr(updatedTable.GetTableName(), \"parent\")\n\tparentTbl, err := parentTblExpr.TableName()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Alias the foreign key's child table name\n\tchildTblExpr := sqlparser.NewAliasedTableExpr(cFk.Table.GetTableName(), \"child\")\n\tchildTbl, err := childTblExpr.TableName()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tvar joinCond sqlparser.Expr\n\tfor idx := range cFk.ParentColumns {\n\t\tjoinExpr := &sqlparser.ComparisonExpr{","sourceCodeStart":924,"sourceCodeEnd":960,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtgate/planbuilder/operators/update.go#L924-L960","documentation":"Vitess cannot plan an UPDATE to a parent table that participates in a cross-shard ON UPDATE RESTRICT foreign key unless vtgate is configured to verify all foreign keys at the VTGate level (ctx.VerifyAllFKs). When only some FKs are verified locally, a cross-shard RESTRICT check cannot be done safely, so planning panics with VT12002 naming the parent and child tables.","triggerScenarios":"Running an UPDATE that modifies columns of a parent table referenced by a RESTRICT foreign key whose parent and child live on different shards, while --verify_all_fks (VerifyAllFKs) is disabled or FK verification mode is not set to VTGate-level verification.","commonSituations":"Clusters with foreign keys enabled (foreign_key_mode=unmanaged is off) but verify_all_fks=false; sharded keyspaces where a RESTRICT FK spans shards; operators upgrading schemas to add FKs without changing vtgate FK settings.","solutions":["Enable VTGate-level FK verification by starting vtgate with --verify_all_fks=true","Set --foreign_key_mode to managed (or check the FK is not handled only at MySQL level) so cross-shard RESTRICT updates are supported","Move the parent and child tables into the same shard/keyspace, or change the FK to not require ON UPDATE RESTRICT validation","Rewrite the application to avoid updating the parent's referenced columns (update children first, or use surrogate keys)"],"exampleFix":"// before: vtgate started without FK verification\nvtgate --foreign_key_mode=managed\n// after\nvtgate --foreign_key_mode=managed --verify_all_fks=true","handlingStrategy":"validation","validationCode":"// Before issuing: parent-table UPDATE touching FK parent columns on a sharded keyspace\n// ensure vtgate flags: --foreign_key_mode=managed --verify_all_fks=true\n// App-side guard:\nif updatingParentFKColumns(update) && clusterIsSharded {\n    requireVTGateFlag(t, \"verify_all_fks\", true)\n}","typeGuard":null,"tryCatchPattern":"// Vitess returns VT12002 as a gRPC error\nif verr, ok := err.(vtgo.Err); ok && verr.Code == 12002 {\n    // fall back to explicit select-then-update flow\n}","preventionTips":["Run vtgate with --verify_all_fks=true whenever managed FKs are used on sharded keyspaces","Keep parent and child tables of a RESTRICT FK in the same keyspace","Avoid UPDATEs to FK-referenced parent columns; use delete/insert with children handled first","Document FK topology for the team before enabling foreign_key_mode"],"tags":["vitess","vtgate","foreign-keys","sharding","planbuilder"],"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"}