Skip to content

Commit 3aead9e

Browse files
authored
Online DDL: revert considerations for migrations with foreign key constraints (#14368)
Signed-off-by: Shlomi Noach <[email protected]>
1 parent a7a44c6 commit 3aead9e

File tree

15 files changed

+2423
-1974
lines changed

15 files changed

+2423
-1974
lines changed

go/test/endtoend/onlineddl/revert/onlineddl_revert_test.go

+85-3
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,11 @@ type revertibleTestCase struct {
125125
fromSchema string
126126
toSchema string
127127
// expectProblems bool
128+
removedForeignKeyNames string
128129
removedUniqueKeyNames string
129130
droppedNoDefaultColumnNames string
130131
expandedColumnNames string
132+
onlyIfFKOnlineDDLPossible bool
131133
}
132134

133135
func TestMain(m *testing.M) {
@@ -218,6 +220,25 @@ func TestSchemaChange(t *testing.T) {
218220

219221
func testRevertible(t *testing.T) {
220222

223+
fkOnlineDDLPossible := false
224+
t.Run("check 'rename_table_preserve_foreign_key' variable", func(t *testing.T) {
225+
// Online DDL is not possible on vanilla MySQL 8.0 for reasons described in https://vitess.io/blog/2021-06-15-online-ddl-why-no-fk/.
226+
// However, Online DDL is made possible in via these changes: https://github.com/planetscale/mysql-server/commit/bb777e3e86387571c044fb4a2beb4f8c60462ced
227+
// as part of https://github.com/planetscale/mysql-server/releases/tag/8.0.34-ps1.
228+
// Said changes introduce a new global/session boolean variable named 'rename_table_preserve_foreign_key'. It defaults 'false'/0 for backwards compatibility.
229+
// When enabled, a `RENAME TABLE` to a FK parent "pins" the children's foreign keys to the table name rather than the table pointer. Which means after the RENAME,
230+
// the children will point to the newly instated table rather than the original, renamed table.
231+
// (Note: this applies to a particular type of RENAME where we swap tables, see the above blog post).
232+
// For FK children, the MySQL changes simply ignore any Vitess-internal table.
233+
//
234+
// In this stress test, we enable Online DDL if the variable 'rename_table_preserve_foreign_key' is present. The Online DDL mechanism will in turn
235+
// query for this variable, and manipulate it, when starting the migration and when cutting over.
236+
rs, err := shards[0].Vttablets[0].VttabletProcess.QueryTablet("show global variables like 'rename_table_preserve_foreign_key'", keyspaceName, false)
237+
require.NoError(t, err)
238+
fkOnlineDDLPossible = len(rs.Rows) > 0
239+
t.Logf("MySQL support for 'rename_table_preserve_foreign_key': %v", fkOnlineDDLPossible)
240+
})
241+
221242
var testCases = []revertibleTestCase{
222243
{
223244
name: "identical schemas",
@@ -253,6 +274,20 @@ func testRevertible(t *testing.T) {
253274
toSchema: `id int primary key, i1 int default null, unique key i1_uidx(i1)`,
254275
removedUniqueKeyNames: ``,
255276
},
277+
{
278+
name: "removed foreign key",
279+
fromSchema: "id int primary key, i int, constraint some_fk_1 foreign key (i) references parent (id) on delete cascade",
280+
toSchema: "id int primary key, i int",
281+
removedForeignKeyNames: "some_fk_1",
282+
onlyIfFKOnlineDDLPossible: true,
283+
},
284+
285+
{
286+
name: "renamed foreign key",
287+
fromSchema: "id int primary key, i int, constraint f1 foreign key (i) references parent (id) on delete cascade",
288+
toSchema: "id int primary key, i int, constraint f2 foreign key (i) references parent (id) on delete cascade",
289+
onlyIfFKOnlineDDLPossible: true,
290+
},
256291
{
257292
name: "remove column without default",
258293
fromSchema: `id int primary key, i1 int not null`,
@@ -344,24 +379,30 @@ func testRevertible(t *testing.T) {
344379
dropTableStatement = `
345380
DROP TABLE onlineddl_test
346381
`
347-
tableName = "onlineddl_test"
348-
ddlStrategy = "online --declarative --allow-zero-in-date"
382+
tableName = "onlineddl_test"
383+
ddlStrategy = "online --declarative --allow-zero-in-date --unsafe-allow-foreign-keys"
384+
createParentTable = "create table parent (id int primary key)"
349385
)
350386

387+
onlineddl.VtgateExecQuery(t, &vtParams, createParentTable, "")
388+
351389
removeBackticks := func(s string) string {
352390
return strings.Replace(s, "`", "", -1)
353391
}
354392

355393
for _, testcase := range testCases {
356394
t.Run(testcase.name, func(t *testing.T) {
395+
if testcase.onlyIfFKOnlineDDLPossible && !fkOnlineDDLPossible {
396+
t.Skipf("skipped because backing database does not support 'rename_table_preserve_foreign_key'")
397+
return
398+
}
357399

358400
t.Run("ensure table dropped", func(t *testing.T) {
359401
// A preparation step, to clean up anything from the previous test case
360402
uuid := testOnlineDDLStatement(t, dropTableStatement, ddlStrategy, "vtgate", tableName, "")
361403
onlineddl.CheckMigrationStatus(t, &vtParams, shards, uuid, schema.OnlineDDLStatusComplete)
362404
checkTable(t, tableName, false)
363405
})
364-
365406
t.Run("create from-table", func(t *testing.T) {
366407
// A preparation step, to re-create the base table
367408
fromStatement := fmt.Sprintf(createTableWrapper, testcase.fromSchema)
@@ -382,17 +423,56 @@ func testRevertible(t *testing.T) {
382423
rs := onlineddl.ReadMigrations(t, &vtParams, uuid)
383424
require.NotNil(t, rs)
384425
for _, row := range rs.Named().Rows {
426+
removedForeignKeyNames := row.AsString("removed_foreign_key_names", "")
385427
removedUniqueKeyNames := row.AsString("removed_unique_key_names", "")
386428
droppedNoDefaultColumnNames := row.AsString("dropped_no_default_column_names", "")
387429
expandedColumnNames := row.AsString("expanded_column_names", "")
388430

431+
assert.Equal(t, testcase.removedForeignKeyNames, removeBackticks(removedForeignKeyNames))
389432
assert.Equal(t, testcase.removedUniqueKeyNames, removeBackticks(removedUniqueKeyNames))
390433
assert.Equal(t, testcase.droppedNoDefaultColumnNames, removeBackticks(droppedNoDefaultColumnNames))
391434
assert.Equal(t, testcase.expandedColumnNames, removeBackticks(expandedColumnNames))
392435
}
393436
})
394437
})
395438
}
439+
440+
t.Run("drop fk child table", func(t *testing.T) {
441+
t.Run("ensure table dropped", func(t *testing.T) {
442+
// A preparation step, to clean up anything from the previous test case
443+
uuid := testOnlineDDLStatement(t, dropTableStatement, ddlStrategy, "vtgate", tableName, "")
444+
onlineddl.CheckMigrationStatus(t, &vtParams, shards, uuid, schema.OnlineDDLStatusComplete)
445+
checkTable(t, tableName, false)
446+
})
447+
t.Run("create child table", func(t *testing.T) {
448+
fromStatement := fmt.Sprintf(createTableWrapper, "id int primary key, i int, constraint some_fk_2 foreign key (i) references parent (id) on delete cascade")
449+
uuid := testOnlineDDLStatement(t, fromStatement, ddlStrategy, "vtgate", tableName, "")
450+
onlineddl.CheckMigrationStatus(t, &vtParams, shards, uuid, schema.OnlineDDLStatusComplete)
451+
checkTable(t, tableName, true)
452+
})
453+
var uuid string
454+
t.Run("drop", func(t *testing.T) {
455+
uuid = testOnlineDDLStatement(t, dropTableStatement, ddlStrategy, "vtgate", tableName, "")
456+
onlineddl.CheckMigrationStatus(t, &vtParams, shards, uuid, schema.OnlineDDLStatusComplete)
457+
checkTable(t, tableName, false)
458+
})
459+
t.Run("check migration", func(t *testing.T) {
460+
// All right, the actual test
461+
rs := onlineddl.ReadMigrations(t, &vtParams, uuid)
462+
require.NotNil(t, rs)
463+
for _, row := range rs.Named().Rows {
464+
removedForeignKeyNames := row.AsString("removed_foreign_key_names", "")
465+
removedUniqueKeyNames := row.AsString("removed_unique_key_names", "")
466+
droppedNoDefaultColumnNames := row.AsString("dropped_no_default_column_names", "")
467+
expandedColumnNames := row.AsString("expanded_column_names", "")
468+
469+
assert.Equal(t, "some_fk_2", removeBackticks(removedForeignKeyNames))
470+
assert.Equal(t, "", removeBackticks(removedUniqueKeyNames))
471+
assert.Equal(t, "", removeBackticks(droppedNoDefaultColumnNames))
472+
assert.Equal(t, "", removeBackticks(expandedColumnNames))
473+
}
474+
})
475+
})
396476
}
397477

398478
func testRevert(t *testing.T) {
@@ -964,6 +1044,8 @@ func testRevert(t *testing.T) {
9641044
assert.Empty(t, specialPlan)
9651045
assert.NotEmpty(t, artifacts)
9661046
}
1047+
removedForeignKeyNames := row.AsString("removed_foreign_key_names", "")
1048+
assert.Empty(t, removedForeignKeyNames)
9671049
})
9681050
t.Run("INSTANT DDL: fail revert", func(t *testing.T) {
9691051
uuid := testRevertMigration(t, uuids[len(uuids)-1], ddlStrategy)

0 commit comments

Comments
 (0)