Skip to content

Commit dd8b581

Browse files
strong guards (some panics) against any calls to .OriginalTableName or things like "GetTargetTableName" in move-tables mode
1 parent 79394c2 commit dd8b581

7 files changed

Lines changed: 123 additions & 41 deletions

File tree

go/base/context.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,9 @@ func getSafeTableName(baseName string, suffix string) string {
554554
// GetGhostTableName generates the name of ghost table, based on original table name
555555
// or a given table name
556556
func (mctx *MigrationContext) GetGhostTableName() string {
557+
if mctx.IsMoveTablesMode() {
558+
panic("GetGhostTableName() must not be called in move-tables mode; there is no ghost table (the target keeps each migrated table's name)")
559+
}
557560
if mctx.Revert {
558561
// When reverting the "ghost" table is the _del table from the original migration.
559562
return mctx.OldTableName
@@ -567,8 +570,12 @@ func (mctx *MigrationContext) GetGhostTableName() string {
567570

568571
// GetTargetTableName generates the name of the target table. In move-tables mode
569572
// each table keeps its own name on the target, so there is no single target
570-
// table name; per-table code uses MoveTable.TargetTableName instead.
573+
// table name; per-table code uses MoveTable.TargetTableName instead, and calling
574+
// this is a programmer error that panics to fail fast.
571575
func (mctx *MigrationContext) GetTargetTableName() string {
576+
if mctx.IsMoveTablesMode() {
577+
panic("GetTargetTableName() must not be called in move-tables mode; use MoveTable.TargetTableName")
578+
}
572579
return mctx.GetGhostTableName()
573580
}
574581

@@ -583,6 +590,9 @@ func (mctx *MigrationContext) GetTargetDatabaseName() string {
583590

584591
// GetOldTableName generates the name of the "old" table, into which the original table is renamed.
585592
func (mctx *MigrationContext) GetOldTableName() string {
593+
if mctx.IsMoveTablesMode() {
594+
panic("GetOldTableName() must not be called in move-tables mode; use MoveTableDelName(tableName) for each migrated table's `_<table>_del` rollback handle")
595+
}
586596
var tableName string
587597
if mctx.ForceTmpTableName != "" {
588598
tableName = mctx.ForceTmpTableName
@@ -624,6 +634,9 @@ func (mctx *MigrationContext) MoveTableDelName(tableName string) string {
624634
// GetChangelogTableName generates the name of changelog table, based on original table name
625635
// or a given table name.
626636
func (mctx *MigrationContext) GetChangelogTableName() string {
637+
if mctx.IsMoveTablesMode() {
638+
panic("GetChangelogTableName() must not be called in move-tables mode; there is no changelog table (§1.2)")
639+
}
627640
if mctx.ForceTmpTableName != "" {
628641
return getSafeTableName(mctx.ForceTmpTableName, "ghc")
629642
} else {
@@ -644,12 +657,6 @@ func (mctx *MigrationContext) GetCheckpointTableName() string {
644657
return getSafeTableName(mctx.OriginalTableName, "ghk")
645658
}
646659

647-
// GetVoluntaryLockName returns a name of a voluntary lock to be used throughout
648-
// the swap-tables process.
649-
func (mctx *MigrationContext) GetVoluntaryLockName() string {
650-
return fmt.Sprintf("%s.%s.lock", mctx.DatabaseName, mctx.OriginalTableName)
651-
}
652-
653660
// RequiresBinlogFormatChange is `true` when the original binlog format isn't `ROW`
654661
func (mctx *MigrationContext) RequiresBinlogFormatChange() bool {
655662
return mctx.OriginalBinlogFormat != "ROW"

go/logic/applier.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,15 @@ func (apl *Applier) AcquireMigrationLock(ctx context.Context) error {
280280
// One advisory lock per run. In move-tables mode it is keyed on the
281281
// set-derived run token (not any single table) so two processes moving the
282282
// same set of tables collide, while a single-table run keeps its table-keyed
283-
// lock name.
284-
lockTable := apl.originalTableName()
283+
// lock name. lockSubject is a human-readable description used in contention
284+
// errors; neither branch consults the representative table accessor.
285+
var lockTable, lockSubject string
285286
if apl.migrationContext.IsMoveTablesMode() {
286287
lockTable = "movetables." + apl.migrationContext.MoveTablesRunToken()
288+
lockSubject = fmt.Sprintf("tables %v", apl.migrationContext.MoveTables.TableNames)
289+
} else {
290+
lockTable = apl.originalTableName()
291+
lockSubject = fmt.Sprintf("`%s`.`%s`", apl.migrationContext.DatabaseName, apl.originalTableName())
287292
}
288293
lockName := buildMigrationLockName(apl.migrationContext.GetTargetDatabaseName(), lockTable)
289294

@@ -322,11 +327,11 @@ func (apl *Applier) AcquireMigrationLock(ctx context.Context) error {
322327
conn.Close()
323328
lockDB.Close()
324329
if holderID.Valid {
325-
return fmt.Errorf("another gh-ost process is already migrating `%s`.`%s`: migration lock %s held by connection id %d",
326-
apl.migrationContext.DatabaseName, apl.originalTableName(), lockName, holderID.Int64)
330+
return fmt.Errorf("another gh-ost process is already migrating %s: migration lock %s held by connection id %d",
331+
lockSubject, lockName, holderID.Int64)
327332
}
328-
return fmt.Errorf("another gh-ost process is already migrating `%s`.`%s`: migration lock %s is held",
329-
apl.migrationContext.DatabaseName, apl.originalTableName(), lockName)
333+
return fmt.Errorf("another gh-ost process is already migrating %s: migration lock %s is held",
334+
lockSubject, lockName)
330335
}
331336

332337
apl.migrationLockConn = conn
@@ -598,7 +603,14 @@ func (apl *Applier) tableExists(tableName string) (tableFound bool) {
598603
return (m != nil)
599604
}
600605

606+
// originalTableName returns the single migrated table. It is a representative
607+
// accessor that has no meaning in move-tables mode (every table is handled
608+
// through its own MoveTable container), so calling it there is a programmer
609+
// error and panics to fail fast rather than silently operate on the wrong table.
601610
func (apl *Applier) originalTableName() string {
611+
if apl.migrationContext.IsMoveTablesMode() {
612+
panic("applier.originalTableName() must not be called in move-tables mode; use the per-table MoveTable container instead")
613+
}
602614
return apl.migrationContext.OriginalTableName
603615
}
604616

go/logic/applier_test.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -481,19 +481,31 @@ func (suite *ApplierTestSuite) TestFinalCleanupMoveTablesMode_SkipsDrops() {
481481
}
482482

483483
// initiateStreaming() requires a binlog-capable MySQL connection to call directly.
484-
// This test verifies IsMoveTablesMode() and that GetChangelogTableName() returns
485-
// a derivable name. A new streamer always starts with zero listeners; the real
486-
// proof that no changelog listener is registered comes from the full run not
487-
// failing on a nonexistent _ghc table.
484+
// This test verifies IsMoveTablesMode() and that no changelog table is referenced
485+
// in move-tables mode (§1.2): no `_ghc` table exists on the source or target
486+
// database. A new streamer always starts with zero listeners; the real proof that
487+
// no changelog listener is registered comes from the full run not failing on a
488+
// nonexistent _ghc table.
488489
func (suite *ApplierTestSuite) TestInitiateStreamingMoveTablesMode_NoChangelogListener() {
490+
ctx := context.Background()
489491
migrationContext := newTestMigrationContext()
490492
migrationContext.MoveTables.TableNames = []string{testMysqlTableName}
491493
migrationContext.MoveTables.TargetDatabase = testMysqlDatabaseOther
492494

493495
suite.Require().True(migrationContext.IsMoveTablesMode())
494496

495-
changelogTableName := migrationContext.GetChangelogTableName()
496-
suite.Require().NotEmpty(changelogTableName, "changelog table name should be derivable")
497+
// In move-tables mode there is no changelog table. Verify none exists on
498+
// either the source or target database (LIKE '%\_ghc' matches a literal
499+
// trailing "_ghc").
500+
for _, schema := range []string{testMysqlDatabase, testMysqlDatabaseOther} {
501+
var count int
502+
err := suite.db.QueryRowContext(ctx,
503+
`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = ? AND table_name LIKE '%\_ghc'`,
504+
schema,
505+
).Scan(&count)
506+
suite.Require().NoError(err)
507+
suite.Require().Equal(0, count, "no changelog (_ghc) table should exist in move-tables mode in schema %s", schema)
508+
}
497509

498510
streamer := NewEventsStreamer(migrationContext)
499511
suite.Require().Empty(streamer.listeners, "new streamer should have no listeners")

go/logic/hooks.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,32 @@ func NewHooksExecutor(migrationContext *base.MigrationContext) *HooksExecutor {
221221
func (he *HooksExecutor) applyEnvironmentVariables(extraVariables ...string) []string {
222222
env := os.Environ()
223223
env = append(env, fmt.Sprintf("GH_OST_DATABASE_NAME=%s", he.migrationContext.DatabaseName))
224-
tableNameEnv := he.migrationContext.OriginalTableName
224+
225+
var tableNameEnv string
225226
if he.migrationContext.IsMoveTablesMode() {
226-
// No representative table: report the full migrated set (target names equal
227-
// source names in move-tables mode).
228227
tableNameEnv = strings.Join(he.migrationContext.MoveTables.TableNames, ",")
228+
} else {
229+
tableNameEnv = he.migrationContext.OriginalTableName
229230
}
230231
env = append(env, fmt.Sprintf("GH_OST_TABLE_NAME=%s", tableNameEnv))
231-
env = append(env, fmt.Sprintf("GH_OST_GHOST_TABLE_NAME=%s", he.migrationContext.GetGhostTableName()))
232-
env = append(env, fmt.Sprintf("GH_OST_OLD_TABLE_NAME=%s", he.migrationContext.GetOldTableName()))
232+
var ghostTableNameEnv string
233+
var oldTableNameEnv string
234+
if he.migrationContext.IsMoveTablesMode() {
235+
// No ghost or old tables in move-tables mode: the destination keeps each
236+
// source table's name, and the rollback handles are the per-table
237+
// `_<table>_del` tables produced by the atomic cutover RENAME.
238+
ghostTableNameEnv = strings.Join(he.migrationContext.MoveTables.TableNames, ",")
239+
delNames := make([]string, 0, len(he.migrationContext.MoveTables.TableNames))
240+
for _, tableName := range he.migrationContext.MoveTables.TableNames {
241+
delNames = append(delNames, he.migrationContext.MoveTableDelName(tableName))
242+
}
243+
oldTableNameEnv = strings.Join(delNames, ",")
244+
} else {
245+
ghostTableNameEnv = he.migrationContext.GetGhostTableName()
246+
oldTableNameEnv = he.migrationContext.GetOldTableName()
247+
}
248+
env = append(env, fmt.Sprintf("GH_OST_GHOST_TABLE_NAME=%s", ghostTableNameEnv))
249+
env = append(env, fmt.Sprintf("GH_OST_OLD_TABLE_NAME=%s", oldTableNameEnv))
233250
env = append(env, fmt.Sprintf("GH_OST_DDL=%s", he.migrationContext.AlterStatement))
234251
env = append(env, fmt.Sprintf("GH_OST_ELAPSED_SECONDS=%f", he.migrationContext.ElapsedTime().Seconds()))
235252
env = append(env, fmt.Sprintf("GH_OST_ELAPSED_COPY_SECONDS=%f", he.migrationContext.ElapsedRowCopyTime().Seconds()))
@@ -266,13 +283,14 @@ func (he *HooksExecutor) applyEnvironmentVariables(extraVariables ...string) []s
266283
env = append(env, fmt.Sprintf("GH_OST_TABLES=%s", strings.Join(he.migrationContext.MoveTables.TableNames, ",")))
267284
}
268285
env = append(env, fmt.Sprintf("GH_OST_TARGET_DATABASE_NAME=%s", he.migrationContext.GetTargetDatabaseName()))
269-
targetTableNameEnv := he.migrationContext.GetTargetTableName()
286+
287+
var targetTableNameEnv string
270288
if he.migrationContext.IsMoveTablesMode() {
271-
// Target tables keep their source names; there is no single ghost table.
272289
targetTableNameEnv = strings.Join(he.migrationContext.MoveTables.TableNames, ",")
290+
} else {
291+
targetTableNameEnv = he.migrationContext.GetTargetTableName()
273292
}
274293
env = append(env, fmt.Sprintf("GH_OST_TARGET_TABLE_NAME=%s", targetTableNameEnv))
275-
276294
env = append(env, extraVariables...)
277295
return env
278296
}

go/logic/inspect.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ func (isp *Inspector) InspectOriginalTable() (err error) {
130130
}
131131

132132
func (isp *Inspector) originalTableName() string {
133+
if isp.migrationContext.IsMoveTablesMode() {
134+
panic("inspector.originalTableName() must not be called in move-tables mode; inspect each table via its name (e.g. validateTableFor/InspectMoveTable)")
135+
}
133136
return isp.migrationContext.OriginalTableName
134137
}
135138

go/logic/server.go

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,33 @@ func (srv *Server) onServerCommand(command string, writer *bufio.Writer) (err er
192192
return srv.migrationContext.Log.Errore(err)
193193
}
194194

195+
// commandArgMatchesMigration reports whether a table-name argument supplied with
196+
// an interactive command refers to this migration. The argument is optional and
197+
// acts as a courtesy safety check, so an operator who is connected to the wrong
198+
// gh-ost socket is rejected. In standard mode it must equal the single migrated
199+
// table; in move-tables mode it may be any one of the migrated tables.
200+
func (srv *Server) commandArgMatchesMigration(arg string) bool {
201+
if srv.migrationContext.IsMoveTablesMode() {
202+
for _, tableName := range srv.migrationContext.MoveTables.TableNames {
203+
if arg == tableName {
204+
return true
205+
}
206+
}
207+
return false
208+
}
209+
return arg == srv.migrationContext.OriginalTableName
210+
}
211+
212+
// migrationTargetDescription returns a human-readable description of the migrated
213+
// table(s), used in interactive-command messages. In move-tables mode it is the
214+
// comma-joined list of migrated tables; otherwise the single table name.
215+
func (srv *Server) migrationTargetDescription() string {
216+
if srv.migrationContext.IsMoveTablesMode() {
217+
return strings.Join(srv.migrationContext.MoveTables.TableNames, ",")
218+
}
219+
return srv.migrationContext.OriginalTableName
220+
}
221+
195222
// applyServerCommand parses and executes commands by user
196223
func (srv *Server) applyServerCommand(command string, writer *bufio.Writer) (printStatusRule PrintStatusRule, err error) {
197224
tokens := strings.SplitN(command, "=", 2)
@@ -387,9 +414,9 @@ help # This message
387414
}
388415
case "throttle", "pause", "suspend":
389416
{
390-
if arg != "" && arg != srv.migrationContext.OriginalTableName {
417+
if arg != "" && !srv.commandArgMatchesMigration(arg) {
391418
// User explicitly provided table name. This is a courtesy protection mechanism
392-
err := fmt.Errorf("user commanded 'throttle' on %s, but migrated table is %s; ignoring request", arg, srv.migrationContext.OriginalTableName)
419+
err := fmt.Errorf("user commanded 'throttle' on %s, but migrated table is %s; ignoring request", arg, srv.migrationTargetDescription())
393420
return NoPrintStatusRule, err
394421
}
395422
atomic.StoreInt64(&srv.migrationContext.ThrottleCommandedByUser, 1)
@@ -398,9 +425,9 @@ help # This message
398425
}
399426
case "no-throttle", "unthrottle", "resume", "continue":
400427
{
401-
if arg != "" && arg != srv.migrationContext.OriginalTableName {
428+
if arg != "" && !srv.commandArgMatchesMigration(arg) {
402429
// User explicitly provided table name. This is a courtesy protection mechanism
403-
err := fmt.Errorf("user commanded 'no-throttle' on %s, but migrated table is %s; ignoring request", arg, srv.migrationContext.OriginalTableName)
430+
err := fmt.Errorf("user commanded 'no-throttle' on %s, but migrated table is %s; ignoring request", arg, srv.migrationTargetDescription())
404431
return NoPrintStatusRule, err
405432
}
406433
atomic.StoreInt64(&srv.migrationContext.ThrottleCommandedByUser, 0)
@@ -425,9 +452,9 @@ help # This message
425452
err := fmt.Errorf("user commanded 'unpostpone' without specifying table name, but --force-named-cut-over is set")
426453
return NoPrintStatusRule, err
427454
}
428-
if arg != "" && arg != srv.migrationContext.OriginalTableName {
455+
if arg != "" && !srv.commandArgMatchesMigration(arg) {
429456
// User explicitly provided table name. This is a courtesy protection mechanism
430-
err := fmt.Errorf("user commanded 'unpostpone' on %s, but migrated table is %s; ignoring request", arg, srv.migrationContext.OriginalTableName)
457+
err := fmt.Errorf("user commanded 'unpostpone' on %s, but migrated table is %s; ignoring request", arg, srv.migrationTargetDescription())
431458
return NoPrintStatusRule, err
432459
}
433460
if atomic.LoadInt64(&srv.migrationContext.IsPostponingCutOver) > 0 {
@@ -444,9 +471,9 @@ help # This message
444471
err := fmt.Errorf("user commanded 'panic' without specifying table name, but --force-named-panic is set")
445472
return NoPrintStatusRule, err
446473
}
447-
if arg != "" && arg != srv.migrationContext.OriginalTableName {
474+
if arg != "" && !srv.commandArgMatchesMigration(arg) {
448475
// User explicitly provided table name. This is a courtesy protection mechanism
449-
err := fmt.Errorf("user commanded 'panic' on %s, but migrated table is %s; ignoring request", arg, srv.migrationContext.OriginalTableName)
476+
err := fmt.Errorf("user commanded 'panic' on %s, but migrated table is %s; ignoring request", arg, srv.migrationTargetDescription())
450477
return NoPrintStatusRule, err
451478
}
452479
err := fmt.Errorf("user commanded 'panic'. The migration will be aborted without cleanup. Please drop the gh-ost tables before trying again")

go/logic/throttler.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,15 @@ func (thlr *Throttler) collectControlReplicasLag() {
193193
return
194194
}
195195

196-
replicationLagQuery := fmt.Sprintf(`
197-
select value from %s.%s where hint = 'heartbeat' and id <= 255
198-
`,
199-
sql.EscapeName(thlr.migrationContext.DatabaseName),
200-
sql.EscapeName(thlr.migrationContext.GetChangelogTableName()),
201-
)
196+
var replicationLagQuery string
197+
if !thlr.migrationContext.IsMoveTablesMode() {
198+
replicationLagQuery = fmt.Sprintf(`
199+
select value from %s.%s where hint = 'heartbeat' and id <= 255
200+
`,
201+
sql.EscapeName(thlr.migrationContext.DatabaseName),
202+
sql.EscapeName(thlr.migrationContext.GetChangelogTableName()),
203+
)
204+
}
202205

203206
readReplicaLag := func(connectionConfig *mysql.ConnectionConfig) (lag time.Duration, err error) {
204207
dbUri := connectionConfig.GetDBUri("information_schema")

0 commit comments

Comments
 (0)