diff --git a/go/vt/vtgate/planbuilder/operators/update.go b/go/vt/vtgate/planbuilder/operators/update.go index 85a44bafcca..625cbe86928 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, @@ -812,12 +812,20 @@ 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) + // 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(updatedTable.GetTableName(), "child") childTbl, err := childTblExpr.TableName() if err != nil { panic(err) } - parentTbl := pFK.Table.GetTableName() + var whereCond sqlparser.Expr var joinCond sqlparser.Expr var notEqualColNames sqlparser.ValTuple @@ -894,7 +902,7 @@ func createFkVerifyOpForParentFKForUpdate(ctx *plancontext.PlanningContext, upda sqlparser.NewJoinTableExpr( childTblExpr, sqlparser.LeftJoinType, - sqlparser.NewAliasedTableExpr(parentTbl, ""), + parentTblExpr, sqlparser.NewJoinCondition(joinCond, nil)), }, sqlparser.NewWhere(sqlparser.WhereClause, whereCond), @@ -931,12 +939,20 @@ func createFkVerifyOpForChildFKForUpdate(ctx *plancontext.PlanningContext, updat if !ctx.VerifyAllFKs { 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) } - childTbl := cFk.Table.GetTableName() + + // 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 for idx := range cFk.ParentColumns { joinExpr := &sqlparser.ComparisonExpr{ @@ -967,7 +983,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) } @@ -978,7 +994,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), @@ -992,7 +1008,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 { @@ -1007,7 +1023,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 fcdd8b5498e..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. @@ -236,7 +237,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..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", @@ -2968,6 +2968,120 @@ ] } }, + { + "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": [ + { + "InputName": "VerifyParent-1", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "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 + }, + "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)" + } + ] + }, + "TablesUsed": [ + "unsharded_fk_allow.u_tbl12" + ] + } + }, + { + "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": [ + { + "InputName": "VerifyParent-1", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "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 + }, + "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)" + } + ] + }, + "TablesUsed": [ + "unsharded_fk_allow.u_tbl12" + ] + } + }, + { + "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", @@ -3283,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", @@ -3717,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", @@ -3816,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", 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", 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": {},