Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8c45ee1
Add CLI flags for move-tables
danieljoos May 15, 2026
1dc932b
Add switch for move-tables to main.go
danieljoos May 15, 2026
2b3fb21
First iteration of `MoveTables` migrator main function
danieljoos May 18, 2026
d8a4c2c
Add query builder for splitting up range-insert for move-tables feature
danieljoos May 15, 2026
a189729
Rename MoveTables... to MoveTable...
danieljoos May 15, 2026
5f7ff63
Add `ApplyIterationMoveTableCopyQueries` function to `Applier` to use…
danieljoos May 20, 2026
927e8cb
Add move-tables query builders to applier's prepareQueries method
danieljoos May 20, 2026
ac6f966
Call `ApplyIterationMoveTableCopyQueries` in migrator
danieljoos May 20, 2026
c151452
Adapt applier to support DML events for move tables
danieljoos May 27, 2026
d55a82f
[stash] skip ghost, changelog tables on move-tables
chriskirkland May 27, 2026
f336f8e
[WIP] flag parsing tweaks
chriskirkland May 29, 2026
bbde7b0
logging, notes debugging... halp
chriskirkland May 29, 2026
bda68c7
one more note
chriskirkland May 29, 2026
b7e87a8
seeds table in target, working up to DML events
chriskirkland May 31, 2026
bc7a24b
stash... debugging no row writes
chriskirkland Jun 1, 2026
fd68b3a
fewer errors, more debug
chriskirkland Jun 1, 2026
0508dd7
YAY
chriskirkland Jun 1, 2026
b140615
TODO
chriskirkland Jun 1, 2026
04c3ecf
[hacks] DEMO READY!
chriskirkland Jun 3, 2026
a87ab35
#8206: productionize move-tables 1.2 skip ghost/changelog/heartbeat
womoruyi Jun 5, 2026
9252fc2
#8206: productionize move-tables 1.2 skip ghost/changelog/heartbeat
womoruyi Jun 5, 2026
0dabde6
fix: address Copilot review findings on #8206 PR
womoruyi Jun 5, 2026
d249690
docs: add clarity comments to predicate-only tests per Copilot review
womoruyi Jun 5, 2026
5353a3b
fix: resolve golangci-lint errors in modified files
womoruyi Jun 5, 2026
dcecfd5
fix: last errorlint %v→%w in finalCleanup showCreateTable
womoruyi Jun 5, 2026
6ffb6ff
fix: address second Copilot review round
womoruyi Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions go/base/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,17 @@ type MigrationContext struct {
SkipMetadataLockCheck bool
IsOpenMetadataLockInstruments bool

// move tables:
MoveTables struct {
TableNames []string // List of table names to be moved.
TargetHost string // Target hostname for the move. This must be a primary/writable host.
TargetPort int // Target MySQL port for the move.
TargetUser string // Target username for the move. If not specified, it will default to the source user.
TargetPass string // Target password for the move. If not specified, it will default to the source password.
TargetDatabase string // Target database name for the move. If not specified, it will default to the source database name.
ConnectionConfig *mysql.ConnectionConfig
}

Log Logger
}

Expand Down Expand Up @@ -343,6 +354,9 @@ func (mctx *MigrationContext) SetConnectionConfig(storageEngine string) error {
}
mctx.InspectorConnectionConfig.TransactionIsolation = transactionIsolation
mctx.ApplierConnectionConfig.TransactionIsolation = transactionIsolation
if mctx.MoveTables.ConnectionConfig != nil {
mctx.MoveTables.ConnectionConfig.TransactionIsolation = transactionIsolation
}
return nil
}

Expand All @@ -353,6 +367,9 @@ func (mctx *MigrationContext) SetConnectionCharset(charset string) {

mctx.InspectorConnectionConfig.Charset = charset
mctx.ApplierConnectionConfig.Charset = charset
if mctx.MoveTables.ConnectionConfig != nil {
mctx.MoveTables.ConnectionConfig.Charset = charset
}
}

func getSafeTableName(baseName string, suffix string) string {
Expand All @@ -378,6 +395,24 @@ func (mctx *MigrationContext) GetGhostTableName() string {
}
}

// GetTargetTableName generates the name of the target table, based on original table name and
// the migration context (i.e. move-tables mode).
func (mctx *MigrationContext) GetTargetTableName() string {
if mctx.IsMoveTablesMode() {
return mctx.MoveTables.TableNames[0]
}
return mctx.GetGhostTableName()
}

