diff --git a/go/test/endtoend/vtgate/foreignkey/fk_fuzz_test.go b/go/test/endtoend/vtgate/foreignkey/fk_fuzz_test.go index df0b427af9e..d884fee1468 100644 --- a/go/test/endtoend/vtgate/foreignkey/fk_fuzz_test.go +++ b/go/test/endtoend/vtgate/foreignkey/fk_fuzz_test.go @@ -151,8 +151,17 @@ func (fz *fuzzer) generateInsertDMLQuery(insertType string) string { } } -// generateUpdateDMLQuery generates an UPDATE query from the parameters for the fuzzer. +// generateUpdateDMLQuery generates a UPDATE query from the parameters for the fuzzer. func (fz *fuzzer) generateUpdateDMLQuery() string { + multiTableUpdate := rand.IntN(2) + 1 + if multiTableUpdate == 1 { + return fz.generateSingleUpdateDMLQuery() + } + return fz.generateMultiUpdateDMLQuery() +} + +// generateSingleUpdateDMLQuery generates an UPDATE query from the parameters for the fuzzer. +func (fz *fuzzer) generateSingleUpdateDMLQuery() string { tableId := rand.IntN(len(fkTables)) idValue := 1 + rand.IntN(fz.maxValForId) tableName := fkTables[tableId] @@ -195,6 +204,22 @@ func (fz *fuzzer) generateUpdateDMLQuery() string { } } +// generateMultiUpdateDMLQuery generates a UPDATE query using 2 tables from the parameters for the fuzzer. +func (fz *fuzzer) generateMultiUpdateDMLQuery() string { + tableId := rand.IntN(len(fkTables)) + tableId2 := rand.IntN(len(fkTables)) + idValue := 1 + rand.IntN(fz.maxValForId) + colValue := convertIntValueToString(rand.IntN(1 + fz.maxValForCol)) + col2Value := convertIntValueToString(rand.IntN(1 + fz.maxValForCol)) + setVarFkChecksVal := fz.getSetVarFkChecksVal() + setExprs := fmt.Sprintf("%v.col = %v", fkTables[tableId], colValue) + if rand.IntN(2)%2 == 0 { + setExprs += ", " + fmt.Sprintf("%v.col = %v", fkTables[tableId2], col2Value) + } + query := fmt.Sprintf("update %v%v join %v using (id) set %s where %v.id = %v", setVarFkChecksVal, fkTables[tableId], fkTables[tableId2], setExprs, fkTables[tableId], idValue) + return query +} + // generateDeleteDMLQuery generates a DELETE query using 1 table from the parameters for the fuzzer. func (fz *fuzzer) generateSingleDeleteDMLQuery() string { tableId := rand.IntN(len(fkTables)) diff --git a/go/test/endtoend/vtgate/foreignkey/fk_test.go b/go/test/endtoend/vtgate/foreignkey/fk_test.go index a1619970b5c..a0678e6dba1 100644 --- a/go/test/endtoend/vtgate/foreignkey/fk_test.go +++ b/go/test/endtoend/vtgate/foreignkey/fk_test.go @@ -825,6 +825,36 @@ func TestFkScenarios(t *testing.T) { "select * from fk_multicol_t17 order by id", "select * from fk_multicol_t19 order by id", }, + }, { + name: "Multi Table Update with non-literal update", + dataQueries: []string{ + "insert into fk_multicol_t15(id, cola, colb) values (1, 7, 1), (2, 9, 1), (3, 12, 1)", + "insert into fk_multicol_t16(id, cola, colb) values (1, 7, 1), (2, 9, 1), (3, 12, 1)", + "insert into fk_multicol_t17(id, cola, colb) values (1, 7, 1)", + "insert into fk_multicol_t19(id, cola, colb) values (1, 7, 1)", + }, + dmlQuery: "update fk_multicol_t15 m1 join fk_multicol_t17 on m1.id = fk_multicol_t17.id set m1.cola = m1.id + 8 where m1.id < 3", + assertionQueries: []string{ + "select * from fk_multicol_t15 order by id", + "select * from fk_multicol_t16 order by id", + "select * from fk_multicol_t17 order by id", + "select * from fk_multicol_t19 order by id", + }, + }, { + name: "Multi Target Update with non-literal update", + dataQueries: []string{ + "insert into fk_multicol_t15(id, cola, colb) values (1, 7, 1), (2, 9, 1), (3, 12, 1)", + "insert into fk_multicol_t16(id, cola, colb) values (1, 7, 1), (2, 9, 1), (3, 12, 1)", + "insert into fk_multicol_t17(id, cola, colb) values (1, 7, 1), (2, 9, 1)", + "insert into fk_multicol_t19(id, cola, colb) values (1, 7, 1)", + }, + dmlQuery: "update fk_multicol_t15 m1 join fk_multicol_t17 on m1.id = fk_multicol_t17.id set m1.cola = m1.id + 8, fk_multicol_t17.colb = 32 where m1.id < 3", + assertionQueries: []string{ + "select * from fk_multicol_t15 order by id", + "select * from fk_multicol_t16 order by id", + "select * from fk_multicol_t17 order by id", + "select * from fk_multicol_t19 order by id", + }, }, } diff --git a/go/vt/vtgate/planbuilder/operators/update.go b/go/vt/vtgate/planbuilder/operators/update.go index 4c559fcf7f7..b4aca5ee5f8 100644 --- a/go/vt/vtgate/planbuilder/operators/update.go +++ b/go/vt/vtgate/planbuilder/operators/update.go @@ -103,16 +103,15 @@ func createOperatorFromUpdate(ctx *plancontext.PlanningContext, updStmt *sqlpars childFks := ctx.SemTable.GetChildForeignKeysForTargets() // We check if dml with input plan is required. DML with input planning is generally - // slower, because it does a selection and then creates a update statement wherein we have to + // slower, because it does a selection and then creates an update statement wherein we have to // list all the primary key values. if updateWithInputPlanningRequired(ctx, childFks, parentFks, updStmt) { return createUpdateWithInputOp(ctx, updStmt) } var updClone *sqlparser.Update - var vTbl *vindexes.Table - - op, vTbl, updClone = createUpdateOperator(ctx, updStmt) + var targetTbl TargetTable + op, targetTbl, updClone = createUpdateOperator(ctx, updStmt) op = &LockAndComment{ Source: op, @@ -120,11 +119,12 @@ func createOperatorFromUpdate(ctx *plancontext.PlanningContext, updStmt *sqlpars Lock: sqlparser.ShareModeLock, } + parentFks = ctx.SemTable.GetParentForeignKeysForTableSet(targetTbl.ID) + childFks = ctx.SemTable.GetChildForeignKeysForTableSet(targetTbl.ID) if len(childFks) == 0 && len(parentFks) == 0 { return op } - - return buildFkOperator(ctx, op, updClone, parentFks, childFks, vTbl) + return buildFkOperator(ctx, op, updClone, parentFks, childFks, targetTbl) } func updateWithInputPlanningRequired( @@ -133,7 +133,7 @@ func updateWithInputPlanningRequired( parentFks []vindexes.ParentFKInfo, updateStmt *sqlparser.Update, ) bool { - if isMultiTargetUpdate(ctx, childFks, parentFks, updateStmt) { + if isMultiTargetUpdate(ctx, updateStmt) { return true } // If there are no foreign keys, we don't need to use delete with input. @@ -147,20 +147,12 @@ func updateWithInputPlanningRequired( return false } -func isMultiTargetUpdate(ctx *plancontext.PlanningContext, childFks []vindexes.ChildFKInfo, parentFks []vindexes.ParentFKInfo, updateStmt *sqlparser.Update) bool { +func isMultiTargetUpdate(ctx *plancontext.PlanningContext, updateStmt *sqlparser.Update) bool { var targetTS semantics.TableSet for _, ue := range updateStmt.Exprs { targetTS = targetTS.Merge(ctx.SemTable.DirectDeps(ue.Name)) } - if targetTS.NumberOfTables() == 1 { - return false - } - - if len(childFks) > 0 || len(parentFks) > 0 { - panic(vterrors.VT12001("multi table update with foreign keys")) - } - - return true + return targetTS.NumberOfTables() > 1 } func createUpdateWithInputOp(ctx *plancontext.PlanningContext, upd *sqlparser.Update) (op Operator) { @@ -282,7 +274,7 @@ func errIfUpdateNotSupported(ctx *plancontext.PlanningContext, stmt *sqlparser.U } } -func createUpdateOperator(ctx *plancontext.PlanningContext, updStmt *sqlparser.Update) (Operator, *vindexes.Table, *sqlparser.Update) { +func createUpdateOperator(ctx *plancontext.PlanningContext, updStmt *sqlparser.Update) (Operator, TargetTable, *sqlparser.Update) { op := crossJoin(ctx, updStmt.TableExprs) sqc := &SubQueryBuilder{} @@ -364,7 +356,7 @@ func createUpdateOperator(ctx *plancontext.PlanningContext, updStmt *sqlparser.U } } - return sqc.getRootOperator(updOp, nil), vTbl, updClone + return sqc.getRootOperator(updOp, nil), targetTbl, updClone } func getUpdateVindexInformation( @@ -382,14 +374,14 @@ func getUpdateVindexInformation( return changedVindexValues, ownedVindexQuery, subQueriesArgOnChangedVindex } -func buildFkOperator(ctx *plancontext.PlanningContext, updOp Operator, updClone *sqlparser.Update, parentFks []vindexes.ParentFKInfo, childFks []vindexes.ChildFKInfo, updatedTable *vindexes.Table) Operator { +func buildFkOperator(ctx *plancontext.PlanningContext, updOp Operator, updClone *sqlparser.Update, parentFks []vindexes.ParentFKInfo, childFks []vindexes.ChildFKInfo, targetTbl TargetTable) Operator { // If there is a subquery container above update operator, we want to do the foreign key planning inside it, // because we want the Inner of the subquery to execute first and its result be used for the entire foreign key update planning. foundSubqc := false TopDown(updOp, TableID, func(in Operator, _ semantics.TableSet, _ bool) (Operator, *ApplyResult) { if op, isSubqc := in.(*SubQueryContainer); isSubqc { foundSubqc = true - op.Outer = buildFkOperator(ctx, op.Outer, updClone, parentFks, childFks, updatedTable) + op.Outer = buildFkOperator(ctx, op.Outer, updClone, parentFks, childFks, targetTbl) } return in, NoRewrite }, stopAtUpdateOp) @@ -399,9 +391,9 @@ func buildFkOperator(ctx *plancontext.PlanningContext, updOp Operator, updClone restrictChildFks, cascadeChildFks := splitChildFks(childFks) - op := createFKCascadeOp(ctx, updOp, updClone, cascadeChildFks, updatedTable) + op := createFKCascadeOp(ctx, updOp, updClone, cascadeChildFks, targetTbl) - return createFKVerifyOp(ctx, op, updClone, parentFks, restrictChildFks, updatedTable) + return createFKVerifyOp(ctx, op, updClone, parentFks, restrictChildFks, targetTbl.VTable) } func stopAtUpdateOp(operator Operator) VisitRule { @@ -428,7 +420,7 @@ func splitChildFks(fks []vindexes.ChildFKInfo) (restrictChildFks, cascadeChildFk return } -func createFKCascadeOp(ctx *plancontext.PlanningContext, parentOp Operator, updStmt *sqlparser.Update, childFks []vindexes.ChildFKInfo, updatedTable *vindexes.Table) Operator { +func createFKCascadeOp(ctx *plancontext.PlanningContext, parentOp Operator, updStmt *sqlparser.Update, childFks []vindexes.ChildFKInfo, targetTbl TargetTable) Operator { if len(childFks) == 0 { return parentOp } @@ -444,29 +436,29 @@ func createFKCascadeOp(ctx *plancontext.PlanningContext, parentOp Operator, updS // We need to select all the parent columns for the foreign key constraint, to use in the update of the child table. var selectOffsets []int - selectOffsets, selectExprs = addColumns(ctx, fk.ParentColumns, selectExprs, updatedTable.GetTableName()) + selectOffsets, selectExprs = addColumns(ctx, fk.ParentColumns, selectExprs, targetTbl.Name) // If we are updating a foreign key column to a non-literal value then, need information about // 1. whether the new value is different from the old value // 2. the new value itself. // 3. the bind variable to assign to this value. var nonLiteralUpdateInfo []engine.NonLiteralUpdateInfo - ue := ctx.SemTable.GetUpdateExpressionsForFk(fk.String(updatedTable)) + ue := ctx.SemTable.GetUpdateExpressionsForFk(fk.String(targetTbl.VTable)) // We only need to store these offsets and add these expressions to SELECT when there are non-literal updates present. if hasNonLiteralUpdate(ue) { for _, updExpr := range ue { // We add the expression and a comparison expression to the SELECT exprssion while storing their offsets. var info engine.NonLiteralUpdateInfo - info, selectExprs = addNonLiteralUpdExprToSelect(ctx, updatedTable, updExpr, selectExprs) + info, selectExprs = addNonLiteralUpdExprToSelect(ctx, targetTbl.VTable, updExpr, selectExprs) nonLiteralUpdateInfo = append(nonLiteralUpdateInfo, info) } } - fkChild := createFkChildForUpdate(ctx, fk, selectOffsets, nonLiteralUpdateInfo, updatedTable) + fkChild := createFkChildForUpdate(ctx, fk, selectOffsets, nonLiteralUpdateInfo, targetTbl.VTable) fkChildren = append(fkChildren, fkChild) } - selectionOp := createSelectionOp(ctx, selectExprs, updStmt.TableExprs, updStmt.Where, updStmt.OrderBy, nil, getUpdateLock(updatedTable)) + selectionOp := createSelectionOp(ctx, selectExprs, updStmt.TableExprs, updStmt.Where, updStmt.OrderBy, nil, getUpdateLock(targetTbl.VTable)) return &FkCascade{ Selection: selectionOp, diff --git a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json index ae82f075d08..b4a0ad63206 100644 --- a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json @@ -4071,5 +4071,331 @@ "unsharded_fk_allow.u_tbl3" ] } + }, + { + "comment": "multi table update", + "query": "update u_tbl6 u join u_tbl5 m on u.col = m.col set u.col6 = 'foo' where u.col2 = 4 and m.col3 = 6", + "plan": { + "QueryType": "UPDATE", + "Original": "update u_tbl6 u join u_tbl5 m on u.col = m.col set u.col6 = 'foo' where u.col2 = 4 and m.col3 = 6", + "Instructions": { + "OperatorType": "FkCascade", + "Inputs": [ + { + "InputName": "Selection", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "FieldQuery": "select u.col6 from u_tbl6 as u, u_tbl5 as m where 1 != 1", + "Query": "select u.col6 from u_tbl6 as u, u_tbl5 as m where u.col = m.col and u.col2 = 4 and m.col3 = 6 for update", + "Table": "u_tbl5, u_tbl6" + }, + { + "InputName": "CascadeChild-1", + "OperatorType": "FKVerify", + "BvName": "fkc_vals", + "Cols": [ + 0 + ], + "Inputs": [ + { + "InputName": "VerifyParent-1", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "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 cast('foo' as CHAR) is not null and not (u_tbl8.col8) <=> (cast('foo' as CHAR)) and (u_tbl8.col8) in ::fkc_vals limit 1 for share nowait", + "Table": "u_tbl8, u_tbl9" + }, + { + "InputName": "PostVerify", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl8 set col8 = 'foo' where (col8) in ::fkc_vals", + "Table": "u_tbl8" + } + ] + }, + { + "InputName": "Parent", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "update u_tbl6 as u, u_tbl5 as m set u.col6 = 'foo' where u.col2 = 4 and m.col3 = 6 and u.col = m.col", + "Table": "u_tbl6" + } + ] + }, + "TablesUsed": [ + "unsharded_fk_allow.u_tbl5", + "unsharded_fk_allow.u_tbl6", + "unsharded_fk_allow.u_tbl8", + "unsharded_fk_allow.u_tbl9" + ] + } + }, + { + "comment": "multi target update", + "query": "update u_tbl1 u join u_multicol_tbl1 m on u.col = m.col set u.col1 = 'foo', m.cola = 'bar' where u.foo = 4 and m.bar = 6", + "plan": { + "QueryType": "UPDATE", + "Original": "update u_tbl1 u join u_multicol_tbl1 m on u.col = m.col set u.col1 = 'foo', m.cola = 'bar' where u.foo = 4 and m.bar = 6", + "Instructions": { + "OperatorType": "DMLWithInput", + "TargetTabletType": "PRIMARY", + "Offset": [ + "0:[0]", + "1:[1]" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "FieldQuery": "select u.id, m.id from u_tbl1 as u, u_multicol_tbl1 as m where 1 != 1", + "Query": "select u.id, m.id from u_tbl1 as u, u_multicol_tbl1 as m where u.foo = 4 and m.bar = 6 and u.col = m.col for update", + "Table": "u_multicol_tbl1, u_tbl1" + }, + { + "OperatorType": "FkCascade", + "Inputs": [ + { + "InputName": "Selection", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "FieldQuery": "select u.col1 from u_tbl1 as u where 1 != 1", + "Query": "select u.col1 from u_tbl1 as u where u.id in ::dml_vals for update", + "Table": "u_tbl1" + }, + { + "InputName": "CascadeChild-1", + "OperatorType": "FkCascade", + "BvName": "fkc_vals", + "Cols": [ + 0 + ], + "Inputs": [ + { + "InputName": "Selection", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "FieldQuery": "select u_tbl2.col2 from u_tbl2 where 1 != 1", + "Query": "select u_tbl2.col2 from u_tbl2 where (col2) in ::fkc_vals for update", + "Table": "u_tbl2" + }, + { + "InputName": "CascadeChild-1", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "BvName": "fkc_vals1", + "Cols": [ + 0 + ], + "Query": "update u_tbl3 set col3 = null where (col3) in ::fkc_vals1 and (col3) not in ((cast('foo' as CHAR)))", + "Table": "u_tbl3" + }, + { + "InputName": "Parent", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_tbl2 set col2 = 'foo' where (col2) in ::fkc_vals", + "Table": "u_tbl2" + } + ] + }, + { + "InputName": "CascadeChild-2", + "OperatorType": "FkCascade", + "BvName": "fkc_vals2", + "Cols": [ + 0 + ], + "Inputs": [ + { + "InputName": "Selection", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "FieldQuery": "select u_tbl9.col9 from u_tbl9 where 1 != 1", + "Query": "select u_tbl9.col9 from u_tbl9 where (col9) in ::fkc_vals2 and (col9) not in ((cast('foo' as CHAR))) for update nowait", + "Table": "u_tbl9" + }, + { + "InputName": "CascadeChild-1", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "BvName": "fkc_vals3", + "Cols": [ + 0 + ], + "Query": "update u_tbl8 set col8 = null where (col8) in ::fkc_vals3", + "Table": "u_tbl8" + }, + { + "InputName": "Parent", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "update u_tbl9 set col9 = null where (col9) in ::fkc_vals2 and (col9) not in ((cast('foo' as CHAR)))", + "Table": "u_tbl9" + } + ] + }, + { + "InputName": "Parent", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "update u_tbl1 as u set u.col1 = 'foo' where u.id in ::dml_vals", + "Table": "u_tbl1" + } + ] + }, + { + "OperatorType": "FkCascade", + "Inputs": [ + { + "InputName": "Selection", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "FieldQuery": "select m.cola, m.colb from u_multicol_tbl1 as m where 1 != 1", + "Query": "select m.cola, m.colb from u_multicol_tbl1 as m where m.id in ::dml_vals for update", + "Table": "u_multicol_tbl1" + }, + { + "InputName": "CascadeChild-1", + "OperatorType": "FkCascade", + "BvName": "fkc_vals4", + "Cols": [ + 0, + 1 + ], + "Inputs": [ + { + "InputName": "Selection", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "FieldQuery": "select u_multicol_tbl2.cola, u_multicol_tbl2.colb from u_multicol_tbl2 where 1 != 1", + "Query": "select u_multicol_tbl2.cola, u_multicol_tbl2.colb from u_multicol_tbl2 where (cola, colb) in ::fkc_vals4 and (cola) not in (('bar')) for update", + "Table": "u_multicol_tbl2" + }, + { + "InputName": "CascadeChild-1", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "BvName": "fkc_vals5", + "Cols": [ + 0, + 1 + ], + "Query": "update /*+ SET_VAR(foreign_key_checks=OFF) */ u_multicol_tbl3 set cola = null, colb = null where (cola, colb) in ::fkc_vals5", + "Table": "u_multicol_tbl3" + }, + { + "InputName": "Parent", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "update u_multicol_tbl2 set cola = null, colb = null where (cola, colb) in ::fkc_vals4 and (cola) not in (('bar'))", + "Table": "u_multicol_tbl2" + } + ] + }, + { + "InputName": "Parent", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "update u_multicol_tbl1 as m set m.cola = 'bar' where m.id in ::dml_vals", + "Table": "u_multicol_tbl1" + } + ] + } + ] + }, + "TablesUsed": [ + "unsharded_fk_allow.u_multicol_tbl1", + "unsharded_fk_allow.u_multicol_tbl2", + "unsharded_fk_allow.u_multicol_tbl3", + "unsharded_fk_allow.u_tbl1", + "unsharded_fk_allow.u_tbl2", + "unsharded_fk_allow.u_tbl3", + "unsharded_fk_allow.u_tbl8", + "unsharded_fk_allow.u_tbl9" + ] + } } ] diff --git a/go/vt/vtgate/semantics/semantic_state.go b/go/vt/vtgate/semantics/semantic_state.go index 9ba1fa43de1..1ea4bc2a889 100644 --- a/go/vt/vtgate/semantics/semantic_state.go +++ b/go/vt/vtgate/semantics/semantic_state.go @@ -198,6 +198,16 @@ func (st *SemTable) GetChildForeignKeysForTargets() (fks []vindexes.ChildFKInfo) return fks } +// GetChildForeignKeysForTableSet gets the child foreign keys as a listfor the TableSet. +func (st *SemTable) GetChildForeignKeysForTableSet(target TableSet) (fks []vindexes.ChildFKInfo) { + for _, ts := range st.Targets.Constituents() { + if target.IsSolvedBy(ts) { + fks = append(fks, st.childForeignKeysInvolved[ts]...) + } + } + return fks +} + // GetChildForeignKeysForTable gets the child foreign keys as a list for the specified TableName. func (st *SemTable) GetChildForeignKeysForTable(tbl sqlparser.TableName) ([]vindexes.ChildFKInfo, error) { ts, err := st.GetTargetTableSetForTableName(tbl) @@ -224,6 +234,16 @@ func (st *SemTable) GetParentForeignKeysForTargets() (fks []vindexes.ParentFKInf return fks } +// GetParentForeignKeysForTableSet gets the parent foreign keys as a list for the TableSet. +func (st *SemTable) GetParentForeignKeysForTableSet(target TableSet) (fks []vindexes.ParentFKInfo) { + for _, ts := range st.Targets.Constituents() { + if target.IsSolvedBy(ts) { + fks = append(fks, st.parentForeignKeysInvolved[ts]...) + } + } + return fks +} + // GetParentForeignKeysList gets the parent foreign keys as a list. func (st *SemTable) GetParentForeignKeysList() []vindexes.ParentFKInfo { var parentFkInfos []vindexes.ParentFKInfo