@@ -22,6 +22,7 @@ import (
2222// * uses /* -- and # for comments
2323// * supports advisory locks
2424// * has quoting modes (ANSI_QUOTES)
25+ // * can use ALTER TABLE to modify the primary key of a table
2526//
2627// Because mysql DDL commands cause transactions to autocommit, tracking the schema changes in
2728// a secondary table (like libschema does) is inherently unsafe. The MySQL driver will
@@ -43,9 +44,9 @@ type MySQL struct {
4344 lockTx * sql.Tx
4445 lockStr string
4546 db * sql.DB
46- databaseName string // used in skip.go only
47+ databaseName string
4748 lock sync.Mutex
48- trackingSchemaTable func (* libschema.Database ) (string , string , error )
49+ trackingSchemaTable func (* libschema.Database ) (string , string , string , error )
4950 skipDatabase bool
5051}
5152
@@ -219,7 +220,7 @@ func (p *MySQL) DoOneMigration(ctx context.Context, log *internal.Log, d *libsch
219220// called internally which means that is safe to override
220221// in types that embed MySQL.
221222func (p * MySQL ) CreateSchemaTableIfNotExists (ctx context.Context , _ * internal.Log , d * libschema.Database ) error {
222- schema , tableName , err := p .trackingSchemaTable (d )
223+ schema , tableName , _ , err := p .trackingSchemaTable (d )
223224 if err != nil {
224225 return err
225226 }
@@ -248,7 +249,10 @@ func (p *MySQL) CreateSchemaTableIfNotExists(ctx context.Context, _ *internal.Lo
248249
249250var simpleIdentifierRE = regexp .MustCompile (`\A[A-Za-z][A-Za-z0-9_]*\z` )
250251
251- func WithTrackingTableQuoter (f func (* libschema.Database ) (schemaName string , tableName string , err error )) MySQLOpt {
252+ // WithTrackingTableQuoter is a somewhat internal function -- used by lssinglestore.
253+ // It replaces the private function that takes apart the name of the tracking
254+ // table and provides the components.
255+ func WithTrackingTableQuoter (f func (* libschema.Database ) (schemaName , tableName , simpleTableName string , err error )) MySQLOpt {
252256 return func (p * MySQL ) {
253257 p .trackingSchemaTable = f
254258 }
@@ -259,34 +263,34 @@ func WithTrackingTableQuoter(f func(*libschema.Database) (schemaName string, tab
259263// mode, you could have a table called `table` (eg: `CREATE TABLE "table"`) but
260264// if you're not in ANSI_QUOTES mode then you cannot. We're going to assume
261265// that we're not in ANSI_QUOTES mode because we cannot assume that we are.
262- func trackingSchemaTable (d * libschema.Database ) (string , string , error ) {
266+ func trackingSchemaTable (d * libschema.Database ) (string , string , string , error ) {
263267 tableName := d .Options .TrackingTable
264268 s := strings .Split (tableName , "." )
265269 switch len (s ) {
266270 case 2 :
267271 schema := s [0 ]
268272 if ! simpleIdentifierRE .MatchString (schema ) {
269- return "" , "" , errors .Errorf ("Tracking table schema name must be a simple identifier, not '%s'" , schema )
273+ return "" , "" , "" , errors .Errorf ("Tracking table schema name must be a simple identifier, not '%s'" , schema )
270274 }
271275 table := s [1 ]
272276 if ! simpleIdentifierRE .MatchString (table ) {
273- return "" , "" , errors .Errorf ("Tracking table table name must be a simple identifier, not '%s'" , table )
277+ return "" , "" , "" , errors .Errorf ("Tracking table table name must be a simple identifier, not '%s'" , table )
274278 }
275- return schema , schema + "." + table , nil
279+ return schema , schema + "." + table , table , nil
276280 case 1 :
277281 if ! simpleIdentifierRE .MatchString (tableName ) {
278- return "" , "" , errors .Errorf ("Tracking table table name must be a simple identifier, not '%s'" , tableName )
282+ return "" , "" , "" , errors .Errorf ("Tracking table table name must be a simple identifier, not '%s'" , tableName )
279283 }
280- return "" , tableName , nil
284+ return "" , tableName , tableName , nil
281285 default :
282- return "" , "" , errors .Errorf ("Tracking table '%s' is not valid" , tableName )
286+ return "" , "" , "" , errors .Errorf ("Tracking table '%s' is not valid" , tableName )
283287 }
284288}
285289
286290// trackingTable returns the schema+table reference for the migration tracking table.
287291// The name is already quoted properly for use as a save mysql identifier.
288292func (p * MySQL ) trackingTable (d * libschema.Database ) string {
289- _ , table , _ := p .trackingSchemaTable (d )
293+ _ , table , _ , _ := p .trackingSchemaTable (d )
290294 return table
291295}
292296
@@ -301,9 +305,9 @@ func (p *MySQL) saveStatus(log *internal.Log, tx *sql.Tx, d *libschema.Database,
301305 "error" : migrationError ,
302306 })
303307 q := fmt .Sprintf (`
304- REPLACE INTO %s (library, migration, done, error, updated_at)
305- VALUES (?, ?, ?, ?, now())` , p .trackingTable (d ))
306- _ , err := tx .Exec (q , m .Base ().Name .Library , m .Base ().Name .Name , done , estr )
308+ REPLACE INTO %s (db_name, library, migration, done, error, updated_at)
309+ VALUES (?, ?, ?, ?, ?, now())` , p .trackingTable (d ))
310+ _ , err := tx .Exec (q , p . databaseName , m .Base ().Name .Library , m .Base ().Name .Name , done , estr )
307311 if err != nil {
308312 return errors .Wrapf (err , "Save status for %s" , m .Base ().Name )
309313 }
@@ -321,14 +325,15 @@ func (p *MySQL) saveStatus(log *internal.Log, tx *sql.Tx, d *libschema.Database,
321325// does not release the lock. We'll use a transaction just to make sure that
322326// we're using the same connection. If LockMigrationsTable succeeds, be sure to
323327// call UnlockMigrationsTable.
324- func (p * MySQL ) LockMigrationsTable (ctx context.Context , _ * internal.Log , d * libschema.Database ) error {
325- // LockMigrationsTable is overridden for SingleStore
326- p .lock .Lock ()
327- defer p .lock .Unlock ()
328- _ , tableName , err := p .trackingSchemaTable (d )
328+ func (p * MySQL ) LockMigrationsTable (ctx context.Context , _ * internal.Log , d * libschema.Database ) (finalErr error ) {
329+ schema , tableName , simpleTableName , err := p .trackingSchemaTable (d )
329330 if err != nil {
330331 return err
331332 }
333+
334+ // LockMigrationsTable is overridden for SingleStore
335+ p .lock .Lock ()
336+ defer p .lock .Unlock ()
332337 if p .lockTx != nil {
333338 return errors .Errorf ("libschema migrations table, '%s' already locked" , tableName )
334339 }
@@ -343,6 +348,56 @@ func (p *MySQL) LockMigrationsTable(ctx context.Context, _ *internal.Log, d *lib
343348 return errors .Wrapf (err , "Could not get lock for libschema migrations" )
344349 }
345350 p .lockTx = tx
351+
352+ // This moment, after getting an exclusive lock on the migrations table, is
353+ // the right moment to do any schema upgrades of the migrations table.
354+
355+ defer func () {
356+ if finalErr != nil {
357+ _ , _ = tx .Exec (`SELECT RELEASE_LOCK(?)` , p .lockStr )
358+ _ = tx .Rollback ()
359+ p .lockTx = nil
360+ }
361+ }()
362+
363+ currentDatabaseValue := p .databaseName
364+ defer func () {
365+ p .databaseName = currentDatabaseValue
366+ }()
367+ p .databaseName = schema
368+
369+ ok , err := p .DoesColumnExist (simpleTableName , "db_name" )
370+ if err != nil {
371+ return errors .Wrapf (err , "could not check if %s has a db_name column" , tableName )
372+ }
373+ if ! ok {
374+ _ , err = d .DB ().ExecContext (ctx , fmt .Sprintf (`
375+ ALTER TABLE %s
376+ ADD COLUMN db_name varchar(255)` , tableName ))
377+ if err != nil {
378+ return errors .Wrapf (err , "could not add db_name column to %s" , tableName )
379+ }
380+ }
381+ ok , err = p .ColumnIsInPrimaryKey (simpleTableName , "db_name" )
382+ if err != nil {
383+ return errors .Wrapf (err , "could not check if %s.db_name column is in the primary key" , tableName )
384+ }
385+ if ok {
386+ return nil
387+ }
388+ _ , err = d .DB ().ExecContext (ctx , fmt .Sprintf (`
389+ UPDATE %s
390+ SET db_name = ?
391+ WHERE db_name IS NULL` , tableName ), p .databaseName )
392+ if err != nil {
393+ return errors .Wrapf (err , "could not set %s.db_name column" , tableName )
394+ }
395+ _ , err = d .DB ().ExecContext (ctx , fmt .Sprintf (`
396+ ALTER TABLE %s
397+ DROP PRIMARY KEY, ADD PRIMARY KEY (db_name, library, migration)` , tableName ))
398+ if err != nil {
399+ return errors .Wrapf (err , "could change primary key for %s" , tableName )
400+ }
346401 return nil
347402}
348403
@@ -379,7 +434,8 @@ func (p *MySQL) LoadStatus(ctx context.Context, _ *internal.Log, d *libschema.Da
379434 tableName := p .trackingTable (d )
380435 rows , err := d .DB ().QueryContext (ctx , fmt .Sprintf (`
381436 SELECT library, migration, done
382- FROM %s` , tableName ))
437+ FROM %s
438+ WHERE db_name = ?` , tableName ), p .databaseName )
383439 if err != nil {
384440 return nil , errors .Wrap (err , "Cannot query migration status" )
385441 }
0 commit comments