From 462ebbd278a07c20d3587f12037530d0f5af4b36 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Fri, 29 Aug 2025 10:26:04 +0000 Subject: [PATCH 1/7] Introduce aliases when handling self-referencing foreign keys. Signed-off-by: Arthur Schreiber --- go/vt/vtgate/planbuilder/operators/update.go | 28 +++++++++++++- go/vt/vtgate/planbuilder/plan_test.go | 5 ++- .../testdata/foreignkey_cases.json | 38 +++++++++++++++++++ .../planbuilder/testdata/vschemas/schema.json | 1 + 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/go/vt/vtgate/planbuilder/operators/update.go b/go/vt/vtgate/planbuilder/operators/update.go index 85a44bafcca..40d79250a15 100644 --- a/go/vt/vtgate/planbuilder/operators/update.go +++ b/go/vt/vtgate/planbuilder/operators/update.go @@ -817,7 +817,31 @@ func createFkVerifyOpForParentFKForUpdate(ctx *plancontext.PlanningContext, upda if err != nil { panic(err) } - parentTbl := pFK.Table.GetTableName() + + var parentTblAlias *sqlparser.AliasedTableExpr + var parentTbl sqlparser.TableName + + origParentTable := pFK.Table.GetTableName() + if sqlparser.Equals.IdentifierCS(childTbl.Name, origParentTable.Name) { + // Is this a self-referential foreign key? If yes, we need to introduce aliases + // so the table names don't clash. We add both child and parent aliases potentially + // using the same name for the parent alias as the child table. + parentTblAlias = sqlparser.NewAliasedTableExpr(pFK.Table.GetTableName(), "parent") + parentTbl, err = parentTblAlias.TableName() + if err != nil { + panic(err) + } + + childTblExpr = sqlparser.NewAliasedTableExpr(childTbl, "child") + childTbl, err = childTblExpr.TableName() + if err != nil { + panic(err) + } + } else { + parentTblAlias = sqlparser.NewAliasedTableExpr(pFK.Table.GetTableName(), "") + parentTbl = origParentTable + } + var whereCond sqlparser.Expr var joinCond sqlparser.Expr var notEqualColNames sqlparser.ValTuple @@ -894,7 +918,7 @@ func createFkVerifyOpForParentFKForUpdate(ctx *plancontext.PlanningContext, upda sqlparser.NewJoinTableExpr( childTblExpr, sqlparser.LeftJoinType, - sqlparser.NewAliasedTableExpr(parentTbl, ""), + parentTblAlias, sqlparser.NewJoinCondition(joinCond, nil)), }, sqlparser.NewWhere(sqlparser.WhereClause, whereCond), diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go index fcdd8b5498e..ee06c5712b7 100644 --- a/go/vt/vtgate/planbuilder/plan_test.go +++ b/go/vt/vtgate/planbuilder/plan_test.go @@ -236,7 +236,10 @@ func (s *planTestSuite) setFks(vschema *vindexes.VSchema) { _ = vschema.AddUniqueKey("unsharded_fk_allow", "u_tbl9", []sqlparser.Expr{sqlparser.NewColName("bar"), sqlparser.NewColName("col9")}) _ = vschema.AddUniqueKey("unsharded_fk_allow", "u_tbl8", []sqlparser.Expr{sqlparser.NewColName("col8")}) - s.addPKs(vschema, "unsharded_fk_allow", []string{"u_tbl1", "u_tbl2", "u_tbl3", "u_tbl4", "u_tbl5", "u_tbl6", "u_tbl7", "u_tbl8", "u_tbl9", "u_tbl10", "u_tbl11", + // FK from u_tbl12 that is self-referential. + _ = vschema.AddForeignKey("unsharded_fk_allow", "u_tbl12", createFkDefinition([]string{"parent_id"}, "u_tbl12", []string{"id"}, sqlparser.Restrict, sqlparser.Restrict)) + + s.addPKs(vschema, "unsharded_fk_allow", []string{"u_tbl1", "u_tbl2", "u_tbl3", "u_tbl4", "u_tbl5", "u_tbl6", "u_tbl7", "u_tbl8", "u_tbl9", "u_tbl10", "u_tbl11", "u_tbl12", "u_multicol_tbl1", "u_multicol_tbl2", "u_multicol_tbl3"}) } } diff --git a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json index c8d8942fcc0..c1cc32ddde6 100644 --- a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json @@ -2968,6 +2968,44 @@ ] } }, + { + "comment": "Self-referential foreign key", + "query": "UPDATE u_tbl12 SET parent_id = CASE WHEN (parent_id = 1) THEN NULL ELSE NULL END WHERE id IN (1)", + "plan": { + "Instructions": { + "Inputs": [ + { + "FieldQuery": "select 1 from u_tbl12 as child left join u_tbl12 as parent on parent.id = case when child.parent_id = 1 then null else null end where 1 != 1", + "InputName": "VerifyParent-1", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "OperatorType": "Route", + "Query": "select 1 from u_tbl12 as child left join u_tbl12 as parent on parent.id = case when child.parent_id = 1 then null else null end where parent.id is null and case when child.parent_id = 1 then null else null end is not null and not (child.parent_id) <=> (case when child.parent_id = 1 then null else null end) and child.id in (1) limit 1 for share", + "Variant": "Unsharded" + }, + { + "InputName": "PostVerify", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "OperatorType": "Update", + "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl12 set parent_id = case when parent_id = 1 then null else null end where id in (1)", + "Variant": "Unsharded" + } + ], + "OperatorType": "FKVerify" + }, + "Original": "UPDATE u_tbl12 SET parent_id = CASE WHEN (parent_id = 1) THEN NULL ELSE NULL END WHERE id IN (1)", + "QueryType": "UPDATE", + "TablesUsed": [ + "unsharded_fk_allow.u_tbl12" + ], + "Type": "ForeignKey" + } + }, { "comment": "Multi table delete with using", "query": "delete u_tbl10 from u_tbl10 join u_tbl11 using (id) where id = 5", diff --git a/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json b/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json index aaa11727510..47ced967ae3 100644 --- a/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json +++ b/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json @@ -1014,6 +1014,7 @@ ], "column_list_authoritative": true }, + "u_tbl12": {}, "u_tbl": {}, "u_multicol_tbl1": {}, "u_multicol_tbl2": {}, From 9a2c20d24478228a929c3af9137b075ee3084f50 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Sat, 30 Aug 2025 13:48:35 +0000 Subject: [PATCH 2/7] Simplify how we check for self-referencing foreign key. Signed-off-by: Arthur Schreiber --- go/vt/vtgate/planbuilder/operators/update.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/go/vt/vtgate/planbuilder/operators/update.go b/go/vt/vtgate/planbuilder/operators/update.go index 40d79250a15..883059adff9 100644 --- a/go/vt/vtgate/planbuilder/operators/update.go +++ b/go/vt/vtgate/planbuilder/operators/update.go @@ -821,8 +821,7 @@ func createFkVerifyOpForParentFKForUpdate(ctx *plancontext.PlanningContext, upda var parentTblAlias *sqlparser.AliasedTableExpr var parentTbl sqlparser.TableName - origParentTable := pFK.Table.GetTableName() - if sqlparser.Equals.IdentifierCS(childTbl.Name, origParentTable.Name) { + if pFK.Table == updatedTable { // Is this a self-referential foreign key? If yes, we need to introduce aliases // so the table names don't clash. We add both child and parent aliases potentially // using the same name for the parent alias as the child table. @@ -839,7 +838,7 @@ func createFkVerifyOpForParentFKForUpdate(ctx *plancontext.PlanningContext, upda } } else { parentTblAlias = sqlparser.NewAliasedTableExpr(pFK.Table.GetTableName(), "") - parentTbl = origParentTable + parentTbl = pFK.Table.GetTableName() } var whereCond sqlparser.Expr From 5aba4ed824170e61239afb4ea0fba8091a69892d Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Mon, 1 Sep 2025 07:34:03 +0000 Subject: [PATCH 3/7] Tweak variable naming. Signed-off-by: Arthur Schreiber --- go/vt/vtgate/planbuilder/operators/update.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/go/vt/vtgate/planbuilder/operators/update.go b/go/vt/vtgate/planbuilder/operators/update.go index 883059adff9..d5910bb368d 100644 --- a/go/vt/vtgate/planbuilder/operators/update.go +++ b/go/vt/vtgate/planbuilder/operators/update.go @@ -818,15 +818,15 @@ func createFkVerifyOpForParentFKForUpdate(ctx *plancontext.PlanningContext, upda panic(err) } - var parentTblAlias *sqlparser.AliasedTableExpr + var parentTblExpr *sqlparser.AliasedTableExpr var parentTbl sqlparser.TableName if pFK.Table == updatedTable { // Is this a self-referential foreign key? If yes, we need to introduce aliases // so the table names don't clash. We add both child and parent aliases potentially // using the same name for the parent alias as the child table. - parentTblAlias = sqlparser.NewAliasedTableExpr(pFK.Table.GetTableName(), "parent") - parentTbl, err = parentTblAlias.TableName() + parentTblExpr = sqlparser.NewAliasedTableExpr(pFK.Table.GetTableName(), "parent") + parentTbl, err = parentTblExpr.TableName() if err != nil { panic(err) } @@ -837,7 +837,7 @@ func createFkVerifyOpForParentFKForUpdate(ctx *plancontext.PlanningContext, upda panic(err) } } else { - parentTblAlias = sqlparser.NewAliasedTableExpr(pFK.Table.GetTableName(), "") + parentTblExpr = sqlparser.NewAliasedTableExpr(pFK.Table.GetTableName(), "") parentTbl = pFK.Table.GetTableName() } @@ -917,7 +917,7 @@ func createFkVerifyOpForParentFKForUpdate(ctx *plancontext.PlanningContext, upda sqlparser.NewJoinTableExpr( childTblExpr, sqlparser.LeftJoinType, - parentTblAlias, + parentTblExpr, sqlparser.NewJoinCondition(joinCond, nil)), }, sqlparser.NewWhere(sqlparser.WhereClause, whereCond), From 5149e6d11c4b2876309e8cd8c5f173a50f19fe4f Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Mon, 1 Sep 2025 11:29:39 +0000 Subject: [PATCH 4/7] Fix self-referential foreign keys that specify a custom alias. Signed-off-by: Arthur Schreiber --- go/vt/vtgate/planbuilder/operators/update.go | 12 +++--- .../testdata/foreignkey_cases.json | 40 ++++++++++++++++++- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/go/vt/vtgate/planbuilder/operators/update.go b/go/vt/vtgate/planbuilder/operators/update.go index d5910bb368d..98bb63bf618 100644 --- a/go/vt/vtgate/planbuilder/operators/update.go +++ b/go/vt/vtgate/planbuilder/operators/update.go @@ -821,17 +821,19 @@ func createFkVerifyOpForParentFKForUpdate(ctx *plancontext.PlanningContext, upda var parentTblExpr *sqlparser.AliasedTableExpr var parentTbl sqlparser.TableName - if pFK.Table == updatedTable { - // Is this a self-referential foreign key? If yes, we need to introduce aliases - // so the table names don't clash. We add both child and parent aliases potentially - // using the same name for the parent alias as the child table. + // If the table name (or table alias if one is given) matches + // the parent foreign key's table name, we need to introduce aliases + // to make sure there's no ambiguity. + if pFK.Table.Name == childTbl.Name { + // Alias the foreign key's parent table name parentTblExpr = sqlparser.NewAliasedTableExpr(pFK.Table.GetTableName(), "parent") parentTbl, err = parentTblExpr.TableName() if err != nil { panic(err) } - childTblExpr = sqlparser.NewAliasedTableExpr(childTbl, "child") + // Alias the foreign key's child table name + childTblExpr = sqlparser.NewAliasedTableExpr(childTblExpr.Expr.(sqlparser.TableName), "child") childTbl, err = childTblExpr.TableName() if err != nil { panic(err) diff --git a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json index c1cc32ddde6..cb9a9afeeda 100644 --- a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json @@ -2969,7 +2969,7 @@ } }, { - "comment": "Self-referential foreign key", + "comment": "Self-referential foreign key without table alias introduces new aliases", "query": "UPDATE u_tbl12 SET parent_id = CASE WHEN (parent_id = 1) THEN NULL ELSE NULL END WHERE id IN (1)", "plan": { "Instructions": { @@ -3006,6 +3006,44 @@ "Type": "ForeignKey" } }, + { + "comment": "Self-referential foreign key with table alias does not introduce new aliases", + "query": "UPDATE u_tbl12 as foobar SET parent_id = CASE WHEN (parent_id = 1) THEN NULL ELSE NULL END WHERE id IN (1)", + "plan": { + "Instructions": { + "Inputs": [ + { + "FieldQuery": "select 1 from u_tbl12 as foobar left join u_tbl12 on u_tbl12.id = case when foobar.parent_id = 1 then null else null end where 1 != 1", + "InputName": "VerifyParent-1", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "OperatorType": "Route", + "Query": "select 1 from u_tbl12 as foobar left join u_tbl12 on u_tbl12.id = case when foobar.parent_id = 1 then null else null end where u_tbl12.id is null and case when foobar.parent_id = 1 then null else null end is not null and not (foobar.parent_id) <=> (case when foobar.parent_id = 1 then null else null end) and foobar.id in (1) limit 1 for share", + "Variant": "Unsharded" + }, + { + "InputName": "PostVerify", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "OperatorType": "Update", + "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl12 as foobar set parent_id = case when parent_id = 1 then null else null end where id in (1)", + "Variant": "Unsharded" + } + ], + "OperatorType": "FKVerify" + }, + "Original": "UPDATE u_tbl12 as foobar SET parent_id = CASE WHEN (parent_id = 1) THEN NULL ELSE NULL END WHERE id IN (1)", + "QueryType": "UPDATE", + "TablesUsed": [ + "unsharded_fk_allow.u_tbl12" + ], + "Type": "ForeignKey" + } + }, { "comment": "Multi table delete with using", "query": "delete u_tbl10 from u_tbl10 join u_tbl11 using (id) where id = 5", From 6e26ba5b6b7b2799a53c8a6c61088f12e2081a35 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Mon, 1 Sep 2025 11:53:56 +0000 Subject: [PATCH 5/7] Fix updating the child side column of a self referential foreign key. Signed-off-by: Arthur Schreiber --- go/vt/vtgate/planbuilder/operators/update.go | 34 ++++++++-- go/vt/vtgate/planbuilder/plan_test.go | 21 ++++--- .../testdata/foreignkey_cases.json | 62 +++++++++++++++---- 3 files changed, 89 insertions(+), 28 deletions(-) diff --git a/go/vt/vtgate/planbuilder/operators/update.go b/go/vt/vtgate/planbuilder/operators/update.go index 98bb63bf618..92f99465570 100644 --- a/go/vt/vtgate/planbuilder/operators/update.go +++ b/go/vt/vtgate/planbuilder/operators/update.go @@ -731,7 +731,7 @@ func buildChildUpdOpForSetNull( updateExprs := ctx.SemTable.GetUpdateExpressionsForFk(fk.String(updatedTable)) compExpr := nullSafeNotInComparison(ctx, updatedTable, - updateExprs, fk, updatedTable.GetTableName(), nonLiteralUpdateInfo, false /* appendQualifier */) + updateExprs, fk, updatedTable.GetTableName(), fk.Table.GetTableName(), nonLiteralUpdateInfo, false /* appendQualifier */) if compExpr != nil { childWhereExpr = &sqlparser.AndExpr{ Left: childWhereExpr, @@ -956,12 +956,34 @@ func createFkVerifyOpForChildFKForUpdate(ctx *plancontext.PlanningContext, updat if !ctx.VerifyAllFKs { panic(vterrors.VT12002(updatedTable.String(), cFk.Table.String())) } + parentTblExpr := updStmt.TableExprs[0].(*sqlparser.AliasedTableExpr) parentTbl, err := parentTblExpr.TableName() if err != nil { panic(err) } - childTbl := cFk.Table.GetTableName() + + var childTblExpr *sqlparser.AliasedTableExpr + var childTbl sqlparser.TableName + if cFk.Table.Name == parentTbl.Name { + + parentTblExpr = sqlparser.NewAliasedTableExpr(parentTblExpr.Expr.(sqlparser.TableName), "parent") + parentTbl, err = parentTblExpr.TableName() + if err != nil { + panic(err) + } + + // Alias the foreign key's child table name + childTblExpr = sqlparser.NewAliasedTableExpr(cFk.Table.GetTableName(), "child") + childTbl, err = childTblExpr.TableName() + if err != nil { + panic(err) + } + } else { + childTbl = cFk.Table.GetTableName() + childTblExpr = sqlparser.NewAliasedTableExpr(childTbl, "") + } + var joinCond sqlparser.Expr for idx := range cFk.ParentColumns { joinExpr := &sqlparser.ComparisonExpr{ @@ -992,7 +1014,7 @@ func createFkVerifyOpForChildFKForUpdate(ctx *plancontext.PlanningContext, updat // For example, if we are setting `update child cola = :v1 and colb = :v2`, then on the parent, the where condition would look something like this - // `:v1 IS NULL OR :v2 IS NULL OR (cola, colb) NOT IN ((:v1,:v2))` // So, if either of :v1 or :v2 is NULL, then the entire condition is true (which is the same as not having the condition when :v1 or :v2 is NULL). - compExpr := nullSafeNotInComparison(ctx, updatedTable, updStmt.Exprs, cFk, parentTbl, nil /* nonLiteralUpdateInfo */, true /* appendQualifier */) + compExpr := nullSafeNotInComparison(ctx, updatedTable, updStmt.Exprs, cFk, parentTbl, childTbl, nil /* nonLiteralUpdateInfo */, true /* appendQualifier */) if compExpr != nil { whereCond = sqlparser.AndExpressions(whereCond, compExpr) } @@ -1003,7 +1025,7 @@ func createFkVerifyOpForChildFKForUpdate(ctx *plancontext.PlanningContext, updat sqlparser.NewJoinTableExpr( parentTblExpr, sqlparser.NormalJoinType, - sqlparser.NewAliasedTableExpr(childTbl, ""), + childTblExpr, sqlparser.NewJoinCondition(joinCond, nil)), }, sqlparser.NewWhere(sqlparser.WhereClause, whereCond), @@ -1017,7 +1039,7 @@ func createFkVerifyOpForChildFKForUpdate(ctx *plancontext.PlanningContext, updat // `:v1 IS NULL OR :v2 IS NULL OR (cola, colb) NOT IN ((:v1,:v2))` // So, if either of :v1 or :v2 is NULL, then the entire condition is true (which is the same as not having the condition when :v1 or :v2 is NULL) // This expression is used in cascading SET NULLs and in verifying whether an update should be restricted. -func nullSafeNotInComparison(ctx *plancontext.PlanningContext, updatedTable *vindexes.BaseTable, updateExprs sqlparser.UpdateExprs, cFk vindexes.ChildFKInfo, parentTbl sqlparser.TableName, nonLiteralUpdateInfo []engine.NonLiteralUpdateInfo, appendQualifier bool) sqlparser.Expr { +func nullSafeNotInComparison(ctx *plancontext.PlanningContext, updatedTable *vindexes.BaseTable, updateExprs sqlparser.UpdateExprs, cFk vindexes.ChildFKInfo, parentTbl, childTbl sqlparser.TableName, nonLiteralUpdateInfo []engine.NonLiteralUpdateInfo, appendQualifier bool) sqlparser.Expr { var valTuple sqlparser.ValTuple var updateValues sqlparser.ValTuple for idx, updateExpr := range updateExprs { @@ -1032,7 +1054,7 @@ func nullSafeNotInComparison(ctx *plancontext.PlanningContext, updatedTable *vin } updateValues = append(updateValues, childUpdateExpr) if appendQualifier { - valTuple = append(valTuple, sqlparser.NewColNameWithQualifier(cFk.ChildColumns[colIdx].String(), cFk.Table.GetTableName())) + valTuple = append(valTuple, sqlparser.NewColNameWithQualifier(cFk.ChildColumns[colIdx].String(), childTbl)) } else { valTuple = append(valTuple, sqlparser.NewColName(cFk.ChildColumns[colIdx].String())) } diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go index ee06c5712b7..142d9ba0e64 100644 --- a/go/vt/vtgate/planbuilder/plan_test.go +++ b/go/vt/vtgate/planbuilder/plan_test.go @@ -200,16 +200,17 @@ func (s *planTestSuite) setFks(vschema *vindexes.VSchema) { "multicol_tbl1", "multicol_tbl2", "tbl_auth", "tblrefDef", "tbl20"}) } if vschema.Keyspaces["unsharded_fk_allow"] != nil { - // u_tbl2(col2) -> u_tbl1(col1) Cascade. - // u_tbl4(col41) -> u_tbl1(col14) Restrict. - // u_tbl9(col9) -> u_tbl1(col1) Cascade Null. - // u_tbl3(col2) -> u_tbl2(col2) Cascade Null. - // u_tbl4(col4) -> u_tbl3(col3) Restrict. - // u_tbl6(col6) -> u_tbl5(col5) Restrict. - // u_tbl8(col8) -> u_tbl9(col9) Null Null. - // u_tbl8(col8) -> u_tbl6(col6) Cascade Null. - // u_tbl4(col4) -> u_tbl7(col7) Cascade Cascade. - // u_tbl9(col9) -> u_tbl4(col4) Restrict Restrict. + // u_tbl2(col2) -> u_tbl1(col1) Cascade. + // u_tbl4(col41) -> u_tbl1(col14) Restrict. + // u_tbl9(col9) -> u_tbl1(col1) Cascade Null. + // u_tbl3(col2) -> u_tbl2(col2) Cascade Null. + // u_tbl4(col4) -> u_tbl3(col3) Restrict. + // u_tbl6(col6) -> u_tbl5(col5) Restrict. + // u_tbl8(col8) -> u_tbl9(col9) Null Null. + // u_tbl8(col8) -> u_tbl6(col6) Cascade Null. + // u_tbl4(col4) -> u_tbl7(col7) Cascade Cascade. + // u_tbl9(col9) -> u_tbl4(col4) Restrict Restrict. + // u_tbl12(parent_id) -> u_tbl12(id) Restrict Restrict. // u_multicol_tbl2(cola, colb) -> u_multicol_tbl1(cola, colb) Null Null. // u_multicol_tbl3(cola, colb) -> u_multicol_tbl2(cola, colb) Cascade Cascade. diff --git a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json index cb9a9afeeda..e1696056e8e 100644 --- a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json @@ -2969,20 +2969,20 @@ } }, { - "comment": "Self-referential foreign key without table alias introduces new aliases", - "query": "UPDATE u_tbl12 SET parent_id = CASE WHEN (parent_id = 1) THEN NULL ELSE NULL END WHERE id IN (1)", + "comment": "Self-referential foreign key update (parent reference column) without table alias introduces new aliases", + "query": "UPDATE u_tbl12 SET parent_id = CASE WHEN (parent_id = 1) THEN 1 ELSE 2 END WHERE id IN (1)", "plan": { "Instructions": { "Inputs": [ { - "FieldQuery": "select 1 from u_tbl12 as child left join u_tbl12 as parent on parent.id = case when child.parent_id = 1 then null else null end where 1 != 1", + "FieldQuery": "select 1 from u_tbl12 as child left join u_tbl12 as parent on parent.id = case when child.parent_id = 1 then 1 else 2 end where 1 != 1", "InputName": "VerifyParent-1", "Keyspace": { "Name": "unsharded_fk_allow", "Sharded": false }, "OperatorType": "Route", - "Query": "select 1 from u_tbl12 as child left join u_tbl12 as parent on parent.id = case when child.parent_id = 1 then null else null end where parent.id is null and case when child.parent_id = 1 then null else null end is not null and not (child.parent_id) <=> (case when child.parent_id = 1 then null else null end) and child.id in (1) limit 1 for share", + "Query": "select 1 from u_tbl12 as child left join u_tbl12 as parent on parent.id = case when child.parent_id = 1 then 1 else 2 end where parent.id is null and case when child.parent_id = 1 then 1 else 2 end is not null and not (child.parent_id) <=> (case when child.parent_id = 1 then 1 else 2 end) and child.id in (1) limit 1 for share", "Variant": "Unsharded" }, { @@ -2992,13 +2992,13 @@ "Sharded": false }, "OperatorType": "Update", - "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl12 set parent_id = case when parent_id = 1 then null else null end where id in (1)", + "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl12 set parent_id = case when parent_id = 1 then 1 else 2 end where id in (1)", "Variant": "Unsharded" } ], "OperatorType": "FKVerify" }, - "Original": "UPDATE u_tbl12 SET parent_id = CASE WHEN (parent_id = 1) THEN NULL ELSE NULL END WHERE id IN (1)", + "Original": "UPDATE u_tbl12 SET parent_id = CASE WHEN (parent_id = 1) THEN 1 ELSE 2 END WHERE id IN (1)", "QueryType": "UPDATE", "TablesUsed": [ "unsharded_fk_allow.u_tbl12" @@ -3007,20 +3007,20 @@ } }, { - "comment": "Self-referential foreign key with table alias does not introduce new aliases", - "query": "UPDATE u_tbl12 as foobar SET parent_id = CASE WHEN (parent_id = 1) THEN NULL ELSE NULL END WHERE id IN (1)", + "comment": "Self-referential foreign key update (parent reference column) with table alias does not introduce new aliases", + "query": "UPDATE u_tbl12 as foobar SET parent_id = CASE WHEN (parent_id = 1) THEN 1 ELSE 2 END WHERE id IN (1)", "plan": { "Instructions": { "Inputs": [ { - "FieldQuery": "select 1 from u_tbl12 as foobar left join u_tbl12 on u_tbl12.id = case when foobar.parent_id = 1 then null else null end where 1 != 1", + "FieldQuery": "select 1 from u_tbl12 as foobar left join u_tbl12 on u_tbl12.id = case when foobar.parent_id = 1 then 1 else 2 end where 1 != 1", "InputName": "VerifyParent-1", "Keyspace": { "Name": "unsharded_fk_allow", "Sharded": false }, "OperatorType": "Route", - "Query": "select 1 from u_tbl12 as foobar left join u_tbl12 on u_tbl12.id = case when foobar.parent_id = 1 then null else null end where u_tbl12.id is null and case when foobar.parent_id = 1 then null else null end is not null and not (foobar.parent_id) <=> (case when foobar.parent_id = 1 then null else null end) and foobar.id in (1) limit 1 for share", + "Query": "select 1 from u_tbl12 as foobar left join u_tbl12 on u_tbl12.id = case when foobar.parent_id = 1 then 1 else 2 end where u_tbl12.id is null and case when foobar.parent_id = 1 then 1 else 2 end is not null and not (foobar.parent_id) <=> (case when foobar.parent_id = 1 then 1 else 2 end) and foobar.id in (1) limit 1 for share", "Variant": "Unsharded" }, { @@ -3030,13 +3030,13 @@ "Sharded": false }, "OperatorType": "Update", - "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl12 as foobar set parent_id = case when parent_id = 1 then null else null end where id in (1)", + "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl12 as foobar set parent_id = case when parent_id = 1 then 1 else 2 end where id in (1)", "Variant": "Unsharded" } ], "OperatorType": "FKVerify" }, - "Original": "UPDATE u_tbl12 as foobar SET parent_id = CASE WHEN (parent_id = 1) THEN NULL ELSE NULL END WHERE id IN (1)", + "Original": "UPDATE u_tbl12 as foobar SET parent_id = CASE WHEN (parent_id = 1) THEN 1 ELSE 2 END WHERE id IN (1)", "QueryType": "UPDATE", "TablesUsed": [ "unsharded_fk_allow.u_tbl12" @@ -3044,6 +3044,44 @@ "Type": "ForeignKey" } }, + { + "comment": "self-referential foreign key update (child side column) without table aliases introduces new aliases", + "query": "UPDATE u_tbl12 SET id = CASE WHEN (parent_id = 1) THEN 1 ELSE 2 END WHERE id IN (1)", + "plan": { + "Type": "ForeignKey", + "QueryType": "UPDATE", + "Original": "UPDATE u_tbl12 SET id = CASE WHEN (parent_id = 1) THEN 1 ELSE 2 END WHERE id IN (1)", + "Instructions": { + "OperatorType": "FKVerify", + "Inputs": [ + { + "InputName": "VerifyChild-1", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "FieldQuery": "select 1 from u_tbl12 as parent, u_tbl12 as child where 1 != 1", + "Query": "select 1 from u_tbl12 as parent, u_tbl12 as child where parent.id = child.parent_id and parent.id in (1) and (case when parent.parent_id = 1 then 1 else 2 end is null or (child.parent_id) not in ((case when parent.parent_id = 1 then 1 else 2 end))) limit 1 for share" + }, + { + "InputName": "PostVerify", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl12 set id = case when parent_id = 1 then 1 else 2 end where id in (1)" + } + ] + }, + "TablesUsed": [ + "unsharded_fk_allow.u_tbl12" + ] + } + }, { "comment": "Multi table delete with using", "query": "delete u_tbl10 from u_tbl10 join u_tbl11 using (id) where id = 5", From 6a9d5883b412e918fb1054a05c5c418187f3d89e Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Mon, 1 Sep 2025 13:43:24 +0000 Subject: [PATCH 6/7] Streamline the logic by always aliasing the tables. Signed-off-by: Arthur Schreiber --- go/vt/vtgate/planbuilder/operators/update.go | 59 ++------ .../testdata/foreignkey_cases.json | 132 +++++++++--------- 2 files changed, 80 insertions(+), 111 deletions(-) diff --git a/go/vt/vtgate/planbuilder/operators/update.go b/go/vt/vtgate/planbuilder/operators/update.go index 92f99465570..625cbe86928 100644 --- a/go/vt/vtgate/planbuilder/operators/update.go +++ b/go/vt/vtgate/planbuilder/operators/update.go @@ -812,35 +812,18 @@ func createFKVerifyOp( // and Child.c2 is not null and not ((Child.c1) <=> (Child.c2 + 1)) // limit 1 func createFkVerifyOpForParentFKForUpdate(ctx *plancontext.PlanningContext, updatedTable *vindexes.BaseTable, updStmt *sqlparser.Update, pFK vindexes.ParentFKInfo) Operator { - childTblExpr := updStmt.TableExprs[0].(*sqlparser.AliasedTableExpr) - childTbl, err := childTblExpr.TableName() + // Alias the foreign key's parent table name + parentTblExpr := sqlparser.NewAliasedTableExpr(pFK.Table.GetTableName(), "parent") + parentTbl, err := parentTblExpr.TableName() if err != nil { panic(err) } - var parentTblExpr *sqlparser.AliasedTableExpr - var parentTbl sqlparser.TableName - - // If the table name (or table alias if one is given) matches - // the parent foreign key's table name, we need to introduce aliases - // to make sure there's no ambiguity. - if pFK.Table.Name == childTbl.Name { - // Alias the foreign key's parent table name - parentTblExpr = sqlparser.NewAliasedTableExpr(pFK.Table.GetTableName(), "parent") - parentTbl, err = parentTblExpr.TableName() - if err != nil { - panic(err) - } - - // Alias the foreign key's child table name - childTblExpr = sqlparser.NewAliasedTableExpr(childTblExpr.Expr.(sqlparser.TableName), "child") - childTbl, err = childTblExpr.TableName() - if err != nil { - panic(err) - } - } else { - parentTblExpr = sqlparser.NewAliasedTableExpr(pFK.Table.GetTableName(), "") - parentTbl = pFK.Table.GetTableName() + // Alias the foreign key's child table name + childTblExpr := sqlparser.NewAliasedTableExpr(updatedTable.GetTableName(), "child") + childTbl, err := childTblExpr.TableName() + if err != nil { + panic(err) } var whereCond sqlparser.Expr @@ -957,31 +940,17 @@ func createFkVerifyOpForChildFKForUpdate(ctx *plancontext.PlanningContext, updat panic(vterrors.VT12002(updatedTable.String(), cFk.Table.String())) } - parentTblExpr := updStmt.TableExprs[0].(*sqlparser.AliasedTableExpr) + parentTblExpr := sqlparser.NewAliasedTableExpr(updatedTable.GetTableName(), "parent") parentTbl, err := parentTblExpr.TableName() if err != nil { panic(err) } - var childTblExpr *sqlparser.AliasedTableExpr - var childTbl sqlparser.TableName - if cFk.Table.Name == parentTbl.Name { - - parentTblExpr = sqlparser.NewAliasedTableExpr(parentTblExpr.Expr.(sqlparser.TableName), "parent") - parentTbl, err = parentTblExpr.TableName() - if err != nil { - panic(err) - } - - // Alias the foreign key's child table name - childTblExpr = sqlparser.NewAliasedTableExpr(cFk.Table.GetTableName(), "child") - childTbl, err = childTblExpr.TableName() - if err != nil { - panic(err) - } - } else { - childTbl = cFk.Table.GetTableName() - childTblExpr = sqlparser.NewAliasedTableExpr(childTbl, "") + // Alias the foreign key's child table name + childTblExpr := sqlparser.NewAliasedTableExpr(cFk.Table.GetTableName(), "child") + childTbl, err := childTblExpr.TableName() + if err != nil { + panic(err) } var joinCond sqlparser.Expr diff --git a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json index e1696056e8e..99130ec9c74 100644 --- a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json @@ -494,7 +494,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": "tbl3.col is null", + "Predicate": "parent.col is null", "Inputs": [ { "OperatorType": "Join", @@ -508,8 +508,8 @@ "Name": "sharded_fk_allow", "Sharded": true }, - "FieldQuery": "select 1 from tbl10 where 1 != 1", - "Query": "select 1 from tbl10 where not (tbl10.col) <=> ('foo') for share" + "FieldQuery": "select 1 from tbl10 as child where 1 != 1", + "Query": "select 1 from tbl10 as child where not (child.col) <=> ('foo') for share" }, { "OperatorType": "Route", @@ -518,8 +518,8 @@ "Name": "sharded_fk_allow", "Sharded": true }, - "FieldQuery": "select tbl3.col from tbl3 where 1 != 1", - "Query": "select tbl3.col from tbl3 where tbl3.col = 'foo' for share" + "FieldQuery": "select parent.col from tbl3 as parent where 1 != 1", + "Query": "select parent.col from tbl3 as parent where parent.col = 'foo' for share" } ] } @@ -763,8 +763,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl2 left join u_tbl1 on u_tbl1.col1 = cast(u_tbl2.col1 + 'bar' as CHAR) where 1 != 1", - "Query": "select 1 from u_tbl2 left join u_tbl1 on u_tbl1.col1 = cast(u_tbl2.col1 + 'bar' as CHAR) where u_tbl1.col1 is null and cast(u_tbl2.col1 + 'bar' as CHAR) is not null and not (u_tbl2.col2) <=> (cast(u_tbl2.col1 + 'bar' as CHAR)) and u_tbl2.id = 1 limit 1 for share" + "FieldQuery": "select 1 from u_tbl2 as child left join u_tbl1 as parent on parent.col1 = cast(child.col1 + 'bar' as CHAR) where 1 != 1", + "Query": "select 1 from u_tbl2 as child left join u_tbl1 as parent on parent.col1 = cast(child.col1 + 'bar' as CHAR) where parent.col1 is null and cast(child.col1 + 'bar' as CHAR) is not null and not (child.col2) <=> (cast(child.col1 + 'bar' as CHAR)) and child.id = 1 limit 1 for share" }, { "InputName": "PostVerify", @@ -1253,14 +1253,14 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": "tbl1.t1col1 is null", + "Predicate": "parent.t1col1 is null", "Inputs": [ { "OperatorType": "Join", "Variant": "LeftJoin", "JoinColumnIndexes": "R:0", "JoinVars": { - "tbl3_colx": 0 + "child_colx": 0 }, "Inputs": [ { @@ -1270,8 +1270,8 @@ "Name": "sharded_fk_allow", "Sharded": true }, - "FieldQuery": "select tbl3.colx from tbl3 where 1 != 1", - "Query": "select tbl3.colx from tbl3 where tbl3.colx + 10 is not null and not (tbl3.coly) <=> (tbl3.colx + 10) and tbl3.coly = 10 for share" + "FieldQuery": "select child.colx from tbl3 as child where 1 != 1", + "Query": "select child.colx from tbl3 as child where child.colx + 10 is not null and not (child.coly) <=> (child.colx + 10) and child.coly = 10 for share" }, { "OperatorType": "Route", @@ -1280,8 +1280,8 @@ "Name": "sharded_fk_allow", "Sharded": true }, - "FieldQuery": "select tbl1.t1col1 from tbl1 where 1 != 1", - "Query": "select tbl1.t1col1 from tbl1 where tbl1.t1col1 = :tbl3_colx + 10 for share" + "FieldQuery": "select parent.t1col1 from tbl1 as parent where 1 != 1", + "Query": "select parent.t1col1 from tbl1 as parent where parent.t1col1 = :child_colx + 10 for share" } ] } @@ -1332,7 +1332,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": "tbl1.t1col1 is null", + "Predicate": "parent.t1col1 is null", "Inputs": [ { "OperatorType": "Join", @@ -1346,8 +1346,8 @@ "Name": "sharded_fk_allow", "Sharded": true }, - "FieldQuery": "select 1 from tbl3 where 1 != 1", - "Query": "select 1 from tbl3 where not (tbl3.coly) <=> (20) and tbl3.coly = 10 for share" + "FieldQuery": "select 1 from tbl3 as child where 1 != 1", + "Query": "select 1 from tbl3 as child where not (child.coly) <=> (20) and child.coly = 10 for share" }, { "OperatorType": "Route", @@ -1356,8 +1356,8 @@ "Name": "sharded_fk_allow", "Sharded": true }, - "FieldQuery": "select tbl1.t1col1 from tbl1 where 1 != 1", - "Query": "select tbl1.t1col1 from tbl1 where tbl1.t1col1 = 20 for share" + "FieldQuery": "select parent.t1col1 from tbl1 as parent where 1 != 1", + "Query": "select parent.t1col1 from tbl1 as parent where parent.t1col1 = 20 for share" } ] } @@ -1422,8 +1422,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl8 left join u_tbl9 on u_tbl9.col9 = cast('foo' as CHAR) where 1 != 1", - "Query": "select 1 from u_tbl8 left join u_tbl9 on u_tbl9.col9 = cast('foo' as CHAR) where u_tbl9.col9 is null and not (u_tbl8.col8) <=> (cast('foo' as CHAR)) and (u_tbl8.col8) in ::fkc_vals limit 1 for share nowait" + "FieldQuery": "select 1 from u_tbl8 as child left join u_tbl9 as parent on parent.col9 = cast('foo' as CHAR) where 1 != 1", + "Query": "select 1 from u_tbl8 as child left join u_tbl9 as parent on parent.col9 = cast('foo' as CHAR) where parent.col9 is null and not (child.col8) <=> (cast('foo' as CHAR)) and (child.col8) in ::fkc_vals limit 1 for share nowait" }, { "InputName": "PostVerify", @@ -1493,8 +1493,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = cast('foo' as CHAR) where 1 != 1", - "Query": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = cast('foo' as CHAR) where u_tbl3.col3 is null and not (u_tbl4.col4) <=> (cast('foo' as CHAR)) and (u_tbl4.col4) in ::fkc_vals limit 1 for share" + "FieldQuery": "select 1 from u_tbl4 as child left join u_tbl3 as parent on parent.col3 = cast('foo' as CHAR) where 1 != 1", + "Query": "select 1 from u_tbl4 as child left join u_tbl3 as parent on parent.col3 = cast('foo' as CHAR) where parent.col3 is null and not (child.col4) <=> (cast('foo' as CHAR)) and (child.col4) in ::fkc_vals limit 1 for share" }, { "InputName": "VerifyChild-2", @@ -1504,8 +1504,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl4, u_tbl9 where 1 != 1", - "Query": "select 1 from u_tbl4, u_tbl9 where u_tbl4.col4 = u_tbl9.col9 and (u_tbl4.col4) in ::fkc_vals and (u_tbl9.col9) not in ((cast('foo' as CHAR))) limit 1 for share" + "FieldQuery": "select 1 from u_tbl4 as parent, u_tbl9 as child where 1 != 1", + "Query": "select 1 from u_tbl4 as parent, u_tbl9 as child where parent.col4 = child.col9 and (parent.col4) in ::fkc_vals and (child.col9) not in ((cast('foo' as CHAR))) limit 1 for share" }, { "InputName": "PostVerify", @@ -1576,8 +1576,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = cast(:v1 as CHAR) where 1 != 1", - "Query": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = cast(:v1 as CHAR) where u_tbl3.col3 is null and cast(:v1 as CHAR) is not null and not (u_tbl4.col4) <=> (cast(:v1 as CHAR)) and (u_tbl4.col4) in ::fkc_vals limit 1 for share" + "FieldQuery": "select 1 from u_tbl4 as child left join u_tbl3 as parent on parent.col3 = cast(:v1 as CHAR) where 1 != 1", + "Query": "select 1 from u_tbl4 as child left join u_tbl3 as parent on parent.col3 = cast(:v1 as CHAR) where parent.col3 is null and cast(:v1 as CHAR) is not null and not (child.col4) <=> (cast(:v1 as CHAR)) and (child.col4) in ::fkc_vals limit 1 for share" }, { "InputName": "VerifyChild-2", @@ -1587,8 +1587,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl4, u_tbl9 where 1 != 1", - "Query": "select 1 from u_tbl4, u_tbl9 where u_tbl4.col4 = u_tbl9.col9 and (u_tbl4.col4) in ::fkc_vals and (cast(:v1 as CHAR) is null or (u_tbl9.col9) not in ((cast(:v1 as CHAR)))) limit 1 for share" + "FieldQuery": "select 1 from u_tbl4 as parent, u_tbl9 as child where 1 != 1", + "Query": "select 1 from u_tbl4 as parent, u_tbl9 as child where parent.col4 = child.col9 and (parent.col4) in ::fkc_vals and (cast(:v1 as CHAR) is null or (child.col9) not in ((cast(:v1 as CHAR)))) limit 1 for share" }, { "InputName": "PostVerify", @@ -2199,8 +2199,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = cast(:fkc_upd as CHAR) where 1 != 1", - "Query": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = cast(:fkc_upd as CHAR) where u_tbl3.col3 is null and cast(:fkc_upd as CHAR) is not null and not (u_tbl4.col4) <=> (cast(:fkc_upd as CHAR)) and (u_tbl4.col4) in ::fkc_vals limit 1 for share" + "FieldQuery": "select 1 from u_tbl4 as child left join u_tbl3 as parent on parent.col3 = cast(:fkc_upd as CHAR) where 1 != 1", + "Query": "select 1 from u_tbl4 as child left join u_tbl3 as parent on parent.col3 = cast(:fkc_upd as CHAR) where parent.col3 is null and cast(:fkc_upd as CHAR) is not null and not (child.col4) <=> (cast(:fkc_upd as CHAR)) and (child.col4) in ::fkc_vals limit 1 for share" }, { "InputName": "VerifyChild-2", @@ -2210,8 +2210,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl4, u_tbl9 where 1 != 1", - "Query": "select 1 from u_tbl4, u_tbl9 where u_tbl4.col4 = u_tbl9.col9 and (u_tbl4.col4) in ::fkc_vals and (cast(:fkc_upd as CHAR) is null or (u_tbl9.col9) not in ((cast(:fkc_upd as CHAR)))) limit 1 for share" + "FieldQuery": "select 1 from u_tbl4 as parent, u_tbl9 as child where 1 != 1", + "Query": "select 1 from u_tbl4 as parent, u_tbl9 as child where parent.col4 = child.col9 and (parent.col4) in ::fkc_vals and (cast(:fkc_upd as CHAR) is null or (child.col9) not in ((cast(:fkc_upd as CHAR)))) limit 1 for share" }, { "InputName": "PostVerify", @@ -2362,8 +2362,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_multicol_tbl2 left join u_multicol_tbl1 on u_multicol_tbl1.cola = 2 and u_multicol_tbl1.colb = u_multicol_tbl2.colc - 2 where 1 != 1", - "Query": "select 1 from u_multicol_tbl2 left join u_multicol_tbl1 on u_multicol_tbl1.cola = 2 and u_multicol_tbl1.colb = u_multicol_tbl2.colc - 2 where u_multicol_tbl1.cola is null and u_multicol_tbl1.colb is null and u_multicol_tbl2.colc - 2 is not null and not (u_multicol_tbl2.cola, u_multicol_tbl2.colb) <=> (2, u_multicol_tbl2.colc - 2) and u_multicol_tbl2.id = 7 limit 1 for share" + "FieldQuery": "select 1 from u_multicol_tbl2 as child left join u_multicol_tbl1 as parent on parent.cola = 2 and parent.colb = child.colc - 2 where 1 != 1", + "Query": "select 1 from u_multicol_tbl2 as child left join u_multicol_tbl1 as parent on parent.cola = 2 and parent.colb = child.colc - 2 where parent.cola is null and parent.colb is null and child.colc - 2 is not null and not (child.cola, child.colb) <=> (2, child.colc - 2) and child.id = 7 limit 1 for share" }, { "InputName": "PostVerify", @@ -2972,76 +2972,76 @@ "comment": "Self-referential foreign key update (parent reference column) without table alias introduces new aliases", "query": "UPDATE u_tbl12 SET parent_id = CASE WHEN (parent_id = 1) THEN 1 ELSE 2 END WHERE id IN (1)", "plan": { + "Type": "ForeignKey", + "QueryType": "UPDATE", + "Original": "UPDATE u_tbl12 SET parent_id = CASE WHEN (parent_id = 1) THEN 1 ELSE 2 END WHERE id IN (1)", "Instructions": { + "OperatorType": "FKVerify", "Inputs": [ { - "FieldQuery": "select 1 from u_tbl12 as child left join u_tbl12 as parent on parent.id = case when child.parent_id = 1 then 1 else 2 end where 1 != 1", "InputName": "VerifyParent-1", + "OperatorType": "Route", + "Variant": "Unsharded", "Keyspace": { "Name": "unsharded_fk_allow", "Sharded": false }, - "OperatorType": "Route", - "Query": "select 1 from u_tbl12 as child left join u_tbl12 as parent on parent.id = case when child.parent_id = 1 then 1 else 2 end where parent.id is null and case when child.parent_id = 1 then 1 else 2 end is not null and not (child.parent_id) <=> (case when child.parent_id = 1 then 1 else 2 end) and child.id in (1) limit 1 for share", - "Variant": "Unsharded" + "FieldQuery": "select 1 from u_tbl12 as child left join u_tbl12 as parent on parent.id = case when child.parent_id = 1 then 1 else 2 end where 1 != 1", + "Query": "select 1 from u_tbl12 as child left join u_tbl12 as parent on parent.id = case when child.parent_id = 1 then 1 else 2 end where parent.id is null and case when child.parent_id = 1 then 1 else 2 end is not null and not (child.parent_id) <=> (case when child.parent_id = 1 then 1 else 2 end) and child.id in (1) limit 1 for share" }, { "InputName": "PostVerify", + "OperatorType": "Update", + "Variant": "Unsharded", "Keyspace": { "Name": "unsharded_fk_allow", "Sharded": false }, - "OperatorType": "Update", - "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl12 set parent_id = case when parent_id = 1 then 1 else 2 end where id in (1)", - "Variant": "Unsharded" + "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl12 set parent_id = case when parent_id = 1 then 1 else 2 end where id in (1)" } - ], - "OperatorType": "FKVerify" + ] }, - "Original": "UPDATE u_tbl12 SET parent_id = CASE WHEN (parent_id = 1) THEN 1 ELSE 2 END WHERE id IN (1)", - "QueryType": "UPDATE", "TablesUsed": [ "unsharded_fk_allow.u_tbl12" - ], - "Type": "ForeignKey" + ] } }, { "comment": "Self-referential foreign key update (parent reference column) with table alias does not introduce new aliases", "query": "UPDATE u_tbl12 as foobar SET parent_id = CASE WHEN (parent_id = 1) THEN 1 ELSE 2 END WHERE id IN (1)", "plan": { + "Type": "ForeignKey", + "QueryType": "UPDATE", + "Original": "UPDATE u_tbl12 as foobar SET parent_id = CASE WHEN (parent_id = 1) THEN 1 ELSE 2 END WHERE id IN (1)", "Instructions": { + "OperatorType": "FKVerify", "Inputs": [ { - "FieldQuery": "select 1 from u_tbl12 as foobar left join u_tbl12 on u_tbl12.id = case when foobar.parent_id = 1 then 1 else 2 end where 1 != 1", "InputName": "VerifyParent-1", + "OperatorType": "Route", + "Variant": "Unsharded", "Keyspace": { "Name": "unsharded_fk_allow", "Sharded": false }, - "OperatorType": "Route", - "Query": "select 1 from u_tbl12 as foobar left join u_tbl12 on u_tbl12.id = case when foobar.parent_id = 1 then 1 else 2 end where u_tbl12.id is null and case when foobar.parent_id = 1 then 1 else 2 end is not null and not (foobar.parent_id) <=> (case when foobar.parent_id = 1 then 1 else 2 end) and foobar.id in (1) limit 1 for share", - "Variant": "Unsharded" + "FieldQuery": "select 1 from u_tbl12 as child left join u_tbl12 as parent on parent.id = case when child.parent_id = 1 then 1 else 2 end where 1 != 1", + "Query": "select 1 from u_tbl12 as child left join u_tbl12 as parent on parent.id = case when child.parent_id = 1 then 1 else 2 end where parent.id is null and case when child.parent_id = 1 then 1 else 2 end is not null and not (child.parent_id) <=> (case when child.parent_id = 1 then 1 else 2 end) and child.id in (1) limit 1 for share" }, { "InputName": "PostVerify", + "OperatorType": "Update", + "Variant": "Unsharded", "Keyspace": { "Name": "unsharded_fk_allow", "Sharded": false }, - "OperatorType": "Update", - "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl12 as foobar set parent_id = case when parent_id = 1 then 1 else 2 end where id in (1)", - "Variant": "Unsharded" + "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl12 as foobar set parent_id = case when parent_id = 1 then 1 else 2 end where id in (1)" } - ], - "OperatorType": "FKVerify" + ] }, - "Original": "UPDATE u_tbl12 as foobar SET parent_id = CASE WHEN (parent_id = 1) THEN 1 ELSE 2 END WHERE id IN (1)", - "QueryType": "UPDATE", "TablesUsed": [ "unsharded_fk_allow.u_tbl12" - ], - "Type": "ForeignKey" + ] } }, { @@ -3397,8 +3397,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl4 left join u_tbl1 on u_tbl1.col14 = cast(:__sq1 as SIGNED) where 1 != 1", - "Query": "select 1 from u_tbl4 left join u_tbl1 on u_tbl1.col14 = cast(:__sq1 as SIGNED) where u_tbl1.col14 is null and cast(:__sq1 as SIGNED) is not null and not (u_tbl4.col41) <=> (cast(:__sq1 as SIGNED)) and u_tbl4.col4 = 3 limit 1 for share" + "FieldQuery": "select 1 from u_tbl4 as child left join u_tbl1 as parent on parent.col14 = cast(:__sq1 as SIGNED) where 1 != 1", + "Query": "select 1 from u_tbl4 as child left join u_tbl1 as parent on parent.col14 = cast(:__sq1 as SIGNED) where parent.col14 is null and cast(:__sq1 as SIGNED) is not null and not (child.col41) <=> (cast(:__sq1 as SIGNED)) and child.col4 = 3 limit 1 for share" }, { "InputName": "PostVerify", @@ -3831,8 +3831,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl2 left join u_tbl1 on u_tbl1.col1 = cast(u_tbl2.id + 1 as CHAR) where 1 != 1", - "Query": "select 1 from u_tbl2 left join u_tbl1 on u_tbl1.col1 = cast(u_tbl2.id + 1 as CHAR) where u_tbl1.col1 is null and cast(u_tbl2.id + 1 as CHAR) is not null and not (u_tbl2.col2) <=> (cast(u_tbl2.id + 1 as CHAR)) and u_tbl2.id in ::dml_vals limit 1 for share" + "FieldQuery": "select 1 from u_tbl2 as child left join u_tbl1 as parent on parent.col1 = cast(child.id + 1 as CHAR) where 1 != 1", + "Query": "select 1 from u_tbl2 as child left join u_tbl1 as parent on parent.col1 = cast(child.id + 1 as CHAR) where parent.col1 is null and cast(child.id + 1 as CHAR) is not null and not (child.col2) <=> (cast(child.id + 1 as CHAR)) and child.id in ::dml_vals limit 1 for share" }, { "InputName": "PostVerify", @@ -3930,8 +3930,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl8 left join u_tbl9 on u_tbl9.col9 = cast('foo' as CHAR) where 1 != 1", - "Query": "select 1 from u_tbl8 left join u_tbl9 on u_tbl9.col9 = cast('foo' as CHAR) where u_tbl9.col9 is null and not (u_tbl8.col8) <=> (cast('foo' as CHAR)) and (u_tbl8.col8) in ::fkc_vals limit 1 for share nowait" + "FieldQuery": "select 1 from u_tbl8 as child left join u_tbl9 as parent on parent.col9 = cast('foo' as CHAR) where 1 != 1", + "Query": "select 1 from u_tbl8 as child left join u_tbl9 as parent on parent.col9 = cast('foo' as CHAR) where parent.col9 is null and not (child.col8) <=> (cast('foo' as CHAR)) and (child.col8) in ::fkc_vals limit 1 for share nowait" }, { "InputName": "PostVerify", From c70fd7c46222b44dc05cf64cfcbb64031fb3fce9 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Mon, 1 Sep 2025 20:52:02 +0530 Subject: [PATCH 7/7] test: update test expectation Signed-off-by: Harshit Gangal --- .../testdata/foreignkey_checks_on_cases.json | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/go/vt/vtgate/planbuilder/testdata/foreignkey_checks_on_cases.json b/go/vt/vtgate/planbuilder/testdata/foreignkey_checks_on_cases.json index 3bb766d1333..c82dceab1f0 100644 --- a/go/vt/vtgate/planbuilder/testdata/foreignkey_checks_on_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/foreignkey_checks_on_cases.json @@ -494,7 +494,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": "tbl3.col is null", + "Predicate": "parent.col is null", "Inputs": [ { "OperatorType": "Join", @@ -508,8 +508,8 @@ "Name": "sharded_fk_allow", "Sharded": true }, - "FieldQuery": "select 1 from tbl10 where 1 != 1", - "Query": "select 1 from tbl10 where not (tbl10.col) <=> ('foo') for share" + "FieldQuery": "select 1 from tbl10 as child where 1 != 1", + "Query": "select 1 from tbl10 as child where not (child.col) <=> ('foo') for share" }, { "OperatorType": "Route", @@ -518,8 +518,8 @@ "Name": "sharded_fk_allow", "Sharded": true }, - "FieldQuery": "select tbl3.col from tbl3 where 1 != 1", - "Query": "select tbl3.col from tbl3 where tbl3.col = 'foo' for share" + "FieldQuery": "select parent.col from tbl3 as parent where 1 != 1", + "Query": "select parent.col from tbl3 as parent where parent.col = 'foo' for share" } ] } @@ -834,8 +834,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl2 left join u_tbl1 on u_tbl1.col1 = cast(u_tbl2.col1 + 'bar' as CHAR) where 1 != 1", - "Query": "select 1 from u_tbl2 left join u_tbl1 on u_tbl1.col1 = cast(u_tbl2.col1 + 'bar' as CHAR) where u_tbl1.col1 is null and cast(u_tbl2.col1 + 'bar' as CHAR) is not null and not (u_tbl2.col2) <=> (cast(u_tbl2.col1 + 'bar' as CHAR)) and u_tbl2.id = 1 limit 1 for share" + "FieldQuery": "select 1 from u_tbl2 as child left join u_tbl1 as parent on parent.col1 = cast(child.col1 + 'bar' as CHAR) where 1 != 1", + "Query": "select 1 from u_tbl2 as child left join u_tbl1 as parent on parent.col1 = cast(child.col1 + 'bar' as CHAR) where parent.col1 is null and cast(child.col1 + 'bar' as CHAR) is not null and not (child.col2) <=> (cast(child.col1 + 'bar' as CHAR)) and child.id = 1 limit 1 for share" }, { "InputName": "PostVerify", @@ -1324,14 +1324,14 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": "tbl1.t1col1 is null", + "Predicate": "parent.t1col1 is null", "Inputs": [ { "OperatorType": "Join", "Variant": "LeftJoin", "JoinColumnIndexes": "R:0", "JoinVars": { - "tbl3_colx": 0 + "child_colx": 0 }, "Inputs": [ { @@ -1341,8 +1341,8 @@ "Name": "sharded_fk_allow", "Sharded": true }, - "FieldQuery": "select tbl3.colx from tbl3 where 1 != 1", - "Query": "select tbl3.colx from tbl3 where tbl3.colx + 10 is not null and not (tbl3.coly) <=> (tbl3.colx + 10) and tbl3.coly = 10 for share" + "FieldQuery": "select child.colx from tbl3 as child where 1 != 1", + "Query": "select child.colx from tbl3 as child where child.colx + 10 is not null and not (child.coly) <=> (child.colx + 10) and child.coly = 10 for share" }, { "OperatorType": "Route", @@ -1351,8 +1351,8 @@ "Name": "sharded_fk_allow", "Sharded": true }, - "FieldQuery": "select tbl1.t1col1 from tbl1 where 1 != 1", - "Query": "select tbl1.t1col1 from tbl1 where tbl1.t1col1 = :tbl3_colx + 10 for share" + "FieldQuery": "select parent.t1col1 from tbl1 as parent where 1 != 1", + "Query": "select parent.t1col1 from tbl1 as parent where parent.t1col1 = :child_colx + 10 for share" } ] } @@ -1403,7 +1403,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": "tbl1.t1col1 is null", + "Predicate": "parent.t1col1 is null", "Inputs": [ { "OperatorType": "Join", @@ -1417,8 +1417,8 @@ "Name": "sharded_fk_allow", "Sharded": true }, - "FieldQuery": "select 1 from tbl3 where 1 != 1", - "Query": "select 1 from tbl3 where not (tbl3.coly) <=> (20) and tbl3.coly = 10 for share" + "FieldQuery": "select 1 from tbl3 as child where 1 != 1", + "Query": "select 1 from tbl3 as child where not (child.coly) <=> (20) and child.coly = 10 for share" }, { "OperatorType": "Route", @@ -1427,8 +1427,8 @@ "Name": "sharded_fk_allow", "Sharded": true }, - "FieldQuery": "select tbl1.t1col1 from tbl1 where 1 != 1", - "Query": "select tbl1.t1col1 from tbl1 where tbl1.t1col1 = 20 for share" + "FieldQuery": "select parent.t1col1 from tbl1 as parent where 1 != 1", + "Query": "select parent.t1col1 from tbl1 as parent where parent.t1col1 = 20 for share" } ] } @@ -1493,8 +1493,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl8 left join u_tbl9 on u_tbl9.col9 = cast('foo' as CHAR) where 1 != 1", - "Query": "select 1 from u_tbl8 left join u_tbl9 on u_tbl9.col9 = cast('foo' as CHAR) where u_tbl9.col9 is null and not (u_tbl8.col8) <=> (cast('foo' as CHAR)) and (u_tbl8.col8) in ::fkc_vals limit 1 for share nowait" + "FieldQuery": "select 1 from u_tbl8 as child left join u_tbl9 as parent on parent.col9 = cast('foo' as CHAR) where 1 != 1", + "Query": "select 1 from u_tbl8 as child left join u_tbl9 as parent on parent.col9 = cast('foo' as CHAR) where parent.col9 is null and not (child.col8) <=> (cast('foo' as CHAR)) and (child.col8) in ::fkc_vals limit 1 for share nowait" }, { "InputName": "PostVerify", @@ -1564,8 +1564,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = cast('foo' as CHAR) where 1 != 1", - "Query": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = cast('foo' as CHAR) where u_tbl3.col3 is null and not (u_tbl4.col4) <=> (cast('foo' as CHAR)) and (u_tbl4.col4) in ::fkc_vals limit 1 for share" + "FieldQuery": "select 1 from u_tbl4 as child left join u_tbl3 as parent on parent.col3 = cast('foo' as CHAR) where 1 != 1", + "Query": "select 1 from u_tbl4 as child left join u_tbl3 as parent on parent.col3 = cast('foo' as CHAR) where parent.col3 is null and not (child.col4) <=> (cast('foo' as CHAR)) and (child.col4) in ::fkc_vals limit 1 for share" }, { "InputName": "VerifyChild-2", @@ -1575,8 +1575,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl4, u_tbl9 where 1 != 1", - "Query": "select 1 from u_tbl4, u_tbl9 where u_tbl4.col4 = u_tbl9.col9 and (u_tbl4.col4) in ::fkc_vals and (u_tbl9.col9) not in ((cast('foo' as CHAR))) limit 1 for share" + "FieldQuery": "select 1 from u_tbl4 as parent, u_tbl9 as child where 1 != 1", + "Query": "select 1 from u_tbl4 as parent, u_tbl9 as child where parent.col4 = child.col9 and (parent.col4) in ::fkc_vals and (child.col9) not in ((cast('foo' as CHAR))) limit 1 for share" }, { "InputName": "PostVerify", @@ -1647,8 +1647,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = cast(:v1 as CHAR) where 1 != 1", - "Query": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = cast(:v1 as CHAR) where u_tbl3.col3 is null and cast(:v1 as CHAR) is not null and not (u_tbl4.col4) <=> (cast(:v1 as CHAR)) and (u_tbl4.col4) in ::fkc_vals limit 1 for share" + "FieldQuery": "select 1 from u_tbl4 as child left join u_tbl3 as parent on parent.col3 = cast(:v1 as CHAR) where 1 != 1", + "Query": "select 1 from u_tbl4 as child left join u_tbl3 as parent on parent.col3 = cast(:v1 as CHAR) where parent.col3 is null and cast(:v1 as CHAR) is not null and not (child.col4) <=> (cast(:v1 as CHAR)) and (child.col4) in ::fkc_vals limit 1 for share" }, { "InputName": "VerifyChild-2", @@ -1658,8 +1658,8 @@ "Name": "unsharded_fk_allow", "Sharded": false }, - "FieldQuery": "select 1 from u_tbl4, u_tbl9 where 1 != 1", - "Query": "select 1 from u_tbl4, u_tbl9 where u_tbl4.col4 = u_tbl9.col9 and (u_tbl4.col4) in ::fkc_vals and (cast(:v1 as CHAR) is null or (u_tbl9.col9) not in ((cast(:v1 as CHAR)))) limit 1 for share" + "FieldQuery": "select 1 from u_tbl4 as parent, u_tbl9 as child where 1 != 1", + "Query": "select 1 from u_tbl4 as parent, u_tbl9 as child where parent.col4 = child.col9 and (parent.col4) in ::fkc_vals and (cast(:v1 as CHAR) is null or (child.col9) not in ((cast(:v1 as CHAR)))) limit 1 for share" }, { "InputName": "PostVerify",