sqldb: add support for custom in-code migrations

This commit introduces support for custom, in-code migrations, allowing
a specific Go function to be executed at a designated database version
during sqlc migrations. If the current database version surpasses the
specified version, the migration will be skipped.
This commit is contained in:
Andras Banki-Horvath
2024-11-22 18:29:54 +01:00
parent 9acd06d296
commit b789fb2db3
6 changed files with 565 additions and 20 deletions

View File

@@ -1,6 +1,7 @@
package sqldb
import (
"context"
"database/sql"
"fmt"
"net/url"
@@ -32,6 +33,9 @@ var (
"BIGINT PRIMARY KEY": "BIGSERIAL PRIMARY KEY",
"TIMESTAMP": "TIMESTAMP WITHOUT TIME ZONE",
}
// Make sure PostgresStore implements the MigrationExecutor interface.
_ MigrationExecutor = (*PostgresStore)(nil)
)
// replacePasswordInDSN takes a DSN string and returns it with the password
@@ -85,43 +89,62 @@ type PostgresStore struct {
// NewPostgresStore creates a new store that is backed by a Postgres database
// backend.
func NewPostgresStore(cfg *PostgresConfig) (*PostgresStore, error) {
func NewPostgresStore(cfg *PostgresConfig, migrations []MigrationConfig) (
*PostgresStore, error) {
sanitizedDSN, err := replacePasswordInDSN(cfg.Dsn)
if err != nil {
return nil, err
}
log.Infof("Using SQL database '%s'", sanitizedDSN)
rawDB, err := sql.Open("pgx", cfg.Dsn)
db, err := sql.Open("pgx", cfg.Dsn)
if err != nil {
return nil, err
}
// Ensure the migration tracker table exists before running migrations.
// This table tracks migration progress and ensures compatibility with
// SQLC query generation. If the table is already created by an SQLC
// migration, this operation becomes a no-op.
migrationTrackerSQL := `
CREATE TABLE IF NOT EXISTS migration_tracker (
version INTEGER UNIQUE NOT NULL,
migration_time TIMESTAMP NOT NULL
);`
_, err = db.Exec(migrationTrackerSQL)
if err != nil {
return nil, fmt.Errorf("error creating migration tracker: %w",
err)
}
maxConns := defaultMaxConns
if cfg.MaxConnections > 0 {
maxConns = cfg.MaxConnections
}
rawDB.SetMaxOpenConns(maxConns)
rawDB.SetMaxIdleConns(maxConns)
rawDB.SetConnMaxLifetime(connIdleLifetime)
db.SetMaxOpenConns(maxConns)
db.SetMaxIdleConns(maxConns)
db.SetConnMaxLifetime(connIdleLifetime)
queries := sqlc.New(rawDB)
queries := sqlc.New(db)
s := &PostgresStore{
cfg: cfg,
BaseDB: &BaseDB{
DB: rawDB,
DB: db,
Queries: queries,
},
}
// Execute migrations unless configured to skip them.
if !cfg.SkipMigrations {
err := s.ExecuteMigrations(TargetLatest)
err := ApplyMigrations(
context.Background(), s.BaseDB, s, migrations,
)
if err != nil {
return nil, fmt.Errorf("error executing migrations: %w",
err)
return nil, err
}
}