@@ -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
951985func (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
0 commit comments