diff --git a/go/test/endtoend/vtgate/foreignkey/fk_test.go b/go/test/endtoend/vtgate/foreignkey/fk_test.go index d61aea0aed9..2478b525508 100644 --- a/go/test/endtoend/vtgate/foreignkey/fk_test.go +++ b/go/test/endtoend/vtgate/foreignkey/fk_test.go @@ -34,18 +34,51 @@ func TestInsertions(t *testing.T) { // Verify that inserting data into a table that has shard scoped foreign keys works. utils.Exec(t, conn, `insert into t2(id, col) values (100, 125), (1, 132)`) + // Verify that insertion fails if the data doesn't follow the fk constraint. _, err := utils.ExecAllowError(t, conn, `insert into t2(id, col) values (1310, 125)`) require.ErrorContains(t, err, "Cannot add or update a child row: a foreign key constraint fails") + // Verify that insertion fails if the table has cross-shard foreign keys (even if the data follows the constraints). _, err = utils.ExecAllowError(t, conn, `insert into t3(id, col) values (100, 100)`) require.ErrorContains(t, err, "VT12002: unsupported: cross-shard foreign keys") // insert some data in a table with multicol vindex. utils.Exec(t, conn, `insert into multicol_tbl1(cola, colb, colc, msg) values (100, 'a', 'b', 'msg'), (101, 'c', 'd', 'msg2')`) + // Verify that inserting data into a table that has shard scoped multi-column foreign keys works. utils.Exec(t, conn, `insert into multicol_tbl2(cola, colb, colc, msg) values (100, 'a', 'b', 'msg3')`) + // Verify that insertion fails if the data doesn't follow the fk constraint. _, err = utils.ExecAllowError(t, conn, `insert into multicol_tbl2(cola, colb, colc, msg) values (103, 'c', 'd', 'msg2')`) require.ErrorContains(t, err, "Cannot add or update a child row: a foreign key constraint fails") } + +// TestDeletions tests that deletions work as expected when foreign key management is enabled in Vitess. +func TestDeletions(t *testing.T) { + conn, closer := start(t) + defer closer() + + // insert some data. + utils.Exec(t, conn, `insert into t1(id, col) values (100, 123),(10, 12),(1, 13),(1000, 1234)`) + utils.Exec(t, conn, `insert into t2(id, col) values (100, 125), (1, 132)`) + utils.Exec(t, conn, `insert into t4(id, col) values (1, 321)`) + utils.Exec(t, conn, `insert into multicol_tbl1(cola, colb, colc, msg) values (100, 'a', 'b', 'msg'), (101, 'c', 'd', 'msg2')`) + utils.Exec(t, conn, `insert into multicol_tbl2(cola, colb, colc, msg) values (100, 'a', 'b', 'msg3')`) + + // child foreign key is shard scoped. Query will fail at mysql due to On Delete Restrict. + _, err := utils.ExecAllowError(t, conn, `delete from t2 where col = 132`) + require.ErrorContains(t, err, "Cannot delete or update a parent row: a foreign key constraint fails") + + // child row does not exist so query will succeed. + qr := utils.Exec(t, conn, `delete from t2 where col = 125`) + require.EqualValues(t, 1, qr.RowsAffected) + + // table's child foreign key has cross shard fk, so query will fail at vtgate. + _, err = utils.ExecAllowError(t, conn, `delete from t1 where id = 42`) + require.ErrorContains(t, err, "VT12002: unsupported: foreign keys management at vitess (errno 1235) (sqlstate 42000)") + + // child foreign key is cascade, so query will fail at vtgate. + _, err = utils.ExecAllowError(t, conn, `delete from multicol_tbl1 where cola = 100`) + require.ErrorContains(t, err, "VT12002: unsupported: foreign keys management at vitess (errno 1235) (sqlstate 42000)") +} diff --git a/go/test/endtoend/vtgate/foreignkey/main_test.go b/go/test/endtoend/vtgate/foreignkey/main_test.go index eaeae4eeb9b..8cd9cdcadc1 100644 --- a/go/test/endtoend/vtgate/foreignkey/main_test.go +++ b/go/test/endtoend/vtgate/foreignkey/main_test.go @@ -90,10 +90,16 @@ func start(t *testing.T) (*mysql.Conn, func()) { require.NoError(t, err) deleteAll := func() { - tables := []string{"t3", "t2", "t1", "multicol_tbl2", "multicol_tbl1"} + _ = utils.Exec(t, conn, "use `ks/-80`") + tables := []string{"t4", "t3", "t2", "t1", "multicol_tbl2", "multicol_tbl1"} for _, table := range tables { _ = utils.Exec(t, conn, "delete from "+table) } + _ = utils.Exec(t, conn, "use `ks/80-`") + for _, table := range tables { + _ = utils.Exec(t, conn, "delete from "+table) + } + _ = utils.Exec(t, conn, "use `ks`") } deleteAll() diff --git a/go/test/endtoend/vtgate/foreignkey/sharded_schema.sql b/go/test/endtoend/vtgate/foreignkey/sharded_schema.sql index 47432e0998b..37b9e5c15c6 100644 --- a/go/test/endtoend/vtgate/foreignkey/sharded_schema.sql +++ b/go/test/endtoend/vtgate/foreignkey/sharded_schema.sql @@ -10,7 +10,7 @@ create table t2 id bigint, col bigint, primary key (id), - foreign key (id) references t1 (id) + foreign key (id) references t1 (id) on delete restrict ) Engine = InnoDB; create table t3 @@ -18,7 +18,7 @@ create table t3 id bigint, col bigint, primary key (id), - foreign key (col) references t1 (id) + foreign key (col) references t1 (id) on delete restrict ) Engine = InnoDB; create table multicol_tbl1 @@ -37,5 +37,13 @@ create table multicol_tbl2 colc varchar(50), msg varchar(50), primary key (cola, colb, colc), - foreign key (cola, colb, colc) references multicol_tbl1 (cola, colb, colc) + foreign key (cola, colb, colc) references multicol_tbl1 (cola, colb, colc) on delete cascade +) Engine = InnoDB; + +create table t4 +( + id bigint, + col bigint, + primary key (id), + foreign key (id) references t2 (id) on delete restrict ) Engine = InnoDB; diff --git a/go/test/endtoend/vtgate/foreignkey/sharded_vschema.json b/go/test/endtoend/vtgate/foreignkey/sharded_vschema.json index e55185f25b0..04127774fd1 100644 --- a/go/test/endtoend/vtgate/foreignkey/sharded_vschema.json +++ b/go/test/endtoend/vtgate/foreignkey/sharded_vschema.json @@ -39,6 +39,14 @@ } ] }, + "t4": { + "column_vindexes": [ + { + "column": "id", + "name": "xxhash" + } + ] + }, "multicol_tbl1": { "column_vindexes": [ { diff --git a/go/vt/vterrors/code.go b/go/vt/vterrors/code.go index 9f178cc9a13..797f69ce342 100644 --- a/go/vt/vterrors/code.go +++ b/go/vt/vterrors/code.go @@ -81,6 +81,7 @@ var ( VT12001 = errorWithoutState("VT12001", vtrpcpb.Code_UNIMPLEMENTED, "unsupported: %s", "This statement is unsupported by Vitess. Please rewrite your query to use supported syntax.") VT12002 = errorWithoutState("VT12002", vtrpcpb.Code_UNIMPLEMENTED, "unsupported: cross-shard foreign keys", "Vitess does not support cross shard foreign keys.") + VT12003 = errorWithoutState("VT12002", vtrpcpb.Code_UNIMPLEMENTED, "unsupported: foreign keys management at vitess", "Vitess does not support managing foreign keys tables.") // VT13001 General Error VT13001 = errorWithoutState("VT13001", vtrpcpb.Code_INTERNAL, "[BUG] %s", "This error should not happen and is a bug. Please file an issue on GitHub: https://github.com/vitessio/vitess/issues/new/choose.") @@ -145,6 +146,7 @@ var ( VT10001, VT12001, VT12002, + VT12003, VT13001, VT13002, VT14001, diff --git a/go/vt/vtgate/planbuilder/delete.go b/go/vt/vtgate/planbuilder/delete.go index 2c2641e8a3b..cef2108824e 100644 --- a/go/vt/vtgate/planbuilder/delete.go +++ b/go/vt/vtgate/planbuilder/delete.go @@ -18,6 +18,7 @@ package planbuilder import ( querypb "vitess.io/vitess/go/vt/proto/query" + vschemapb "vitess.io/vitess/go/vt/proto/vschema" "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/vterrors" "vitess.io/vitess/go/vt/vtgate/engine" @@ -62,9 +63,11 @@ func gen4DeleteStmtPlanner( } if ks, tables := semTable.SingleUnshardedKeyspace(); ks != nil { - plan := deleteUnshardedShortcut(deleteStmt, ks, tables) - plan = pushCommentDirectivesOnPlan(plan, deleteStmt) - return newPlanResult(plan.Primitive(), operators.QualifiedTables(ks, tables)...), nil + if fkManagementNotRequired(vschema, tables) { + plan := deleteUnshardedShortcut(deleteStmt, ks, tables) + plan = pushCommentDirectivesOnPlan(plan, deleteStmt) + return newPlanResult(plan.Primitive(), operators.QualifiedTables(ks, tables)...), nil + } } if err := checkIfDeleteSupported(deleteStmt, semTable); err != nil { @@ -98,6 +101,24 @@ func gen4DeleteStmtPlanner( return newPlanResult(plan.Primitive(), operators.TablesUsed(op)...), nil } +func fkManagementNotRequired(vschema plancontext.VSchema, vTables []*vindexes.Table) bool { + // Find the foreign key mode and check for any managed child foreign keys. + for _, vTable := range vTables { + ksMode, err := vschema.ForeignKeyMode(vTable.Keyspace.Name) + if err != nil { + return false + } + if ksMode != vschemapb.Keyspace_FK_MANAGED { + continue + } + childFks := vTable.ChildFKsNeedsHandling() + if len(childFks) > 0 { + return false + } + } + return true +} + func rewriteSingleTbl(del *sqlparser.Delete) (*sqlparser.Delete, error) { atExpr, ok := del.TableExprs[0].(*sqlparser.AliasedTableExpr) if !ok { diff --git a/go/vt/vtgate/planbuilder/operators/ast2op.go b/go/vt/vtgate/planbuilder/operators/ast2op.go index d81670a4921..e2f16bcb9bf 100644 --- a/go/vt/vtgate/planbuilder/operators/ast2op.go +++ b/go/vt/vtgate/planbuilder/operators/ast2op.go @@ -191,6 +191,17 @@ func createOperatorFromDelete(ctx *plancontext.PlanningContext, deleteStmt *sqlp Routing: routing, } + ksMode, err := ctx.VSchema.ForeignKeyMode(vindexTable.Keyspace.Name) + if err != nil { + return nil, err + } + if ksMode == vschemapb.Keyspace_FK_MANAGED { + childFks := vindexTable.ChildFKsNeedsHandling() + if len(childFks) > 0 { + return nil, vterrors.VT12003() + } + } + if !vindexTable.Keyspace.Sharded { return route, nil } diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go index c8cdfbf7a0a..75966d11d56 100644 --- a/go/vt/vtgate/planbuilder/plan_test.go +++ b/go/vt/vtgate/planbuilder/plan_test.go @@ -435,25 +435,33 @@ func loadSchema(t testing.TB, filename string, setCollation bool) *vindexes.VSch } if vschema.Keyspaces["user_fk_allow"] != nil { // FK from multicol_tbl2 referencing multicol_tbl1 that is shard scoped. - err = vschema.AddForeignKey("user_fk_allow", "multicol_tbl2", createFkDefinition([]string{"colb", "cola", "x", "colc", "y"}, "multicol_tbl1", []string{"colb", "cola", "y", "colc", "x"})) + err = vschema.AddForeignKey("user_fk_allow", "multicol_tbl2", createFkDefinition([]string{"colb", "cola", "x", "colc", "y"}, "multicol_tbl1", []string{"colb", "cola", "y", "colc", "x"}, sqlparser.Cascade, sqlparser.Cascade)) require.NoError(t, err) // FK from tbl2 referencing tbl1 that is shard scoped. - err = vschema.AddForeignKey("user_fk_allow", "tbl2", createFkDefinition([]string{"col2"}, "tbl1", []string{"col1"})) + err = vschema.AddForeignKey("user_fk_allow", "tbl2", createFkDefinition([]string{"col2"}, "tbl1", []string{"col1"}, sqlparser.Restrict, sqlparser.Restrict)) require.NoError(t, err) // FK from tbl3 referencing tbl1 that is not shard scoped. - err = vschema.AddForeignKey("user_fk_allow", "tbl3", createFkDefinition([]string{"coly"}, "tbl1", []string{"col1"})) + err = vschema.AddForeignKey("user_fk_allow", "tbl3", createFkDefinition([]string{"coly"}, "tbl1", []string{"col1"}, sqlparser.DefaultAction, sqlparser.DefaultAction)) + require.NoError(t, err) + // FK from tbl4 referencing tbl5 that is shard scoped. + err = vschema.AddForeignKey("user_fk_allow", "tbl4", createFkDefinition([]string{"col4"}, "tbl5", []string{"col5"}, sqlparser.SetNull, sqlparser.SetNull)) + require.NoError(t, err) + // FK from tbl6 referencing tbl7 that is shard scoped. + err = vschema.AddForeignKey("user_fk_allow", "tbl6", createFkDefinition([]string{"col6"}, "tbl7", []string{"col7"}, sqlparser.NoAction, sqlparser.NoAction)) require.NoError(t, err) } return vschema } // createFkDefinition is a helper function to create a Foreign key definition struct from the columns used in it provided as list of strings. -func createFkDefinition(childCols []string, parentTableName string, parentCols []string) *sqlparser.ForeignKeyDefinition { +func createFkDefinition(childCols []string, parentTableName string, parentCols []string, onUpdate, onDelete sqlparser.ReferenceAction) *sqlparser.ForeignKeyDefinition { return &sqlparser.ForeignKeyDefinition{ Source: sqlparser.MakeColumns(childCols...), ReferenceDefinition: &sqlparser.ReferenceDefinition{ ReferencedTable: sqlparser.NewTableName(parentTableName), ReferencedColumns: sqlparser.MakeColumns(parentCols...), + OnUpdate: onUpdate, + OnDelete: onDelete, }, } } diff --git a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json index 3d244086667..aefdd6dfb50 100644 --- a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json @@ -53,5 +53,42 @@ "user_fk_allow.multicol_tbl2" ] } + }, + { + "comment": "Delete in a table with cross-shard foreign keys disallowed", + "query": "delete from tbl1", + "plan": "VT12002: unsupported: foreign keys management at vitess" + }, + { + "comment": "Delete in a table with shard-scoped foreign keys is allowed", + "query": "delete from tbl7", + "plan": { + "QueryType": "DELETE", + "Original": "delete from tbl7", + "Instructions": { + "OperatorType": "Delete", + "Variant": "Scatter", + "Keyspace": { + "Name": "user_fk_allow", + "Sharded": true + }, + "TargetTabletType": "PRIMARY", + "Query": "delete from tbl7", + "Table": "tbl7" + }, + "TablesUsed": [ + "user_fk_allow.tbl7" + ] + } + }, + { + "comment": "Delete in a table with shard-scoped multiple column foreign key with cascade not allowed", + "query": "delete from multicol_tbl1 where cola = 1 and colb = 2 and colc = 3", + "plan": "VT12002: unsupported: foreign keys management at vitess" + }, + { + "comment": "Delete in a table with shard-scoped foreign keys with cascade disallowed", + "query": "delete from tbl5", + "plan": "VT12002: unsupported: foreign keys management at vitess" } ] \ No newline at end of file diff --git a/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json b/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json index 1d23f669795..52f1c8da7a8 100644 --- a/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json +++ b/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json @@ -608,8 +608,7 @@ "foreignKeyMode": "FK_MANAGED", "vindexes": { "hash_vin": { - "type": "hash_test", - "owner": "user" + "type": "hash_test" }, "multicolIdx": { "type": "multiCol_test" @@ -663,6 +662,38 @@ "name": "hash_vin" } ] + }, + "tbl4": { + "column_vindexes": [ + { + "column": "col4", + "name": "hash_vin" + } + ] + }, + "tbl5": { + "column_vindexes": [ + { + "column": "col5", + "name": "hash_vin" + } + ] + }, + "tbl6": { + "column_vindexes": [ + { + "column": "col6", + "name": "hash_vin" + } + ] + }, + "tbl7": { + "column_vindexes": [ + { + "column": "col7", + "name": "hash_vin" + } + ] } } } diff --git a/go/vt/vtgate/vindexes/foreign_keys.go b/go/vt/vtgate/vindexes/foreign_keys.go index dca74d163d7..e18ed27ff25 100644 --- a/go/vt/vtgate/vindexes/foreign_keys.go +++ b/go/vt/vtgate/vindexes/foreign_keys.go @@ -125,42 +125,78 @@ func (t *Table) CrossShardParentFKs() (crossShardFKs []ParentFKInfo) { continue } - // If the primary vindexes don't match between the parent and child table, - // we cannot infer that the fk constraint in shard scoped. - primaryVindex := t.ColumnVindexes[0] - if fk.Table.ColumnVindexes[0].Vindex != primaryVindex.Vindex { + if !isShardScoped(fk.Table, t, fk.ParentColumns, fk.ChildColumns) { crossShardFKs = append(crossShardFKs, fk) - continue } + } + return +} - childFkContatined, childFkIndexes := fk.ChildColumns.Indexes(primaryVindex.Columns) - if !childFkContatined { - // PrimaryVindex is not part of the foreign key constraint on the children side. - // So it is a cross-shard foreign key. - crossShardFKs = append(crossShardFKs, fk) +// ChildFKsNeedsHandling retuns the child foreign keys that needs to be handled by the vtgate. +// This can be either the foreign key is not shard scoped or the child tables needs cascading. +func (t *Table) ChildFKsNeedsHandling() (fks []ChildFKInfo) { + for _, fk := range t.ChildForeignKeys { + // If the keyspaces are different, then the fk definition + // is going to go across shards. + if fk.Table.Keyspace.Name != t.Keyspace.Name { + fks = append(fks, fk) continue } - - // We need to run the same check for the parent columns. - parentFkContatined, parentFkIndexes := fk.ParentColumns.Indexes(fk.Table.ColumnVindexes[0].Columns) - if !parentFkContatined { - crossShardFKs = append(crossShardFKs, fk) + // If the action is not Restrict, then it needs a cascade. + switch fk.OnDelete { + case sqlparser.Cascade, sqlparser.SetNull, sqlparser.SetDefault: + fks = append(fks, fk) continue } - - // Both the child and parent table contain the foreign key and that the vindexes are the same, - // now we need to make sure, that the indexes of both match. - // For example, consider the following tables, - // t1 (primary vindex (x,y)) - // t2 (primary vindex (a,b)) - // If we have a foreign key constraint from t1(x,y) to t2(b,a), then they are not shard scoped. - // Let's say in t1, (1,3) will be in -80 and (3,1) will be in 80-, then in t2 (1,3) will end up in 80-. - for i := range parentFkIndexes { - if parentFkIndexes[i] != childFkIndexes[i] { - crossShardFKs = append(crossShardFKs, fk) - break - } + // sqlparser.Restrict, sqlparser.NoAction, sqlparser.DefaultAction + // all the actions means the same thing i.e. Restrict + // do not allow modification if there is a child row. + // Check if the restrict is shard scoped. + if !isShardScoped(t, fk.Table, fk.ParentColumns, fk.ChildColumns) { + fks = append(fks, fk) } } return } + +func isShardScoped(pTable *Table, cTable *Table, pCols sqlparser.Columns, cCols sqlparser.Columns) bool { + if !pTable.Keyspace.Sharded { + return true + } + + pPrimaryVdx := pTable.ColumnVindexes[0] + cPrimaryVdx := cTable.ColumnVindexes[0] + + // If the primary vindexes don't match between the parent and child table, + // we cannot infer that the fk constraint in shard scoped. + if cPrimaryVdx.Vindex != pPrimaryVdx.Vindex { + return false + } + + childFkContatined, childFkIndexes := cCols.Indexes(cPrimaryVdx.Columns) + if !childFkContatined { + // PrimaryVindex is not part of the foreign key constraint on the children side. + // So it is a cross-shard foreign key. + return false + } + + // We need to run the same check for the parent columns. + parentFkContatined, parentFkIndexes := pCols.Indexes(pPrimaryVdx.Columns) + if !parentFkContatined { + return false + } + + // Both the child and parent table contain the foreign key and that the vindexes are the same, + // now we need to make sure, that the indexes of both match. + // For example, consider the following tables, + // t1 (primary vindex (x,y)) + // t2 (primary vindex (a,b)) + // If we have a foreign key constraint from t1(x,y) to t2(b,a), then they are not shard scoped. + // Let's say in t1, (1,3) will be in -80 and (3,1) will be in 80-, then in t2 (1,3) will end up in 80-. + for i := range parentFkIndexes { + if parentFkIndexes[i] != childFkIndexes[i] { + return false + } + } + return true +} diff --git a/go/vt/vtgate/vindexes/foreign_keys_test.go b/go/vt/vtgate/vindexes/foreign_keys_test.go index b56bdf2f062..8a21e6909d0 100644 --- a/go/vt/vtgate/vindexes/foreign_keys_test.go +++ b/go/vt/vtgate/vindexes/foreign_keys_test.go @@ -154,3 +154,112 @@ func pkInfo(parentTable *Table, pCols []string, cCols []string) ParentFKInfo { ChildColumns: sqlparser.MakeColumns(cCols...), } } + +// TestChildFKs tests the ChildFKsNeedsHandling method is provides the child foreign key table whose +// rows needs to be managed by vitess. +func TestChildFKs(t *testing.T) { + col1Vindex := &ColumnVindex{ + Name: "v1", + Vindex: binVindex, + Columns: sqlparser.MakeColumns("col1"), + } + col4DiffVindex := &ColumnVindex{ + Name: "v2", + Vindex: binOnlyVindex, + Columns: sqlparser.MakeColumns("col4"), + } + + unshardedTbl := &Table{ + Name: sqlparser.NewIdentifierCS("t1"), + Keyspace: uks2, + } + shardedSingleColTbl := &Table{ + Name: sqlparser.NewIdentifierCS("t1"), + Keyspace: sks, + ColumnVindexes: []*ColumnVindex{col1Vindex}, + } + shardedSingleColTblWithDiffVindex := &Table{ + Name: sqlparser.NewIdentifierCS("t1"), + Keyspace: sks, + ColumnVindexes: []*ColumnVindex{col4DiffVindex}, + } + + tests := []struct { + name string + table *Table + expChildTbls []string + }{{ + name: "No Parent FKs", + table: &Table{ + ColumnVindexes: []*ColumnVindex{col1Vindex}, + Keyspace: sks, + }, + expChildTbls: []string{}, + }, { + name: "restrict unsharded", + table: &Table{ + ColumnVindexes: []*ColumnVindex{col1Vindex}, + Keyspace: uks2, + ChildForeignKeys: []ChildFKInfo{ckInfo(unshardedTbl, []string{"col4"}, []string{"col1"}, sqlparser.Restrict)}, + }, + expChildTbls: []string{}, + }, { + name: "restrict shard scoped", + table: &Table{ + ColumnVindexes: []*ColumnVindex{col1Vindex}, + Keyspace: sks, + ChildForeignKeys: []ChildFKInfo{ckInfo(shardedSingleColTbl, []string{"col1"}, []string{"col1"}, sqlparser.Restrict)}, + }, + expChildTbls: []string{}, + }, { + name: "restrict Keyspaces don't match", + table: &Table{ + Keyspace: uks, + ChildForeignKeys: []ChildFKInfo{ckInfo(shardedSingleColTbl, []string{"col1"}, []string{"col1"}, sqlparser.Restrict)}, + }, + expChildTbls: []string{"t1"}, + }, { + name: "restrict cross shard", + table: &Table{ + Keyspace: sks, + ColumnVindexes: []*ColumnVindex{col1Vindex}, + ChildForeignKeys: []ChildFKInfo{ckInfo(shardedSingleColTblWithDiffVindex, []string{"col4"}, []string{"col1"}, sqlparser.Restrict)}, + }, + expChildTbls: []string{"t1"}, + }, { + name: "cascade unsharded", + table: &Table{ + Keyspace: uks2, + ColumnVindexes: []*ColumnVindex{col1Vindex}, + ChildForeignKeys: []ChildFKInfo{ckInfo(unshardedTbl, []string{"col4"}, []string{"col1"}, sqlparser.Cascade)}, + }, + expChildTbls: []string{"t1"}, + }, { + name: "cascade cross shard", + table: &Table{ + Keyspace: sks, + ColumnVindexes: []*ColumnVindex{col1Vindex}, + ChildForeignKeys: []ChildFKInfo{ckInfo(shardedSingleColTblWithDiffVindex, []string{"col4"}, []string{"col1"}, sqlparser.Cascade)}, + }, + expChildTbls: []string{"t1"}, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + childFks := tt.table.ChildFKsNeedsHandling() + var actualChildTbls []string + for _, fk := range childFks { + actualChildTbls = append(actualChildTbls, fk.Table.Name.String()) + } + require.ElementsMatch(t, tt.expChildTbls, actualChildTbls) + }) + } +} + +func ckInfo(cTable *Table, pCols []string, cCols []string, refAction sqlparser.ReferenceAction) ChildFKInfo { + return ChildFKInfo{ + Table: cTable, + ParentColumns: sqlparser.MakeColumns(pCols...), + ChildColumns: sqlparser.MakeColumns(cCols...), + OnDelete: refAction, + } +}