// GetTargetDatabaseName fetches the name of the target database, which defaults to the original
// database name unless we're in move-tables mode.
func (mctx *MigrationContext) GetTargetDatabaseName() string {
if mctx.IsMoveTablesMode() {
return mctx.DatabaseName
}
return mctx.MoveTables.TargetDatabase
Comment on lines +410 to +413

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a heads up that this is flipped. I'm working on resolving the merge conflicts and noticed it

@womoruyi womoruyi Jun 11, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup!! @zacharysierakowski I noted it in my PR description, it needed to be fixed in 8207 (which I'll push up my branch for this)
Screenshot 2026-06-10 at 7 27 35 PM

we had noted this in the beginning but didn't want to make the change until we were in the right task for it

}

// GetOldTableName generates the name of the "old" table, into which the original table is renamed.
func (mctx *MigrationContext) GetOldTableName() string {
var tableName string
Expand Down Expand Up @@ -900,8 +935,15 @@ func (mctx *MigrationContext) AddThrottleControlReplicaKey(key mysql.InstanceKey
return nil
}

// TODO(chriskirkland): pick up from here:
//
// need to figure out how to populate the applier connection config from
// the inspector connection config but using the "target" values from the CLI
// args.

// ApplyCredentials sorts out the credentials between the config file and the CLI flags
func (mctx *MigrationContext) ApplyCredentials() {
//TODO(chriskirkland): make this work for move-tables
mctx.configMutex.Lock()
defer mctx.configMutex.Unlock()

Expand All @@ -919,9 +961,23 @@ func (mctx *MigrationContext) ApplyCredentials() {
// Override
mctx.InspectorConnectionConfig.Password = mctx.CliPassword
}

if mctx.IsMoveTablesMode() {
// apply credentials for the applier from target CLI args
if mctx.MoveTables.ConnectionConfig == nil {
mctx.MoveTables.ConnectionConfig = &mysql.ConnectionConfig{}
}
mctx.MoveTables.ConnectionConfig.User = mctx.MoveTables.TargetUser
mctx.MoveTables.ConnectionConfig.Password = mctx.MoveTables.TargetPass
mctx.MoveTables.ConnectionConfig.Key = mysql.InstanceKey{
Hostname: mctx.MoveTables.TargetHost,
Port: mctx.MoveTables.TargetPort,
}
}
}

func (mctx *MigrationContext) SetupTLS() error {
//TODO(chriskirkland): make this work for move-tables?
if mctx.UseTLS {
return mctx.InspectorConnectionConfig.UseTLS(mctx.TLSCACertificate, mctx.TLSCertificate, mctx.TLSKey, mctx.TLSAllowInsecure)
}
Expand Down Expand Up @@ -1029,6 +1085,11 @@ func (mctx *MigrationContext) CancelContext() {
}
}

// IsMoveTablesMode returns true if gh-ost should be used for moving tables instead of running a schema migration.
func (mctx *MigrationContext) IsMoveTablesMode() bool {
return len(mctx.MoveTables.TableNames) > 0
}

// SendWithContext attempts to send a value to a channel, but returns early
// if the context is cancelled. This prevents goroutine deadlocks when the
// channel receiver has exited due to an error.
Expand Down
58 changes: 49 additions & 9 deletions go/cmd/gh-ost/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import (
"os"
"os/signal"
"regexp"
"strings"
"syscall"

"github.com/github/gh-ost/go/base"
"github.com/github/gh-ost/go/logic"
"github.com/github/gh-ost/go/mysql"
"github.com/github/gh-ost/go/sql"
_ "github.com/go-sql-driver/mysql"
"github.com/openark/golib/log"
Expand Down Expand Up @@ -164,8 +166,16 @@ func main() {
version := flag.Bool("version", false, "Print version & exit")
checkFlag := flag.Bool("check-flag", false, "Check if another flag exists/supported. This allows for cross-version scripting. Exits with 0 when all additional provided flags exist, nonzero otherwise. You must provide (dummy) values for flags that require a value. Example: gh-ost --check-flag --cut-over-lock-timeout-seconds --nice-ratio 0")
flag.StringVar(&migrationContext.ForceTmpTableName, "force-table-names", "", "table name prefix to be used on the temporary tables")
flag.CommandLine.SetOutput(os.Stdout)

// move tables flags
moveTables := flag.String("move-tables", "", "Comma delimited list of tables to move. e.g. 'table1,table2,table3'. This is a special mode that allows you to move tables between database clusters. This mode is mutually exclusive with --alter, --table, --test-on-replica, --migrate-on-replica, --execute-on-replica, and --revert.")
flag.StringVar(&migrationContext.MoveTables.TargetHost, "target-host", "", "Target MySQL hostname for --move-tables mode. Must be specified if --move-tables is specified.")
flag.IntVar(&migrationContext.MoveTables.TargetPort, "target-port", 3306, "Target MySQL port for --move-tables mode. Defaults to 3306.")
flag.StringVar(&migrationContext.MoveTables.TargetUser, "target-user", "", "Target MySQL username for --move-tables mode. If not provided, uses the same user as the source connection")
flag.StringVar(&migrationContext.MoveTables.TargetPass, "target-password", "", "Target MySQL password for --move-tables mode. If not provided, uses the same password as the source connection")
flag.StringVar(&migrationContext.MoveTables.TargetDatabase, "target-database", "", "Target MySQL database name for --move-tables mode. If not provided, uses the same database name as the source connection")

flag.CommandLine.SetOutput(os.Stdout)
flag.Parse()

if *checkFlag {
Expand Down Expand Up @@ -203,13 +213,7 @@ func main() {
migrationContext.Log.SetLevel(log.ERROR)
}

if err := migrationContext.SetConnectionConfig(*storageEngine); err != nil {
migrationContext.Log.Fatale(err)
}

migrationContext.SetConnectionCharset(*charset)

if migrationContext.AlterStatement == "" && !migrationContext.Revert {
if migrationContext.AlterStatement == "" && !migrationContext.Revert && *moveTables == "" {
log.Fatal("--alter must be provided and statement must not be empty")
}
parser := sql.NewParserFromAlterStatement(migrationContext.AlterStatement)
Expand Down Expand Up @@ -250,7 +254,7 @@ func main() {
migrationContext.Log.Fatale(err)
}

if migrationContext.OriginalTableName == "" {
if migrationContext.OriginalTableName == "" && *moveTables == "" {
if parser.HasExplicitTable() {
migrationContext.OriginalTableName = parser.GetExplicitTable()
} else {
Expand Down Expand Up @@ -320,6 +324,40 @@ func main() {
migrationContext.Log.Warning("--exact-rowcount with --panic-on-warnings: row counts cannot be exact due to warning detection")
}

if *moveTables != "" {
if migrationContext.AlterStatement != "" {
log.Fatal("--move-tables is mutually exclusive with --alter")
}
if migrationContext.OriginalTableName != "" {
log.Fatal("--move-tables is mutually exclusive with --table")
}
if migrationContext.TestOnReplica {
log.Fatal("--move-tables is mutually exclusive with --test-on-replica")
}
if migrationContext.MigrateOnReplica {
log.Fatal("--move-tables is mutually exclusive with --migrate-on-replica")
}
if migrationContext.Revert {
log.Fatal("--move-tables is mutually exclusive with --revert")
}
if migrationContext.MoveTables.TargetHost == "" {
log.Fatal("--target-host must be specified when using --move-tables")
}
migrationContext.MoveTables.TableNames = strings.Split(*moveTables, ",")
if len(migrationContext.MoveTables.TableNames) > 1 {
// Future version will support moving multiple tables at the same time.
// For now, we only support moving a single table at a time.
log.Fatal("--move-tables currently supports only a single table")
}
migrationContext.MoveTables.ConnectionConfig = mysql.NewConnectionConfig()
}

if err := migrationContext.SetConnectionConfig(*storageEngine); err != nil {
migrationContext.Log.Fatale(err)
}

migrationContext.SetConnectionCharset(*charset)

switch *cutOver {
case "atomic", "default", "":
migrationContext.CutOverType = base.CutOverAtomic
Expand Down Expand Up @@ -379,6 +417,8 @@ func main() {
var err error
if migrationContext.Revert {
err = migrator.Revert()
} else if migrationContext.IsMoveTablesMode() {
err = migrator.MoveTables()
} else {
err = migrator.Migrate()
}
Expand Down
Loading
Loading