Skip to content

Commit d5bd597

Browse files
authored
DDL strategy flag --unsafe-allow-foreign-keys implies setting FOREIGN_KEY_CHECKS=0 (#15432)
Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com>
1 parent 2830a07 commit d5bd597

File tree

5 files changed

+71
-12
lines changed

5 files changed

+71
-12
lines changed

go/test/endtoend/onlineddl/scheduler/onlineddl_scheduler_test.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2214,6 +2214,7 @@ func testForeignKeys(t *testing.T) {
22142214
sql string
22152215
allowForeignKeys bool
22162216
expectHint string
2217+
expectCountUUIDs int
22172218
onlyIfFKOnlineDDLPossible bool
22182219
}
22192220
var testCases = []testCase{
@@ -2286,6 +2287,16 @@ func testForeignKeys(t *testing.T) {
22862287
expectHint: "child_hint",
22872288
onlyIfFKOnlineDDLPossible: true,
22882289
},
2290+
{
2291+
name: "add two tables with cyclic fk relationship",
2292+
sql: `
2293+
create table t11 (id int primary key, i int, constraint f11 foreign key (i) references t12 (id));
2294+
create table t12 (id int primary key, i int, constraint f12 foreign key (i) references t11 (id));
2295+
`,
2296+
allowForeignKeys: true,
2297+
expectCountUUIDs: 2,
2298+
expectHint: "t11",
2299+
},
22892300
}
22902301

22912302
fkOnlineDDLPossible := false
@@ -2328,6 +2339,9 @@ func testForeignKeys(t *testing.T) {
23282339
return testOnlineDDLStatement(t, createParams(sql, ddlStrategy, "vtctl", expectHint, errorHint, false))
23292340
}
23302341
for _, testcase := range testCases {
2342+
if testcase.expectCountUUIDs == 0 {
2343+
testcase.expectCountUUIDs = 1
2344+
}
23312345
t.Run(testcase.name, func(t *testing.T) {
23322346
if testcase.onlyIfFKOnlineDDLPossible && !fkOnlineDDLPossible {
23332347
t.Skipf("skipped because backing database does not support 'rename_table_preserve_foreign_key'")
@@ -2364,7 +2378,10 @@ func testForeignKeys(t *testing.T) {
23642378
var uuid string
23652379
t.Run("run migration", func(t *testing.T) {
23662380
if testcase.allowForeignKeys {
2367-
uuid = testStatement(t, testcase.sql, ddlStrategyAllowFK, testcase.expectHint, false)
2381+
output := testStatement(t, testcase.sql, ddlStrategyAllowFK, testcase.expectHint, false)
2382+
uuids := strings.Split(output, "\n")
2383+
assert.Equal(t, testcase.expectCountUUIDs, len(uuids))
2384+
uuid = uuids[0] // in case of multiple statements, we only check the first
23682385
onlineddl.CheckMigrationStatus(t, &vtParams, shards, uuid, schema.OnlineDDLStatusComplete)
23692386
} else {
23702387
uuid = testStatement(t, testcase.sql, ddlStrategy, "", true)
@@ -2384,7 +2401,7 @@ func testForeignKeys(t *testing.T) {
23842401
artifacts = textutil.SplitDelimitedList(row.AsString("artifacts", ""))
23852402
}
23862403

2387-
artifacts = append(artifacts, "child_table", "child_nofk_table", "parent_table")
2404+
artifacts = append(artifacts, "child_table", "child_nofk_table", "parent_table", "t11", "t12")
23882405
// brute force drop all tables. In MySQL 8.0 you can do a single `DROP TABLE ... <list of all tables>`
23892406
// which auto-resovled order. But in 5.7 you can't.
23902407
droppedTables := map[string]bool{}
@@ -2394,7 +2411,7 @@ func testForeignKeys(t *testing.T) {
23942411
continue
23952412
}
23962413
statement := fmt.Sprintf("DROP TABLE IF EXISTS %s", artifact)
2397-
_, err := clusterInstance.VtctldClientProcess.ApplySchemaWithOutput(keyspaceName, statement, cluster.ApplySchemaParams{DDLStrategy: "direct"})
2414+
_, err := clusterInstance.VtctldClientProcess.ApplySchemaWithOutput(keyspaceName, statement, cluster.ApplySchemaParams{DDLStrategy: "direct --unsafe-allow-foreign-keys"})
23982415
if err == nil {
23992416
droppedTables[artifact] = true
24002417
}

go/test/endtoend/vtgate/schema/schema_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ func TestSchemaChange(t *testing.T) {
108108
testWithDropCreateSchema(t)
109109
testDropNonExistentTables(t)
110110
testApplySchemaBatch(t)
111+
testUnsafeAllowForeignKeys(t)
111112
testCreateInvalidView(t)
112113
testCopySchemaShards(t, clusterInstance.Keyspaces[0].Shards[0].Vttablets[0].VttabletProcess.TabletPath, 2)
113114
testCopySchemaShards(t, fmt.Sprintf("%s/0", keyspaceName), 3)
@@ -252,6 +253,28 @@ func testApplySchemaBatch(t *testing.T) {
252253
}
253254
}
254255

256+
func testUnsafeAllowForeignKeys(t *testing.T) {
257+
sqls := `
258+
create table t11 (id int primary key, i int, constraint f1101 foreign key (i) references t12 (id) on delete restrict);
259+
create table t12 (id int primary key, i int, constraint f1201 foreign key (i) references t11 (id) on delete set null);
260+
`
261+
{
262+
_, err := clusterInstance.VtctldClientProcess.ExecuteCommandWithOutput("ApplySchema", "--ddl-strategy", "direct --allow-zero-in-date", "--sql", sqls, keyspaceName)
263+
assert.Error(t, err)
264+
checkTables(t, totalTableCount)
265+
}
266+
{
267+
_, err := clusterInstance.VtctldClientProcess.ExecuteCommandWithOutput("ApplySchema", "--ddl-strategy", "direct --unsafe-allow-foreign-keys --allow-zero-in-date", "--sql", sqls, keyspaceName)
268+
require.NoError(t, err)
269+
checkTables(t, totalTableCount+2)
270+
}
271+
{
272+
_, err := clusterInstance.VtctldClientProcess.ExecuteCommandWithOutput("ApplySchema", "--sql", "drop table t11, t12", keyspaceName)
273+
require.NoError(t, err)
274+
checkTables(t, totalTableCount)
275+
}
276+
}
277+
255278
// checkTables checks the number of tables in the first two shards.
256279
func checkTables(t *testing.T, count int) {
257280
checkTablesCount(t, clusterInstance.Keyspaces[0].Shards[0].Vttablets[0], count)

go/vt/schemamanager/tablet_executor.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,10 +490,14 @@ func (exec *TabletExecutor) executeOneTablet(
490490
return
491491
}
492492
}
493-
result, err = exec.tmc.ExecuteFetchAsDba(ctx, tablet, false, &tabletmanagerdatapb.ExecuteFetchAsDbaRequest{
493+
request := &tabletmanagerdatapb.ExecuteFetchAsDbaRequest{
494494
Query: []byte(sql),
495495
MaxRows: 10,
496-
})
496+
}
497+
if exec.ddlStrategySetting != nil && exec.ddlStrategySetting.IsAllowForeignKeysFlag() {
498+
request.DisableForeignKeyChecks = true
499+
}
500+
result, err = exec.tmc.ExecuteFetchAsDba(ctx, tablet, false, request)
497501

498502
}
499503
if err != nil {

go/vt/vttablet/onlineddl/executor.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,21 @@ func (e *Executor) executeDirectly(ctx context.Context, onlineDDL *schema.Online
646646
}
647647

648648
_ = e.onSchemaMigrationStatus(ctx, onlineDDL.UUID, schema.OnlineDDLStatusRunning, false, progressPctStarted, etaSecondsUnknown, rowsCopiedUnknown, emptyHint)
649+
if onlineDDL.StrategySetting().IsAllowForeignKeysFlag() {
650+
// Foreign key support is curently "unsafe". We further put the burden on the user
651+
// by disabling foreign key checks. With this, the user is able to create cyclic
652+
// foreign key references (e.g. t1<->t2) without going through the trouble of
653+
// CREATE TABLE t1->CREATE TABLE t2->ALTER TABLE t1 ADD FOREIGN KEY ... REFERENCES ts
654+
// Grab current sql_mode value
655+
if _, err := conn.ExecuteFetch(`set @vt_onlineddl_foreign_key_checks=@@foreign_key_checks`, 0, false); err != nil {
656+
return false, vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "could not read foreign_key_checks: %v", err)
657+
}
658+
_, err = conn.ExecuteFetch("SET foreign_key_checks=0", 0, false)
659+
if err != nil {
660+
return false, err
661+
}
662+
defer conn.ExecuteFetch("SET foreign_key_checks=@vt_onlineddl_foreign_key_checks", 0, false)
663+
}
649664
_, err = conn.ExecuteFetch(onlineDDL.SQL, 0, false)
650665

651666
if err != nil {
@@ -1288,7 +1303,7 @@ func (e *Executor) newConstraintName(onlineDDL *schema.OnlineDDL, constraintType
12881303
// validateAndEditCreateTableStatement inspects the CreateTable AST and does the following:
12891304
// - extra validation (no FKs for now...)
12901305
// - generate new and unique names for all constraints (CHECK and FK; yes, why not handle FK names; even as we don't support FKs today, we may in the future)
1291-
func (e *Executor) validateAndEditCreateTableStatement(ctx context.Context, onlineDDL *schema.OnlineDDL, createTable *sqlparser.CreateTable) (constraintMap map[string]string, err error) {
1306+
func (e *Executor) validateAndEditCreateTableStatement(onlineDDL *schema.OnlineDDL, createTable *sqlparser.CreateTable) (constraintMap map[string]string, err error) {
12921307
constraintMap = map[string]string{}
12931308
hashExists := map[string]bool{}
12941309

@@ -1315,7 +1330,7 @@ func (e *Executor) validateAndEditCreateTableStatement(ctx context.Context, onli
13151330
// validateAndEditAlterTableStatement inspects the AlterTable statement and:
13161331
// - modifies any CONSTRAINT name according to given name mapping
13171332
// - explode ADD FULLTEXT KEY into multiple statements
1318-
func (e *Executor) validateAndEditAlterTableStatement(ctx context.Context, capableOf capabilities.CapableOf, onlineDDL *schema.OnlineDDL, alterTable *sqlparser.AlterTable, constraintMap map[string]string) (alters []*sqlparser.AlterTable, err error) {
1333+
func (e *Executor) validateAndEditAlterTableStatement(capableOf capabilities.CapableOf, onlineDDL *schema.OnlineDDL, alterTable *sqlparser.AlterTable, constraintMap map[string]string) (alters []*sqlparser.AlterTable, err error) {
13191334
capableOfInstantDDLXtrabackup, err := capableOf(capabilities.InstantDDLXtrabackupCapability)
13201335
if err != nil {
13211336
return nil, err
@@ -1405,7 +1420,7 @@ func (e *Executor) duplicateCreateTable(ctx context.Context, onlineDDL *schema.O
14051420
newCreateTable.SetTable(newCreateTable.GetTable().Qualifier.CompliantName(), newTableName)
14061421
// manipulate CreateTable statement: take care of constraints names which have to be
14071422
// unique across the schema
1408-
constraintMap, err = e.validateAndEditCreateTableStatement(ctx, onlineDDL, newCreateTable)
1423+
constraintMap, err = e.validateAndEditCreateTableStatement(onlineDDL, newCreateTable)
14091424
if err != nil {
14101425
return nil, nil, nil, err
14111426
}
@@ -1475,7 +1490,7 @@ func (e *Executor) initVreplicationOriginalMigration(ctx context.Context, online
14751490
// Also, change any constraint names:
14761491

14771492
capableOf := mysql.ServerVersionCapableOf(conn.ServerVersion)
1478-
alters, err := e.validateAndEditAlterTableStatement(ctx, capableOf, onlineDDL, alterTable, constraintMap)
1493+
alters, err := e.validateAndEditAlterTableStatement(capableOf, onlineDDL, alterTable, constraintMap)
14791494
if err != nil {
14801495
return v, err
14811496
}
@@ -2995,7 +3010,7 @@ func (e *Executor) executeCreateDDLActionMigration(ctx context.Context, onlineDD
29953010
newCreateTable := sqlparser.CloneRefOfCreateTable(originalCreateTable)
29963011
// Rewrite this CREATE TABLE statement such that CONSTRAINT names are edited,
29973012
// specifically removing any <tablename> prefix.
2998-
if _, err := e.validateAndEditCreateTableStatement(ctx, onlineDDL, newCreateTable); err != nil {
3013+
if _, err := e.validateAndEditCreateTableStatement(onlineDDL, newCreateTable); err != nil {
29993014
return failMigration(err)
30003015
}
30013016
ddlStmt = newCreateTable

go/vt/vttablet/onlineddl/executor_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ func TestValidateAndEditCreateTableStatement(t *testing.T) {
172172
require.True(t, ok)
173173

174174
onlineDDL := &schema.OnlineDDL{UUID: "a5a563da_dc1a_11ec_a416_0a43f95f28a3", Table: "onlineddl_test", Options: tc.strategyOptions}
175-
constraintMap, err := e.validateAndEditCreateTableStatement(context.Background(), onlineDDL, createTable)
175+
constraintMap, err := e.validateAndEditCreateTableStatement(onlineDDL, createTable)
176176
if tc.expectError != "" {
177177
assert.ErrorContains(t, err, tc.expectError)
178178
return
@@ -290,7 +290,7 @@ func TestValidateAndEditAlterTableStatement(t *testing.T) {
290290
}
291291
capableOf := mysql.ServerVersionCapableOf(tc.mySQLVersion)
292292
onlineDDL := &schema.OnlineDDL{UUID: "a5a563da_dc1a_11ec_a416_0a43f95f28a3", Table: "t", Options: "--unsafe-allow-foreign-keys"}
293-
alters, err := e.validateAndEditAlterTableStatement(context.Background(), capableOf, onlineDDL, alterTable, m)
293+
alters, err := e.validateAndEditAlterTableStatement(capableOf, onlineDDL, alterTable, m)
294294
assert.NoError(t, err)
295295
var altersStrings []string
296296
for _, alter := range alters {

0 commit comments

Comments
 (0)