2016-04-04 13:30:49 +00:00
|
|
|
/*
|
|
|
|
Copyright 2016 GitHub Inc.
|
|
|
|
See https://github.com/github/gh-osc/blob/master/LICENSE
|
|
|
|
*/
|
|
|
|
|
|
|
|
package logic
|
|
|
|
|
|
|
|
import (
|
|
|
|
gosql "database/sql"
|
|
|
|
"fmt"
|
2016-04-08 08:34:44 +00:00
|
|
|
"sync/atomic"
|
2016-04-07 13:57:12 +00:00
|
|
|
"time"
|
|
|
|
|
2016-04-04 13:30:49 +00:00
|
|
|
"github.com/github/gh-osc/go/base"
|
2016-04-11 15:27:16 +00:00
|
|
|
"github.com/github/gh-osc/go/binlog"
|
2016-04-04 13:30:49 +00:00
|
|
|
"github.com/github/gh-osc/go/mysql"
|
|
|
|
"github.com/github/gh-osc/go/sql"
|
|
|
|
|
|
|
|
"github.com/outbrain/golib/log"
|
|
|
|
"github.com/outbrain/golib/sqlutils"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Applier reads data from the read-MySQL-server (typically a replica, but can be the master)
|
|
|
|
// It is used for gaining initial status and structure, and later also follow up on progress and changelog
|
|
|
|
type Applier struct {
|
|
|
|
connectionConfig *mysql.ConnectionConfig
|
|
|
|
db *gosql.DB
|
2016-04-18 17:57:18 +00:00
|
|
|
singletonDB *gosql.DB
|
2016-04-04 13:30:49 +00:00
|
|
|
migrationContext *base.MigrationContext
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewApplier() *Applier {
|
|
|
|
return &Applier{
|
2016-04-14 11:37:56 +00:00
|
|
|
connectionConfig: base.GetMigrationContext().ApplierConnectionConfig,
|
2016-04-04 13:30:49 +00:00
|
|
|
migrationContext: base.GetMigrationContext(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *Applier) InitDBConnections() (err error) {
|
2016-04-18 17:57:18 +00:00
|
|
|
applierUri := this.connectionConfig.GetDBUri(this.migrationContext.DatabaseName)
|
|
|
|
if this.db, _, err = sqlutils.GetDB(applierUri); err != nil {
|
2016-04-04 13:30:49 +00:00
|
|
|
return err
|
|
|
|
}
|
2016-04-18 17:57:18 +00:00
|
|
|
singletonApplierUri := fmt.Sprintf("%s?timeout=0", applierUri)
|
|
|
|
if this.singletonDB, _, err = sqlutils.GetDB(singletonApplierUri); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
this.singletonDB.SetMaxOpenConns(1)
|
|
|
|
if err := this.validateConnection(this.db); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := this.validateConnection(this.singletonDB); err != nil {
|
2016-04-04 13:30:49 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// validateConnection issues a simple can-connect to MySQL
|
2016-04-18 17:57:18 +00:00
|
|
|
func (this *Applier) validateConnection(db *gosql.DB) error {
|
2016-04-04 13:30:49 +00:00
|
|
|
query := `select @@global.port`
|
|
|
|
var port int
|
2016-04-18 17:57:18 +00:00
|
|
|
if err := db.QueryRow(query).Scan(&port); err != nil {
|
2016-04-04 13:30:49 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if port != this.connectionConfig.Key.Port {
|
|
|
|
return fmt.Errorf("Unexpected database port reported: %+v", port)
|
|
|
|
}
|
|
|
|
log.Infof("connection validated on %+v", this.connectionConfig.Key)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-11 15:27:16 +00:00
|
|
|
// CreateGhostTable creates the ghost table on the applier host
|
2016-04-04 13:30:49 +00:00
|
|
|
func (this *Applier) CreateGhostTable() error {
|
|
|
|
query := fmt.Sprintf(`create /* gh-osc */ table %s.%s like %s.%s`,
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.GetGhostTableName()),
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.OriginalTableName),
|
|
|
|
)
|
|
|
|
log.Infof("Creating ghost table %s.%s",
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.GetGhostTableName()),
|
|
|
|
)
|
|
|
|
if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-04-07 13:57:12 +00:00
|
|
|
log.Infof("Ghost table created")
|
2016-04-04 13:30:49 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-11 15:27:16 +00:00
|
|
|
// AlterGhost applies `alter` statement on ghost table
|
2016-04-04 13:30:49 +00:00
|
|
|
func (this *Applier) AlterGhost() error {
|
|
|
|
query := fmt.Sprintf(`alter /* gh-osc */ table %s.%s %s`,
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.GetGhostTableName()),
|
|
|
|
this.migrationContext.AlterStatement,
|
|
|
|
)
|
|
|
|
log.Infof("Altering ghost table %s.%s",
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.GetGhostTableName()),
|
|
|
|
)
|
|
|
|
log.Debugf("ALTER statement: %s", query)
|
|
|
|
if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-04-07 13:57:12 +00:00
|
|
|
log.Infof("Ghost table altered")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-11 15:27:16 +00:00
|
|
|
// CreateChangelogTable creates the changelog table on the applier host
|
2016-04-07 13:57:12 +00:00
|
|
|
func (this *Applier) CreateChangelogTable() error {
|
|
|
|
query := fmt.Sprintf(`create /* gh-osc */ table %s.%s (
|
2016-04-08 12:35:06 +00:00
|
|
|
id bigint auto_increment,
|
2016-04-07 13:57:12 +00:00
|
|
|
last_update timestamp not null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
hint varchar(64) charset ascii not null,
|
2016-04-08 12:35:06 +00:00
|
|
|
value varchar(255) charset ascii not null,
|
2016-04-07 13:57:12 +00:00
|
|
|
primary key(id),
|
|
|
|
unique key hint_uidx(hint)
|
2016-04-11 15:27:16 +00:00
|
|
|
) auto_increment=256
|
2016-04-07 13:57:12 +00:00
|
|
|
`,
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.GetChangelogTableName()),
|
|
|
|
)
|
|
|
|
log.Infof("Creating changelog table %s.%s",
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.GetChangelogTableName()),
|
|
|
|
)
|
|
|
|
if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
log.Infof("Changelog table created")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-11 15:27:16 +00:00
|
|
|
// dropTable drops a given table on the applied host
|
|
|
|
func (this *Applier) dropTable(tableName string) error {
|
2016-04-07 13:57:12 +00:00
|
|
|
query := fmt.Sprintf(`drop /* gh-osc */ table if exists %s.%s`,
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
2016-04-11 15:27:16 +00:00
|
|
|
sql.EscapeName(tableName),
|
2016-04-07 13:57:12 +00:00
|
|
|
)
|
2016-04-11 15:27:16 +00:00
|
|
|
log.Infof("Droppping table %s.%s",
|
2016-04-07 13:57:12 +00:00
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
2016-04-11 15:27:16 +00:00
|
|
|
sql.EscapeName(tableName),
|
2016-04-07 13:57:12 +00:00
|
|
|
)
|
|
|
|
if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-04-11 15:27:16 +00:00
|
|
|
log.Infof("Table dropped")
|
2016-04-04 13:30:49 +00:00
|
|
|
return nil
|
|
|
|
}
|
2016-04-04 16:19:46 +00:00
|
|
|
|
2016-04-11 15:27:16 +00:00
|
|
|
// DropChangelogTable drops the changelog table on the applier host
|
|
|
|
func (this *Applier) DropChangelogTable() error {
|
|
|
|
return this.dropTable(this.migrationContext.GetChangelogTableName())
|
|
|
|
}
|
|
|
|
|
|
|
|
// DropGhostTable drops the ghost table on the applier host
|
|
|
|
func (this *Applier) DropGhostTable() error {
|
|
|
|
return this.dropTable(this.migrationContext.GetGhostTableName())
|
|
|
|
}
|
|
|
|
|
2016-04-07 13:57:12 +00:00
|
|
|
// WriteChangelog writes a value to the changelog table.
|
|
|
|
// It returns the hint as given, for convenience
|
|
|
|
func (this *Applier) WriteChangelog(hint, value string) (string, error) {
|
2016-04-14 11:37:56 +00:00
|
|
|
explicitId := 0
|
|
|
|
switch hint {
|
|
|
|
case "heartbeat":
|
|
|
|
explicitId = 1
|
|
|
|
case "state":
|
|
|
|
explicitId = 2
|
|
|
|
case "throttle":
|
|
|
|
explicitId = 3
|
|
|
|
}
|
2016-04-07 13:57:12 +00:00
|
|
|
query := fmt.Sprintf(`
|
|
|
|
insert /* gh-osc */ into %s.%s
|
|
|
|
(id, hint, value)
|
|
|
|
values
|
2016-04-14 11:37:56 +00:00
|
|
|
(NULLIF(?, 0), ?, ?)
|
2016-04-07 13:57:12 +00:00
|
|
|
on duplicate key update
|
|
|
|
last_update=NOW(),
|
|
|
|
value=VALUES(value)
|
|
|
|
`,
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.GetChangelogTableName()),
|
|
|
|
)
|
2016-04-14 11:37:56 +00:00
|
|
|
_, err := sqlutils.Exec(this.db, query, explicitId, hint, value)
|
2016-04-07 13:57:12 +00:00
|
|
|
return hint, err
|
|
|
|
}
|
|
|
|
|
2016-04-11 15:27:16 +00:00
|
|
|
func (this *Applier) WriteAndLogChangelog(hint, value string) (string, error) {
|
2016-04-08 12:35:06 +00:00
|
|
|
this.WriteChangelog(hint, value)
|
|
|
|
return this.WriteChangelog(fmt.Sprintf("%s at %d", hint, time.Now().UnixNano()), value)
|
|
|
|
}
|
|
|
|
|
2016-04-11 15:27:16 +00:00
|
|
|
func (this *Applier) WriteChangelogState(value string) (string, error) {
|
|
|
|
return this.WriteAndLogChangelog("state", value)
|
|
|
|
}
|
|
|
|
|
2016-04-07 13:57:12 +00:00
|
|
|
// InitiateHeartbeat creates a heartbeat cycle, writing to the changelog table.
|
|
|
|
// This is done asynchronously
|
2016-04-14 11:37:56 +00:00
|
|
|
func (this *Applier) InitiateHeartbeat(heartbeatIntervalMilliseconds int64) {
|
|
|
|
numSuccessiveFailures := 0
|
|
|
|
injectHeartbeat := func() error {
|
2016-04-18 17:57:18 +00:00
|
|
|
if _, err := this.WriteChangelog("heartbeat", time.Now().Format(time.RFC3339Nano)); err != nil {
|
2016-04-14 11:37:56 +00:00
|
|
|
numSuccessiveFailures++
|
|
|
|
if numSuccessiveFailures > this.migrationContext.MaxRetries() {
|
|
|
|
return log.Errore(err)
|
2016-04-07 13:57:12 +00:00
|
|
|
}
|
2016-04-14 11:37:56 +00:00
|
|
|
} else {
|
|
|
|
numSuccessiveFailures = 0
|
2016-04-07 13:57:12 +00:00
|
|
|
}
|
2016-04-14 11:37:56 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
injectHeartbeat()
|
|
|
|
|
|
|
|
heartbeatTick := time.Tick(time.Duration(heartbeatIntervalMilliseconds) * time.Millisecond)
|
|
|
|
for range heartbeatTick {
|
|
|
|
// Generally speaking, we would issue a goroutine, but I'd actually rather
|
|
|
|
// have this blocked rather than spam the master in the event something
|
|
|
|
// goes wrong
|
|
|
|
if err := injectHeartbeat(); err != nil {
|
|
|
|
return
|
2016-04-07 13:57:12 +00:00
|
|
|
}
|
2016-04-14 11:37:56 +00:00
|
|
|
}
|
2016-04-07 13:57:12 +00:00
|
|
|
}
|
|
|
|
|
2016-04-04 16:19:46 +00:00
|
|
|
// ReadMigrationMinValues
|
|
|
|
func (this *Applier) ReadMigrationMinValues(uniqueKey *sql.UniqueKey) error {
|
|
|
|
log.Debugf("Reading migration range according to key: %s", uniqueKey.Name)
|
2016-04-11 15:27:16 +00:00
|
|
|
query, err := sql.BuildUniqueKeyMinValuesPreparedQuery(this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, uniqueKey.Columns.Names)
|
2016-04-04 16:19:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
rows, err := this.db.Query(query)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for rows.Next() {
|
2016-04-11 15:27:16 +00:00
|
|
|
this.migrationContext.MigrationRangeMinValues = sql.NewColumnValues(uniqueKey.Len())
|
2016-04-04 16:19:46 +00:00
|
|
|
if err = rows.Scan(this.migrationContext.MigrationRangeMinValues.ValuesPointers...); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Infof("Migration min values: [%s]", this.migrationContext.MigrationRangeMinValues)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadMigrationMinValues
|
|
|
|
func (this *Applier) ReadMigrationMaxValues(uniqueKey *sql.UniqueKey) error {
|
|
|
|
log.Debugf("Reading migration range according to key: %s", uniqueKey.Name)
|
2016-04-11 15:27:16 +00:00
|
|
|
query, err := sql.BuildUniqueKeyMaxValuesPreparedQuery(this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, uniqueKey.Columns.Names)
|
2016-04-04 16:19:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
rows, err := this.db.Query(query)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for rows.Next() {
|
2016-04-11 15:27:16 +00:00
|
|
|
this.migrationContext.MigrationRangeMaxValues = sql.NewColumnValues(uniqueKey.Len())
|
2016-04-04 16:19:46 +00:00
|
|
|
if err = rows.Scan(this.migrationContext.MigrationRangeMaxValues.ValuesPointers...); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Infof("Migration max values: [%s]", this.migrationContext.MigrationRangeMaxValues)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-04-05 07:14:22 +00:00
|
|
|
func (this *Applier) ReadMigrationRangeValues() error {
|
|
|
|
if err := this.ReadMigrationMinValues(this.migrationContext.UniqueKey); err != nil {
|
2016-04-04 16:19:46 +00:00
|
|
|
return err
|
|
|
|
}
|
2016-04-05 07:14:22 +00:00
|
|
|
if err := this.ReadMigrationMaxValues(this.migrationContext.UniqueKey); err != nil {
|
2016-04-04 16:19:46 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-08 08:34:44 +00:00
|
|
|
// __unused_IterationIsComplete lets us know when the copy-iteration phase is complete, i.e.
|
2016-04-05 07:14:22 +00:00
|
|
|
// we've exhausted all rows
|
2016-04-08 08:34:44 +00:00
|
|
|
func (this *Applier) __unused_IterationIsComplete() (bool, error) {
|
2016-04-05 07:14:22 +00:00
|
|
|
if !this.migrationContext.HasMigrationRange() {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
if this.migrationContext.MigrationIterationRangeMinValues == nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
2016-04-05 17:50:49 +00:00
|
|
|
args := sqlutils.Args()
|
2016-04-11 15:27:16 +00:00
|
|
|
compareWithIterationRangeStart, explodedArgs, err := sql.BuildRangePreparedComparison(this.migrationContext.UniqueKey.Columns.Names, this.migrationContext.MigrationIterationRangeMinValues.AbstractValues(), sql.GreaterThanOrEqualsComparisonSign)
|
2016-04-05 07:14:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2016-04-05 17:50:49 +00:00
|
|
|
args = append(args, explodedArgs...)
|
2016-04-11 15:27:16 +00:00
|
|
|
compareWithRangeEnd, explodedArgs, err := sql.BuildRangePreparedComparison(this.migrationContext.UniqueKey.Columns.Names, this.migrationContext.MigrationRangeMaxValues.AbstractValues(), sql.LessThanComparisonSign)
|
2016-04-05 07:14:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2016-04-05 17:50:49 +00:00
|
|
|
args = append(args, explodedArgs...)
|
2016-04-05 07:14:22 +00:00
|
|
|
query := fmt.Sprintf(`
|
|
|
|
select /* gh-osc IterationIsComplete */ 1
|
|
|
|
from %s.%s
|
|
|
|
where (%s) and (%s)
|
|
|
|
limit 1
|
|
|
|
`,
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.OriginalTableName),
|
|
|
|
compareWithIterationRangeStart,
|
|
|
|
compareWithRangeEnd,
|
|
|
|
)
|
|
|
|
|
|
|
|
moreRowsFound := false
|
|
|
|
err = sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error {
|
|
|
|
moreRowsFound = true
|
|
|
|
return nil
|
2016-04-05 17:50:49 +00:00
|
|
|
}, args...)
|
2016-04-05 07:14:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return !moreRowsFound, nil
|
|
|
|
}
|
|
|
|
|
2016-04-08 08:34:44 +00:00
|
|
|
// CalculateNextIterationRangeEndValues reads the next-iteration-range-end unique key values,
|
|
|
|
// which will be used for copying the next chunk of rows. Ir returns "false" if there is
|
|
|
|
// no further chunk to work through, i.e. we're past the last chunk and are done with
|
|
|
|
// itrating the range (and this done with copying row chunks)
|
|
|
|
func (this *Applier) CalculateNextIterationRangeEndValues() (hasFurtherRange bool, err error) {
|
2016-04-05 07:14:22 +00:00
|
|
|
this.migrationContext.MigrationIterationRangeMinValues = this.migrationContext.MigrationIterationRangeMaxValues
|
2016-04-06 11:05:58 +00:00
|
|
|
if this.migrationContext.MigrationIterationRangeMinValues == nil {
|
|
|
|
this.migrationContext.MigrationIterationRangeMinValues = this.migrationContext.MigrationRangeMinValues
|
2016-04-05 07:14:22 +00:00
|
|
|
}
|
2016-04-05 17:50:49 +00:00
|
|
|
query, explodedArgs, err := sql.BuildUniqueKeyRangeEndPreparedQuery(
|
|
|
|
this.migrationContext.DatabaseName,
|
|
|
|
this.migrationContext.OriginalTableName,
|
2016-04-11 15:27:16 +00:00
|
|
|
this.migrationContext.UniqueKey.Columns.Names,
|
2016-04-06 11:05:58 +00:00
|
|
|
this.migrationContext.MigrationIterationRangeMinValues.AbstractValues(),
|
2016-04-05 17:50:49 +00:00
|
|
|
this.migrationContext.MigrationRangeMaxValues.AbstractValues(),
|
|
|
|
this.migrationContext.ChunkSize,
|
2016-04-08 12:35:06 +00:00
|
|
|
fmt.Sprintf("iteration:%d", this.migrationContext.GetIteration()),
|
2016-04-05 17:50:49 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
2016-04-08 08:34:44 +00:00
|
|
|
return hasFurtherRange, err
|
2016-04-05 17:50:49 +00:00
|
|
|
}
|
|
|
|
rows, err := this.db.Query(query, explodedArgs...)
|
2016-04-05 07:14:22 +00:00
|
|
|
if err != nil {
|
2016-04-08 08:34:44 +00:00
|
|
|
return hasFurtherRange, err
|
2016-04-05 07:14:22 +00:00
|
|
|
}
|
2016-04-11 15:27:16 +00:00
|
|
|
iterationRangeMaxValues := sql.NewColumnValues(this.migrationContext.UniqueKey.Len())
|
2016-04-05 07:14:22 +00:00
|
|
|
for rows.Next() {
|
|
|
|
if err = rows.Scan(iterationRangeMaxValues.ValuesPointers...); err != nil {
|
2016-04-08 08:34:44 +00:00
|
|
|
return hasFurtherRange, err
|
2016-04-05 07:14:22 +00:00
|
|
|
}
|
2016-04-08 08:34:44 +00:00
|
|
|
hasFurtherRange = true
|
2016-04-05 07:14:22 +00:00
|
|
|
}
|
2016-04-08 08:34:44 +00:00
|
|
|
if !hasFurtherRange {
|
2016-04-14 11:37:56 +00:00
|
|
|
log.Debugf("Iteration complete: no further range to iterate")
|
2016-04-08 08:34:44 +00:00
|
|
|
return hasFurtherRange, nil
|
2016-04-05 07:14:22 +00:00
|
|
|
}
|
|
|
|
this.migrationContext.MigrationIterationRangeMaxValues = iterationRangeMaxValues
|
2016-04-08 08:34:44 +00:00
|
|
|
return hasFurtherRange, nil
|
2016-04-06 11:05:58 +00:00
|
|
|
}
|
|
|
|
|
2016-04-08 08:34:44 +00:00
|
|
|
func (this *Applier) ApplyIterationInsertQuery() (chunkSize int64, rowsAffected int64, duration time.Duration, err error) {
|
|
|
|
startTime := time.Now()
|
|
|
|
chunkSize = atomic.LoadInt64(&this.migrationContext.ChunkSize)
|
|
|
|
|
2016-04-06 11:05:58 +00:00
|
|
|
query, explodedArgs, err := sql.BuildRangeInsertPreparedQuery(
|
|
|
|
this.migrationContext.DatabaseName,
|
|
|
|
this.migrationContext.OriginalTableName,
|
|
|
|
this.migrationContext.GetGhostTableName(),
|
2016-04-11 15:27:16 +00:00
|
|
|
this.migrationContext.SharedColumns.Names,
|
2016-04-06 11:05:58 +00:00
|
|
|
this.migrationContext.UniqueKey.Name,
|
2016-04-11 15:27:16 +00:00
|
|
|
this.migrationContext.UniqueKey.Columns.Names,
|
2016-04-06 11:05:58 +00:00
|
|
|
this.migrationContext.MigrationIterationRangeMinValues.AbstractValues(),
|
|
|
|
this.migrationContext.MigrationIterationRangeMaxValues.AbstractValues(),
|
2016-04-08 12:35:06 +00:00
|
|
|
this.migrationContext.GetIteration() == 0,
|
2016-04-08 08:34:44 +00:00
|
|
|
this.migrationContext.IsTransactionalTable(),
|
2016-04-06 11:05:58 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
2016-04-08 08:34:44 +00:00
|
|
|
return chunkSize, rowsAffected, duration, err
|
2016-04-06 11:05:58 +00:00
|
|
|
}
|
2016-04-08 08:34:44 +00:00
|
|
|
sqlResult, err := sqlutils.Exec(this.db, query, explodedArgs...)
|
|
|
|
if err != nil {
|
|
|
|
return chunkSize, rowsAffected, duration, err
|
2016-04-06 11:05:58 +00:00
|
|
|
}
|
2016-04-08 08:34:44 +00:00
|
|
|
rowsAffected, _ = sqlResult.RowsAffected()
|
|
|
|
duration = time.Now().Sub(startTime)
|
2016-04-06 11:05:58 +00:00
|
|
|
log.Debugf(
|
|
|
|
"Issued INSERT on range: [%s]..[%s]; iteration: %d; chunk-size: %d",
|
|
|
|
this.migrationContext.MigrationIterationRangeMinValues,
|
|
|
|
this.migrationContext.MigrationIterationRangeMaxValues,
|
2016-04-08 12:35:06 +00:00
|
|
|
this.migrationContext.GetIteration(),
|
2016-04-08 08:34:44 +00:00
|
|
|
chunkSize)
|
|
|
|
return chunkSize, rowsAffected, duration, nil
|
2016-04-05 07:14:22 +00:00
|
|
|
}
|
|
|
|
|
2016-04-08 08:34:44 +00:00
|
|
|
// LockTables
|
|
|
|
func (this *Applier) LockTables() error {
|
2016-04-18 17:57:18 +00:00
|
|
|
// query := fmt.Sprintf(`lock /* gh-osc */ tables %s.%s write, %s.%s write, %s.%s write`,
|
|
|
|
// sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
// sql.EscapeName(this.migrationContext.OriginalTableName),
|
|
|
|
// sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
// sql.EscapeName(this.migrationContext.GetGhostTableName()),
|
|
|
|
// sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
// sql.EscapeName(this.migrationContext.GetChangelogTableName()),
|
|
|
|
// )
|
|
|
|
query := fmt.Sprintf(`lock /* gh-osc */ tables %s.%s write`,
|
2016-04-08 08:34:44 +00:00
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.OriginalTableName),
|
|
|
|
)
|
|
|
|
log.Infof("Locking tables")
|
2016-04-18 17:57:18 +00:00
|
|
|
this.migrationContext.LockTablesStartTime = time.Now()
|
|
|
|
if _, err := sqlutils.ExecNoPrepare(this.singletonDB, query); err != nil {
|
2016-04-04 16:19:46 +00:00
|
|
|
return err
|
|
|
|
}
|
2016-04-08 08:34:44 +00:00
|
|
|
log.Infof("Tables locked")
|
|
|
|
return nil
|
|
|
|
}
|
2016-04-04 16:19:46 +00:00
|
|
|
|
2016-04-08 08:34:44 +00:00
|
|
|
// UnlockTables
|
|
|
|
func (this *Applier) UnlockTables() error {
|
|
|
|
query := `unlock /* gh-osc */ tables`
|
|
|
|
log.Infof("Unlocking tables")
|
2016-04-18 17:57:18 +00:00
|
|
|
if _, err := sqlutils.ExecNoPrepare(this.singletonDB, query); err != nil {
|
2016-04-04 16:19:46 +00:00
|
|
|
return err
|
|
|
|
}
|
2016-04-08 08:34:44 +00:00
|
|
|
log.Infof("Tables unlocked")
|
2016-04-04 16:19:46 +00:00
|
|
|
return nil
|
|
|
|
}
|
2016-04-08 12:35:06 +00:00
|
|
|
|
2016-04-22 20:41:20 +00:00
|
|
|
// SwapTablesQuickAndBumpy
|
|
|
|
func (this *Applier) SwapTablesQuickAndBumpy() error {
|
2016-04-18 17:57:18 +00:00
|
|
|
// query := fmt.Sprintf(`rename /* gh-osc */ table %s.%s to %s.%s, %s.%s to %s.%s`,
|
|
|
|
// sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
// sql.EscapeName(this.migrationContext.OriginalTableName),
|
|
|
|
// sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
// sql.EscapeName(this.migrationContext.GetOldTableName()),
|
|
|
|
// sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
// sql.EscapeName(this.migrationContext.GetGhostTableName()),
|
|
|
|
// sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
// sql.EscapeName(this.migrationContext.OriginalTableName),
|
|
|
|
// )
|
|
|
|
// log.Infof("Renaming tables")
|
|
|
|
// if _, err := sqlutils.ExecNoPrepare(this.singletonDB, query); err != nil {
|
|
|
|
// return err
|
|
|
|
// }
|
|
|
|
query := fmt.Sprintf(`alter /* gh-osc */ table %s.%s rename %s`,
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.OriginalTableName),
|
|
|
|
sql.EscapeName(this.migrationContext.GetOldTableName()),
|
|
|
|
)
|
|
|
|
log.Infof("Renaming original table")
|
|
|
|
this.migrationContext.RenameTablesStartTime = time.Now()
|
|
|
|
if _, err := sqlutils.ExecNoPrepare(this.singletonDB, query); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
query = fmt.Sprintf(`alter /* gh-osc */ table %s.%s rename %s`,
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.GetGhostTableName()),
|
|
|
|
sql.EscapeName(this.migrationContext.OriginalTableName),
|
|
|
|
)
|
|
|
|
log.Infof("Renaming ghost table")
|
|
|
|
if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
this.migrationContext.RenameTablesEndTime = time.Now()
|
|
|
|
|
|
|
|
log.Infof("Tables renamed")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-22 20:41:20 +00:00
|
|
|
// SwapTablesQuickAndBumpy
|
|
|
|
func (this *Applier) SwapTablesAtomic() error {
|
|
|
|
query := fmt.Sprintf(`rename /* gh-osc */ table %s.%s to %s.%s, %s.%s to %s.%s`,
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.OriginalTableName),
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.GetOldTableName()),
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.GetGhostTableName()),
|
|
|
|
sql.EscapeName(this.migrationContext.DatabaseName),
|
|
|
|
sql.EscapeName(this.migrationContext.OriginalTableName),
|
|
|
|
)
|
|
|
|
log.Infof("Renaming tables")
|
|
|
|
if _, err := sqlutils.ExecNoPrepare(this.singletonDB, query); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
log.Infof("Tables renamed")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-18 17:57:18 +00:00
|
|
|
// StopSlaveIOThread is applicable with --test-on-replica; it stops the IO thread
|
|
|
|
func (this *Applier) StopSlaveIOThread() error {
|
|
|
|
query := `stop /* gh-osc */ slave io_thread`
|
|
|
|
log.Infof("Stopping replication")
|
|
|
|
if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
log.Infof("Replication stopped")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-08 12:35:06 +00:00
|
|
|
func (this *Applier) ShowStatusVariable(variableName string) (result int64, err error) {
|
|
|
|
query := fmt.Sprintf(`show global status like '%s'`, variableName)
|
|
|
|
if err := this.db.QueryRow(query).Scan(&variableName, &result); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
2016-04-11 15:27:16 +00:00
|
|
|
|
2016-04-14 11:37:56 +00:00
|
|
|
func (this *Applier) buildDMLEventQuery(dmlEvent *binlog.BinlogDMLEvent) (query string, args []interface{}, err error) {
|
2016-04-11 15:27:16 +00:00
|
|
|
switch dmlEvent.DML {
|
|
|
|
case binlog.DeleteDML:
|
|
|
|
{
|
2016-04-14 11:37:56 +00:00
|
|
|
query, uniqueKeyArgs, err := sql.BuildDMLDeleteQuery(dmlEvent.DatabaseName, this.migrationContext.GetGhostTableName(), this.migrationContext.OriginalTableColumns, &this.migrationContext.UniqueKey.Columns, dmlEvent.WhereColumnValues.AbstractValues())
|
|
|
|
return query, uniqueKeyArgs, err
|
|
|
|
}
|
|
|
|
case binlog.InsertDML:
|
|
|
|
{
|
|
|
|
query, sharedArgs, err := sql.BuildDMLInsertQuery(dmlEvent.DatabaseName, this.migrationContext.GetGhostTableName(), this.migrationContext.OriginalTableColumns, this.migrationContext.SharedColumns, dmlEvent.NewColumnValues.AbstractValues())
|
|
|
|
return query, sharedArgs, err
|
|
|
|
}
|
|
|
|
case binlog.UpdateDML:
|
|
|
|
{
|
|
|
|
query, sharedArgs, uniqueKeyArgs, err := sql.BuildDMLUpdateQuery(dmlEvent.DatabaseName, this.migrationContext.GetGhostTableName(), this.migrationContext.OriginalTableColumns, this.migrationContext.SharedColumns, &this.migrationContext.UniqueKey.Columns, dmlEvent.NewColumnValues.AbstractValues(), dmlEvent.WhereColumnValues.AbstractValues())
|
|
|
|
args = append(args, sharedArgs...)
|
|
|
|
args = append(args, uniqueKeyArgs...)
|
|
|
|
return query, args, err
|
2016-04-11 15:27:16 +00:00
|
|
|
}
|
|
|
|
}
|
2016-04-14 11:37:56 +00:00
|
|
|
return "", args, fmt.Errorf("Unknown dml event type: %+v", dmlEvent.DML)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *Applier) ApplyDMLEventQuery(dmlEvent *binlog.BinlogDMLEvent) error {
|
|
|
|
query, args, err := this.buildDMLEventQuery(dmlEvent)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = sqlutils.Exec(this.db, query, args...)
|
2016-04-19 11:25:32 +00:00
|
|
|
if err == nil {
|
|
|
|
atomic.AddInt64(&this.migrationContext.TotalDMLEventsApplied, 1)
|
|
|
|
}
|
2016-04-14 11:37:56 +00:00
|
|
|
return err
|
2016-04-11 15:27:16 +00:00
|
|
|
}
|