Support upgrading from old migration table name (#889)

This commit is contained in:
Todd Knight authored and GitHub committed 2021-01-28 14:30:08 -07:00
1 parent 3461dcbd0c
commit 955ae41215
10 files changed
+489 -161

No files matched your search

+108 -48
View File
@@ -77,11 +77,6 @@ func New(ctx context.Context, instance *sql.DB) (*Postgres, error) {
conn: conn,
db: instance,
}
if err := px.ensureVersionTable(ctx); err != nil {
return nil, errors.Wrap(err, op)
}
return px, nil
}
@@ -89,7 +84,7 @@ func New(ctx context.Context, instance *sql.DB) (*Postgres, error) {
// https://www.postgresql.org/docs/9.6/static/explicit-locking.html#ADVISORY-LOCKS
func (p *Postgres) TrySharedLock(ctx context.Context) error {
const op = "postgres.(Postgres).TrySharedLock"
const query = "SELECT pg_try_advisory_lock_shared($1)"
const query = "select pg_try_advisory_lock_shared($1)"
r := p.conn.QueryRowContext(ctx, query, schemaAccessLockId)
if r.Err() != nil {
return errors.Wrap(r.Err(), op)
@@ -108,7 +103,7 @@ func (p *Postgres) TrySharedLock(ctx context.Context) error {
// https://www.postgresql.org/docs/9.6/static/explicit-locking.html#ADVISORY-LOCKS
func (p *Postgres) TryLock(ctx context.Context) error {
const op = "postgres.(Postgres).TryLock"
const query = "SELECT pg_try_advisory_lock($1)"
const query = "select pg_try_advisory_lock($1)"
r := p.conn.QueryRowContext(ctx, query, schemaAccessLockId)
if r.Err() != nil {
return errors.Wrap(r.Err(), op)
@@ -127,7 +122,7 @@ func (p *Postgres) TryLock(ctx context.Context) error {
// if we were unable to get the lock before the context cancels.
func (p *Postgres) Lock(ctx context.Context) error {
const op = "postgres.(Postgres).Lock"
const query = "SELECT pg_advisory_lock($1)"
const query = "select pg_advisory_lock($1)"
if _, err := p.conn.ExecContext(ctx, query, schemaAccessLockId); err != nil {
return errors.Wrap(err, op)
}
@@ -138,7 +133,7 @@ func (p *Postgres) Lock(ctx context.Context) error {
// release the lock before the context cancels.
func (p *Postgres) Unlock(ctx context.Context) error {
const op = "postgres.(Postgres).Unlock"
const query = `SELECT pg_advisory_unlock($1)`
const query = `select pg_advisory_unlock($1)`
if _, err := p.conn.ExecContext(ctx, query, schemaAccessLockId); err != nil {
return errors.Wrap(err, op)
}
@@ -149,13 +144,29 @@ func (p *Postgres) Unlock(ctx context.Context) error {
// release the lock before the context cancels.
func (p *Postgres) UnlockShared(ctx context.Context) error {
const op = "postgres.(Postgres).UnlockShared"
query := `SELECT pg_advisory_unlock_shared($1)`
query := `select pg_advisory_unlock_shared($1)`
if _, err := p.conn.ExecContext(ctx, query, schemaAccessLockId); err != nil {
return errors.Wrap(err, op)
}
return nil
}
// Rollback rolls back the outstanding transaction.
// Calling Rollback when there is not an outstanding transaction is an error.
func (p *Postgres) Rollback() error {
const op = "postgres.(Postgres).Rollback"
defer func() {
p.tx = nil
}()
if p.tx == nil {
return errors.New(errors.MigrationIntegrity, op, "no pending transaction")
}
if err := p.tx.Rollback(); err != nil {
return errors.Wrap(err, op)
}
return nil
}
// StartRun starts a transaction that all subsequent calls to Run will use.
func (p *Postgres) StartRun(ctx context.Context) error {
tx, err := p.conn.BeginTx(ctx, nil)
@@ -177,19 +188,21 @@ func (p *Postgres) CommitRun() error {
}
if err := p.tx.Commit(); err != nil {
if errRollback := p.tx.Rollback(); errRollback != nil {
return multierror.Append(err, errRollback)
err = multierror.Append(err, errRollback)
}
return errors.Wrap(err, op)
}
return nil
}
type execContexter interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}
// Executes the sql provided in the passed in io.Reader. The contents of the reader must
// Run executes the sql provided in the passed in io.Reader. The contents of the reader must
// fit in memory as the full content is read into a string before being passed to the
// backing database.
// backing database. EnsureVersionTable should be ran prior to this call.
func (p *Postgres) Run(ctx context.Context, migration io.Reader, version int) error {
const op = "postgres.(Postgres).Run"
migr, err := ioutil.ReadAll(migration)
@@ -293,7 +306,7 @@ func (p *Postgres) setVersion(ctx context.Context, version int, dirty bool) erro
return tx.Rollback()
}
query := `TRUNCATE ` + pq.QuoteIdentifier(defaultMigrationsTable)
query := `truncate ` + pq.QuoteIdentifier(defaultMigrationsTable)
if _, err := tx.ExecContext(ctx, query); err != nil {
if errRollback := rollback(); errRollback != nil {
err = multierror.Append(err, errRollback)
@@ -305,8 +318,8 @@ func (p *Postgres) setVersion(ctx context.Context, version int, dirty bool) erro
// empty schema Version for failed down migration on the first migration
// See: https://github.com/golang-migrate/migrate/issues/330
if version >= 0 || (version == nilVersion && dirty) {
query = `INSERT INTO ` + pq.QuoteIdentifier(defaultMigrationsTable) +
` (Version, dirty) VALUES ($1, $2)`
query = `insert into ` + pq.QuoteIdentifier(defaultMigrationsTable) +
` (version, dirty) values ($1, $2)`
if _, err := tx.ExecContext(ctx, query, version, dirty); err != nil {
if errRollback := rollback(); errRollback != nil {
err = multierror.Append(err, errRollback)
@@ -323,33 +336,58 @@ func (p *Postgres) setVersion(ctx context.Context, version int, dirty bool) erro
return nil
}
// CurrentState returns the version, if the database is currently in a dirty state, and any error.
// A version value of -1 indicates no version is set.
func (p *Postgres) CurrentState(ctx context.Context) (version int, dirty bool, err error) {
// CurrentState returns the version, if the database was ever initialized
// previously, if it is currently in a dirty state, and any error. A version
// value of -1 indicates no version is set.
func (p *Postgres) CurrentState(ctx context.Context) (version int, previouslyRan, dirty bool, err error) {
const op = "postgres.(Postgres).CurrentState"
query := `SELECT Version, dirty FROM ` + pq.QuoteIdentifier(defaultMigrationsTable) + ` LIMIT 1`
err = p.conn.QueryRowContext(ctx, query).Scan(&version, &dirty)
switch {
case err == sql.ErrNoRows:
return nilVersion, false, nil
case err != nil:
if e, ok := err.(*pq.Error); ok {
if e.Code.Name() == "undefined_table" {
return nilVersion, false, nil
}
}
return 0, false, errors.Wrap(err, op)
version = nilVersion
previouslyRan, dirty = false, false
default:
return version, dirty, nil
tableQuery := `select table_name from information_schema.tables where table_schema=(select current_schema()) and table_name in ('schema_migrations', '` + defaultMigrationsTable + `')`
tableResult, err := p.conn.QueryContext(ctx, tableQuery)
if err != nil {
return nilVersion, previouslyRan, dirty, errors.Wrap(err, op)
}
defer tableResult.Close()
if !tableResult.Next() {
// No version table found
return nilVersion, previouslyRan, dirty, nil
}
tableName := defaultMigrationsTable
if err := tableResult.Scan(&tableName); err != nil {
return nilVersion, previouslyRan, dirty, errors.Wrap(err, op)
}
previouslyRan = true
if tableResult.Next() {
return nilVersion, previouslyRan, dirty, errors.New(errors.MigrationIntegrity, op, "both old and new migration tables exist")
}
query := `select version, dirty from ` + pq.QuoteIdentifier(tableName)
results, err := p.conn.QueryContext(ctx, query)
if err != nil {
return nilVersion, previouslyRan, dirty, errors.Wrap(err, op)
}
defer results.Close()
if !results.Next() {
// no version recorded
return nilVersion, previouslyRan, dirty, nil
}
if err := results.Scan(&version, &dirty); err != nil {
return nilVersion, previouslyRan, dirty, errors.Wrap(err, op)
}
if results.Next() {
return nilVersion, previouslyRan, dirty, errors.New(errors.MigrationIntegrity, op, "to many versions in version table")
}
return version, previouslyRan, dirty, nil
}
func (p *Postgres) drop(ctx context.Context) (err error) {
const op = "postgres.(Postgres).drop"
// select all tables in current schema
query := `SELECT table_name FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_type='BASE TABLE'`
query := `select table_name from information_schema.tables where table_schema=(select current_schema()) and table_type='BASE TABLE'`
tables, err := p.conn.QueryContext(ctx, query)
if err != nil {
return errors.Wrap(err, op)
@@ -379,7 +417,7 @@ func (p *Postgres) drop(ctx context.Context) (err error) {
if len(tableNames) > 0 {
// delete one by one ...
for _, t := range tableNames {
query = `DROP TABLE IF EXISTS ` + pq.QuoteIdentifier(t) + ` CASCADE`
query = `drop table if exists ` + pq.QuoteIdentifier(t) + ` cascade`
if _, err := p.conn.ExecContext(ctx, query); err != nil {
return errors.Wrap(err, op)
}
@@ -389,23 +427,45 @@ func (p *Postgres) drop(ctx context.Context) (err error) {
return nil
}
// ensureVersionTable checks if versions table exists and, if not, creates it.
// Note that this function locks the database, which deviates from the usual
// convention of "caller locks" in the postgres type.
func (p *Postgres) ensureVersionTable(ctx context.Context) (err error) {
const op = "postgres.(Postgres).ensureVersionTable"
if err = p.TryLock(ctx); err != nil {
// EnsureVersionTable checks if versions table exists and, if not, creates it.
func (p *Postgres) EnsureVersionTable(ctx context.Context) (err error) {
const op = "postgres.(Postgres).EnsureVersionTable"
var extr execContexter = p.conn
rollback := func() error { return nil }
if p.tx != nil {
extr = p.tx
rollback = func() error {
defer func() { p.tx = nil }()
return p.tx.Rollback()
}
}
query := `select exists (select 1 from information_schema.tables where table_schema=(select current_schema()) and table_name = '` + defaultMigrationsTable + `');`
exists := false
if err := extr.QueryRowContext(ctx, query).Scan(&exists); err != nil {
if wpErr := rollback(); wpErr != nil {
err = multierror.Append(err, wpErr)
}
return errors.Wrap(err, op)
}
if exists {
return nil
}
updateQuery := `alter table if exists schema_migrations rename to ` + defaultMigrationsTable + `;`
if _, err = extr.ExecContext(ctx, updateQuery); err != nil {
if wpErr := rollback(); wpErr != nil {
err = multierror.Append(err, wpErr)
}
return errors.Wrap(err, op)
}
defer func() {
if e := p.Unlock(ctx); e != nil {
err = multierror.Append(err, e)
createStmt := `create table if not exists ` + pq.QuoteIdentifier(defaultMigrationsTable) + ` (version bigint primary key, dirty boolean not null)`
if _, err = extr.ExecContext(ctx, createStmt); err != nil {
if wpErr := rollback(); wpErr != nil {
err = multierror.Append(err, wpErr)
}
}()
query := `CREATE TABLE IF NOT EXISTS ` + pq.QuoteIdentifier(defaultMigrationsTable) + ` (Version bigint primary key, dirty boolean not null)`
if _, err = p.conn.ExecContext(ctx, query); err != nil {
return errors.Wrap(err, op)
}