Skip to content

Commit 3898d49

Browse files
author
Shlomi Noach
committed
supporting update to columns of migration key
1 parent 8a59d7e commit 3898d49

2 files changed

Lines changed: 93 additions & 58 deletions

File tree

go/logic/applier.go

Lines changed: 93 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,32 @@ const (
2424
atomicCutOverMagicHint = "ghost-cut-over-sentry"
2525
)
2626

27+
type dmlBuildResult struct {
28+
query string
29+
args []interface{}
30+
rowsDelta int64
31+
err error
32+
}
33+
34+
func newDmlBuildResult(query string, args []interface{}, rowsDelta int64, err error) *dmlBuildResult {
35+
return &dmlBuildResult{
36+
query: query,
37+
args: args,
38+
rowsDelta: rowsDelta,
39+
err: err,
40+
}
41+
}
42+
43+
func newDmlBuildResultError(err error) *dmlBuildResult {
44+
return &dmlBuildResult{
45+
err: err,
46+
}
47+
}
48+
49+
func buildResults(args ...(*dmlBuildResult)) [](*dmlBuildResult) {
50+
return args
51+
}
52+
2753
// Applier connects and writes the the applier-server, which is the server where migration
2854
// happens. This is typically the master, but could be a replica when `--test-on-replica` or
2955
// `--execute-on-replica` are given.
@@ -899,7 +925,7 @@ func (this *Applier) ShowStatusVariable(variableName string) (result int64, err
899925
return result, nil
900926
}
901927

