Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions go/base/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ type MigrationContext struct {
PanicOnWarnings bool
Checkpoint bool
CheckpointIntervalSeconds int64
AllowChildForeignKeys bool
ForeignKeyRenamePrefix string

DropServeSocket bool
ServeSocketFile string
Expand Down
5 changes: 5 additions & 0 deletions go/cmd/gh-ost/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ func main() {
flag.BoolVar(&migrationContext.SkipRenamedColumns, "skip-renamed-columns", false, "in case your `ALTER` statement renames columns, gh-ost will note that and offer its interpretation of the rename. By default gh-ost does not proceed to execute. This flag tells gh-ost to skip the renamed columns, i.e. to treat what gh-ost thinks are renamed columns as unrelated columns. NOTE: you may lose column data")
flag.BoolVar(&migrationContext.IsTungsten, "tungsten", false, "explicitly let gh-ost know that you are running on a tungsten-replication based topology (you are likely to also provide --assume-master-host)")
flag.BoolVar(&migrationContext.DiscardForeignKeys, "discard-foreign-keys", false, "DANGER! This flag will migrate a table that has foreign keys and will NOT create foreign keys on the ghost table, thus your altered table will have NO foreign keys. This is useful for intentional dropping of foreign keys")
flag.BoolVar(&migrationContext.AllowChildForeignKeys, "allow-child-foreign-keys", false, "Allow gh-ost to create foreign keys on the ghost table when the child table has foreign keys")
flag.StringVar(&migrationContext.ForeignKeyRenamePrefix, "foreign-key-rename-prefix", "_", "Rename foreign keys in the ghost table by adding this prefix")
flag.BoolVar(&migrationContext.SkipForeignKeyChecks, "skip-foreign-key-checks", false, "set to 'true' when you know for certain there are no foreign keys on your table, and wish to skip the time it takes for gh-ost to verify that")
flag.BoolVar(&migrationContext.SkipStrictMode, "skip-strict-mode", false, "explicitly tell gh-ost binlog applier not to enforce strict sql mode")
flag.BoolVar(&migrationContext.AllowZeroInDate, "allow-zero-in-date", false, "explicitly tell gh-ost binlog applier to ignore NO_ZERO_IN_DATE,NO_ZERO_DATE in sql_mode")
Expand Down Expand Up @@ -235,6 +237,9 @@ func main() {
if migrationContext.DiscardForeignKeys {
log.Warning("--discard-foreign-keys was provided with --revert, it will be ignored")
}
if migrationContext.AllowChildForeignKeys {
log.Warning("--allow-child-foreign-keys was provided with --revert, it will be ignored")
}
}

if migrationContext.DatabaseName == "" {
Expand Down
61 changes: 61 additions & 0 deletions go/logic/applier.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,67 @@ func (this *Applier) CreateGhostTable() error {
return err
}

func (this *Applier) getTableForeignKeyDefinitions(tableName string) (fkNames []string, fkBodies []string, err error) {
query := fmt.Sprintf("show create table %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(tableName))
var dummy string
var createStatement string
if err := this.db.QueryRow(query).Scan(&dummy, &createStatement); err != nil {
return nil, nil, err
}

lines := strings.Split(createStatement, "\n")
re := regexp.MustCompile("CONSTRAINT\\s+['\"`]?([^'\"`]+)['\"`]?\\s+FOREIGN\\s+KEY\\s+(.*)")
for _, line := range lines {
line = strings.TrimSpace(line)
line = strings.TrimRight(line, ",")
if matches := re.FindStringSubmatch(line); len(matches) == 3 {
fkNames = append(fkNames, matches[1])
fkBodies = append(fkBodies, "FOREIGN KEY "+matches[2])
}
}
return fkNames, fkBodies, nil
}

func (this *Applier) ApplyForeignKeys() error {
this.migrationContext.Log.Infof("Applying foreign keys with prefix '%s'", this.migrationContext.ForeignKeyRenamePrefix)

ghostFKNames, _, err := this.getTableForeignKeyDefinitions(this.migrationContext.GetGhostTableName())
if err != nil {
return err
}
for _, name := range ghostFKNames {
query := fmt.Sprintf("alter /* gh-ost */ table %s.%s drop foreign key %s",
sql.EscapeName(this.migrationContext.DatabaseName),
sql.EscapeName(this.migrationContext.GetGhostTableName()),
sql.EscapeName(name))
this.migrationContext.Log.Infof("Dropping foreign key %s from ghost table", name)
if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil {
return err
}
}

origFKNames, origFKBodies, err := this.getTableForeignKeyDefinitions(this.migrationContext.OriginalTableName)
if err != nil {
return err
}
for i, name := range origFKNames {
newName := fmt.Sprintf("%s%s", this.migrationContext.ForeignKeyRenamePrefix, name)
if len(newName) > 64 {
return fmt.Errorf("Generate foreign key name '%s' exceeds 64 characters", newName)
}
query := fmt.Sprintf("alter /* gh-ost */ table %s.%s add constraint %s %s",
sql.EscapeName(this.migrationContext.DatabaseName),
sql.EscapeName(this.migrationContext.GetGhostTableName()),
sql.EscapeName(newName),
origFKBodies[i])
this.migrationContext.Log.Infof("Adding foreign key %s to ghost table", newName)
if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil {
return err
}
}
return nil
}

// AlterGhost applies `alter` statement on ghost table
func (this *Applier) AlterGhost() error {
query := fmt.Sprintf(`alter /* gh-ost */ table %s.%s %s`,
Expand Down
4 changes: 2 additions & 2 deletions go/logic/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (this *Inspector) ValidateOriginalTable() (err error) {
if err := this.validateTable(); err != nil {
return err
}
if err := this.validateTableForeignKeys(this.migrationContext.DiscardForeignKeys); err != nil {
if err := this.validateTableForeignKeys(this.migrationContext.DiscardForeignKeys || this.migrationContext.AllowChildForeignKeys); err != nil {
return err
}
if err := this.validateTableTriggers(); err != nil {
Expand Down Expand Up @@ -539,7 +539,7 @@ func (this *Inspector) validateTableForeignKeys(allowChildForeignKeys bool) erro
}
if numChildForeignKeys > 0 {
if allowChildForeignKeys {
this.migrationContext.Log.Debugf("Foreign keys found and will be dropped, as per given --discard-foreign-keys flag")
this.migrationContext.Log.Debugf("Foreign keys are found and will be removed using the --discard-foreign-keys flag or copied using the --allow-child-foreign-keys flag")
return nil
}
return this.migrationContext.Log.Errorf("Found %d child-side foreign keys on %s.%s. Child-side foreign keys are not supported. Bailing out", numChildForeignKeys, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
Expand Down
4 changes: 4 additions & 0 deletions go/logic/migrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1349,6 +1349,10 @@ func (this *Migrator) initiateApplier() error {
this.migrationContext.Log.Errorf("Unable to create ghost table, see further error details. Perhaps a previous migration failed without dropping the table? Bailing out")
return err
}
if err := this.applier.ApplyForeignKeys(); err != nil {
this.migrationContext.Log.Errorf("Unable to apply foreign keys, see further error details. Bailing out")
return err
}
if err := this.applier.AlterGhost(); err != nil {
this.migrationContext.Log.Errorf("Unable to ALTER ghost table, see further error details. Bailing out")
return err
Expand Down