902-
func (this *Applier) validateUpdateDoesNotModifyMigrationUniqueKeyColumns(dmlEvent *binlog.BinlogDMLEvent) error {
928+
func (this *Applier) updateModifiesUniqueKeyColumns(dmlEvent *binlog.BinlogDMLEvent) (modifiedColumn string, isModified bool) {
903929
// log.Debugf("............ UPDATE")
904930
// log.Debugf("............ UPDATE: %+v", this.migrationContext.UniqueKey.Columns.String())
905931
// log.Debugf("............ UPDATE: %+v", dmlEvent.WhereColumnValues.String())
@@ -912,88 +938,97 @@ func (this *Applier) validateUpdateDoesNotModifyMigrationUniqueKeyColumns(dmlEve
912938
// log.Debugf("............ UPDATE: new value= %+v", newColumnValue)
913939
// log.Debugf("............ UPDATE: equals? %+v", newColumnValue == whereColumnValue)
914940
if newColumnValue != whereColumnValue {
915-
return log.Errorf("gh-ost detected an UPDATE to a unique key column this migration is iterating on. Such update is not supported. Column is %s", column.Name)
941+
return column.Name, true
916942
}
917943
}
918-
return nil
944+
return "", false
919945
}
920946

921947
// buildDMLEventQuery creates a query to operate on the ghost table, based on an intercepted binlog
922948
// event entry on the original table.
923-
func (this *Applier) buildDMLEventQuery(dmlEvent *binlog.BinlogDMLEvent) (query string, args []interface{}, rowsDelta int64, err error) {
949+
func (this *Applier) buildDMLEventQuery(dmlEvent *binlog.BinlogDMLEvent) (results [](*dmlBuildResult)) {
924950
switch dmlEvent.DML {
925951
case binlog.DeleteDML:
926952
{
927953
query, uniqueKeyArgs, err := sql.BuildDMLDeleteQuery(dmlEvent.DatabaseName, this.migrationContext.GetGhostTableName(), this.migrationContext.OriginalTableColumns, &this.migrationContext.UniqueKey.Columns, dmlEvent.WhereColumnValues.AbstractValues())
928-
return query, uniqueKeyArgs, -1, err
954+
return buildResults(newDmlBuildResult(query, uniqueKeyArgs, -1, err))
929955
}
930956
case binlog.InsertDML:
931957
{
932958
query, sharedArgs, err := sql.BuildDMLInsertQuery(dmlEvent.DatabaseName, this.migrationContext.GetGhostTableName(), this.migrationContext.OriginalTableColumns, this.migrationContext.SharedColumns, this.migrationContext.MappedSharedColumns, dmlEvent.NewColumnValues.AbstractValues())
933-
return query, sharedArgs, 1, err
959+
return buildResults(newDmlBuildResult(query, sharedArgs, 1, err))
934960
}
935961
case binlog.UpdateDML:
936962
{
937-
if err := this.validateUpdateDoesNotModifyMigrationUniqueKeyColumns(dmlEvent); err != nil {
938-
return query, args, rowsDelta, err
963+
if modifiedColumn, isModified := this.updateModifiesUniqueKeyColumns(dmlEvent); isModified {
964+
log.Debugf("---------------- Detected modifiedColumn: %+v. Will turn into DELETE+INSERT", modifiedColumn)
965+
dmlEvent.DML = binlog.DeleteDML
966+
results = append(results, this.buildDMLEventQuery(dmlEvent)...)
967+
dmlEvent.DML = binlog.InsertDML
968+
results = append(results, this.buildDMLEventQuery(dmlEvent)...)
969+
return results
970+
// return buildResults(newDmlBuildResultError(log.Errorf("gh-ost detected an UPDATE to a unique key column this migration is iterating on. Such update is not supported. Column is `%s`", modifiedColumn)))
971+
// return query, args, rowsDelta, log.Errorf("gh-ost detected an UPDATE to a unique key column this migration is iterating on. Such update is not supported. Column is `%s`", modifiedColumn)
939972
}
940973
query, sharedArgs, uniqueKeyArgs, err := sql.BuildDMLUpdateQuery(dmlEvent.DatabaseName, this.migrationContext.GetGhostTableName(), this.migrationContext.OriginalTableColumns, this.migrationContext.SharedColumns, this.migrationContext.MappedSharedColumns, &this.migrationContext.UniqueKey.Columns, dmlEvent.NewColumnValues.AbstractValues(), dmlEvent.WhereColumnValues.AbstractValues())
974+
args := sqlutils.Args()
941975
args = append(args, sharedArgs...)
942976
args = append(args, uniqueKeyArgs...)
943-
return query, args, 0, err
977+
return buildResults(newDmlBuildResult(query, args, 0, err))
944978
}
945979
}
946-
return "", args, 0, fmt.Errorf("Unknown dml event type: %+v", dmlEvent.DML)
980+
return buildResults(newDmlBuildResultError(fmt.Errorf("Unknown dml event type: %+v", dmlEvent.DML)))
947981
}
948982

949983
// ApplyDMLEventQuery writes an entry to the ghost table, in response to an intercepted
950984
// original-table binlog event
951985
func (this *Applier) ApplyDMLEventQuery(dmlEvent *binlog.BinlogDMLEvent) error {
952-
query, args, rowDelta, err := this.buildDMLEventQuery(dmlEvent)
953-
if err != nil {
954-
return err
955-
}
956-
// TODO The below is in preparation for transactional writes on the ghost tables.
957-
// Such writes would be, for example:
958-
// - prepended with sql_mode setup
959-
// - prepended with time zone setup
960-
// - prepended with SET SQL_LOG_BIN=0
961-
// - prepended with SET FK_CHECKS=0
962-
// etc.
963-
//
964-
// a known problem: https://github.com/golang/go/issues/9373 -- bitint unsigned values, not supported in database/sql
965-
// is solved by silently converting unsigned bigints to string values.
966-
//
967-
968-
err = func() error {
969-
tx, err := this.db.Begin()
970-
if err != nil {
971-
return err
986+
for _, buildResult := range this.buildDMLEventQuery(dmlEvent) {
987+
if buildResult.err != nil {
988+
return buildResult.err
972989
}
973-
sessionQuery := `SET
990+
// TODO The below is in preparation for transactional writes on the ghost tables.
991+
// Such writes would be, for example:
992+
// - prepended with sql_mode setup
993+
// - prepended with time zone setup
994+
// - prepended with SET SQL_LOG_BIN=0
995+
// - prepended with SET FK_CHECKS=0
996+
// etc.
997+
//
998+
// a known problem: https://github.com/golang/go/issues/9373 -- bitint unsigned values, not supported in database/sql
999+
// is solved by silently converting unsigned bigints to string values.
1000+
//
1001+
1002+
err := func() error {
1003+
tx, err := this.db.Begin()
1004+
if err != nil {
1005+
return err
1006+
}
1007+
sessionQuery := `SET
9741008
SESSION time_zone = '+00:00',
9751009
sql_mode = CONCAT(@@session.sql_mode, ',STRICT_ALL_TABLES')
9761010
`
977-
if _, err := tx.Exec(sessionQuery); err != nil {
978-
return err
979-
}
980-
if _, err := tx.Exec(query, args...); err != nil {
981-
return err
1011+
if _, err := tx.Exec(sessionQuery); err != nil {
1012+
return err
1013+
}
1014+
if _, err := tx.Exec(buildResult.query, buildResult.args...); err != nil {
1015+
return err
1016+
}
1017+
if err := tx.Commit(); err != nil {
1018+
return err
1019+
}
1020+
return nil
1021+
}()
1022+
1023+
if err != nil {
1024+
err = fmt.Errorf("%s; query=%s; args=%+v", err.Error(), buildResult.query, buildResult.args)
1025+
return log.Errore(err)
9821026
}
983-
if err := tx.Commit(); err != nil {
984-
return err
1027+
// no error
1028+
atomic.AddInt64(&this.migrationContext.TotalDMLEventsApplied, 1)
1029+
if this.migrationContext.CountTableRows {
1030+
atomic.AddInt64(&this.migrationContext.RowsDeltaEstimate, buildResult.rowsDelta)
9851031
}
986-
return nil
987-
}()
988-
989-
if err != nil {
990-
err = fmt.Errorf("%s; query=%s; args=%+v", err.Error(), query, args)
991-
return log.Errore(err)
992-
}
993-
// no error
994-
atomic.AddInt64(&this.migrationContext.TotalDMLEventsApplied, 1)
995-
if this.migrationContext.CountTableRows {
996-
atomic.AddInt64(&this.migrationContext.RowsDeltaEstimate, rowDelta)
9971032
}
9981033
return nil
9991034
}
@@ -1022,15 +1057,16 @@ func (this *Applier) ApplyDMLEventQueries(dmlEvents [](*binlog.BinlogDMLEvent))
10221057
return rollback(err)
10231058
}
10241059
for _, dmlEvent := range dmlEvents {
1025-
query, args, rowDelta, err := this.buildDMLEventQuery(dmlEvent)
1026-
if err != nil {
1027-
return rollback(err)
1028-
}
1029-
if _, err := tx.Exec(query, args...); err != nil {
1030-
err = fmt.Errorf("%s; query=%s; args=%+v", err.Error(), query, args)
1031-
return rollback(err)
1060+
for _, buildResult := range this.buildDMLEventQuery(dmlEvent) {
1061+
if buildResult.err != nil {
1062+
return rollback(buildResult.err)
1063+
}
1064+
if _, err := tx.Exec(buildResult.query, buildResult.args...); err != nil {
1065+
err = fmt.Errorf("%s; query=%s; args=%+v", err.Error(), buildResult.query, buildResult.args)
1066+
return rollback(err)
1067+
}
1068+
totalDelta += buildResult.rowsDelta
10321069
}
1033-
totalDelta += rowDelta
10341070
}
10351071
if err := tx.Commit(); err != nil {
10361072
return err

localtests/fail-update-pk-column/expect_failure

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)