sqldb: separate migration execution from construction

This commit separates the execution of SQL and in-code migrations
from their construction. This change is necessary because,
currently, the SQL schema is migrated during the construction
phase in the lncfg package. However, migrations are typically
executed when individual stores are constructed within the
configuration builder.
This commit is contained in:
Andras Banki-Horvath
2024-11-25 20:29:08 +01:00
parent b789fb2db3
commit 91c3e1496f
8 changed files with 137 additions and 57 deletions

View File

@@ -355,6 +355,18 @@ func (t *TransactionExecutor[Q]) ExecTx(ctx context.Context,
)
}
// DB is an interface that represents a generic SQL database. It provides
// methods to apply migrations and access the underlying database connection.
type DB interface {
// GetBaseDB returns the underlying BaseDB instance.
GetBaseDB() *BaseDB
// ApplyAllMigrations applies all migrations to the database including
// both sqlc and custom in-code migrations.
ApplyAllMigrations(ctx context.Context,
customMigrations []MigrationConfig) error
}
// BaseDB is the base database struct that each implementation can embed to
// gain some common functionality.
type BaseDB struct {

View File

@@ -314,16 +314,19 @@ func TestCustomMigration(t *testing.T) {
for i := 0; i < 3; i++ {
db, err = NewSqliteStore(&SqliteConfig{
SkipMigrations: false,
}, dbFileName, test.migrations)
if db != nil {
dbToCleanup := db.DB
t.Cleanup(func() {
require.NoError(
t, dbToCleanup.Close(),
)
})
}
}, dbFileName)
require.NoError(t, err)
dbToCleanup := db.DB
t.Cleanup(func() {
require.NoError(
t, dbToCleanup.Close(),
)
})
err = db.ApplyAllMigrations(
ctxb, test.migrations,
)
if test.expectedSuccess {
require.NoError(t, err)
} else {
@@ -333,7 +336,7 @@ func TestCustomMigration(t *testing.T) {
// so we can read versions.
db, err = NewSqliteStore(&SqliteConfig{
SkipMigrations: true,
}, dbFileName, nil)
}, dbFileName)
require.NoError(t, err)
}
@@ -399,8 +402,12 @@ func TestCustomMigration(t *testing.T) {
// are idempotent.
for i := 0; i < 3; i++ {
cfg.SkipMigrations = false
db, err = NewPostgresStore(cfg, test.migrations)
db, err = NewPostgresStore(cfg)
require.NoError(t, err)
err = db.ApplyAllMigrations(
ctxb, test.migrations,
)
if test.expectedSuccess {
require.NoError(t, err)
} else {
@@ -409,7 +416,7 @@ func TestCustomMigration(t *testing.T) {
// Also repoen the DB without migrations
// so we can read versions.
cfg.SkipMigrations = true
db, err = NewPostgresStore(cfg, nil)
db, err = NewPostgresStore(cfg)
require.NoError(t, err)
}

View File

@@ -2,7 +2,15 @@
package sqldb
import "fmt"
import (
"context"
"fmt"
)
var (
// Make sure SqliteStore implements the DB interface.
_ DB = (*SqliteStore)(nil)
)
// SqliteStore is a database store implementation that uses a sqlite backend.
type SqliteStore struct {
@@ -16,3 +24,17 @@ type SqliteStore struct {
func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) {
return nil, fmt.Errorf("SQLite backend not supported in WebAssembly")
}
// GetBaseDB returns the underlying BaseDB instance for the SQLite store.
// It is a trivial helper method to comply with the sqldb.DB interface.
func (s *SqliteStore) GetBaseDB() *BaseDB {
return s.BaseDB
}
// ApplyAllMigrations applies both the SQLC and custom in-code migrations to
// the SQLite database.
func (s *SqliteStore) ApplyAllMigrations(context.Context,
[]MigrationConfig) error {
return fmt.Errorf("SQLite backend not supported in WebAssembly")
}

View File

@@ -36,6 +36,9 @@ var (
// Make sure PostgresStore implements the MigrationExecutor interface.
_ MigrationExecutor = (*PostgresStore)(nil)
// Make sure PostgresStore implements the DB interface.
_ DB = (*PostgresStore)(nil)
)
// replacePasswordInDSN takes a DSN string and returns it with the password
@@ -89,9 +92,7 @@ type PostgresStore struct {
// NewPostgresStore creates a new store that is backed by a Postgres database
// backend.
func NewPostgresStore(cfg *PostgresConfig, migrations []MigrationConfig) (
*PostgresStore, error) {
func NewPostgresStore(cfg *PostgresConfig) (*PostgresStore, error) {
sanitizedDSN, err := replacePasswordInDSN(cfg.Dsn)
if err != nil {
return nil, err
@@ -130,25 +131,32 @@ func NewPostgresStore(cfg *PostgresConfig, migrations []MigrationConfig) (
queries := sqlc.New(db)
s := &PostgresStore{
return &PostgresStore{
cfg: cfg,
BaseDB: &BaseDB{
DB: db,
Queries: queries,
},
}
}, nil
}
// GetBaseDB returns the underlying BaseDB instance for the Postgres store.
// It is a trivial helper method to comply with the sqldb.DB interface.
func (s *PostgresStore) GetBaseDB() *BaseDB {
return s.BaseDB
}
// ApplyAllMigrations applies both the SQLC and custom in-code migrations to the
// Postgres database.
func (s *PostgresStore) ApplyAllMigrations(ctx context.Context,
migrations []MigrationConfig) error {
// Execute migrations unless configured to skip them.
if !cfg.SkipMigrations {
err := ApplyMigrations(
context.Background(), s.BaseDB, s, migrations,
)
if err != nil {
return nil, err
}
if s.cfg.SkipMigrations {
return nil
}
return s, nil
return ApplyMigrations(ctx, s.BaseDB, s, migrations)
}
// ExecuteMigrations runs migrations for the Postgres database, depending on the

View File

@@ -148,9 +148,13 @@ func NewTestPostgresDB(t *testing.T, fixture *TestPgFixture) *PostgresStore {
require.NoError(t, err)
cfg := fixture.GetConfig(dbName)
store, err := NewPostgresStore(cfg, GetMigrations())
store, err := NewPostgresStore(cfg)
require.NoError(t, err)
require.NoError(t, store.ApplyAllMigrations(
context.Background(), GetMigrations()),
)
return store
}
@@ -172,7 +176,7 @@ func NewTestPostgresDBWithVersion(t *testing.T, fixture *TestPgFixture,
storeCfg := fixture.GetConfig(dbName)
storeCfg.SkipMigrations = true
store, err := NewPostgresStore(storeCfg, GetMigrations())
store, err := NewPostgresStore(storeCfg)
require.NoError(t, err)
err = store.ExecuteMigrations(TargetVersion(version))

View File

@@ -38,6 +38,9 @@ var (
// Make sure SqliteStore implements the MigrationExecutor interface.
_ MigrationExecutor = (*SqliteStore)(nil)
// Make sure SqliteStore implements the DB interface.
_ DB = (*SqliteStore)(nil)
)
// SqliteStore is a database store implementation that uses a sqlite backend.
@@ -49,9 +52,7 @@ type SqliteStore struct {
// NewSqliteStore attempts to open a new sqlite database based on the passed
// config.
func NewSqliteStore(cfg *SqliteConfig, dbPath string,
migrations []MigrationConfig) (*SqliteStore, error) {
func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) {
// The set of pragma options are accepted using query options. For now
// we only want to ensure that foreign key constraints are properly
// enforced.
@@ -138,17 +139,26 @@ func NewSqliteStore(cfg *SqliteConfig, dbPath string,
},
}
return s, nil
}
// GetBaseDB returns the underlying BaseDB instance for the SQLite store.
// It is a trivial helper method to comply with the sqldb.DB interface.
func (s *SqliteStore) GetBaseDB() *BaseDB {
return s.BaseDB
}
// ApplyAllMigrations applies both the SQLC and custom in-code migrations to the
// SQLite database.
func (s *SqliteStore) ApplyAllMigrations(ctx context.Context,
migrations []MigrationConfig) error {
// Execute migrations unless configured to skip them.
if !cfg.SkipMigrations {
err := ApplyMigrations(
context.Background(), s.BaseDB, s, migrations,
)
if err != nil {
return nil, err
}
if s.cfg.SkipMigrations {
return nil
}
return s, nil
return ApplyMigrations(ctx, s.BaseDB, s, migrations)
}
// ExecuteMigrations runs migrations for the sqlite database, depending on the
@@ -181,9 +191,13 @@ func NewTestSqliteDB(t *testing.T) *SqliteStore {
dbFileName := filepath.Join(t.TempDir(), "tmp.db")
sqlDB, err := NewSqliteStore(&SqliteConfig{
SkipMigrations: false,
}, dbFileName, GetMigrations())
}, dbFileName)
require.NoError(t, err)
require.NoError(t, sqlDB.ApplyAllMigrations(
context.Background(), GetMigrations()),
)
t.Cleanup(func() {
require.NoError(t, sqlDB.DB.Close())
})
@@ -204,7 +218,7 @@ func NewTestSqliteDBWithVersion(t *testing.T, version uint) *SqliteStore {
dbFileName := filepath.Join(t.TempDir(), "tmp.db")
sqlDB, err := NewSqliteStore(&SqliteConfig{
SkipMigrations: true,
}, dbFileName, nil)
}, dbFileName)
require.NoError(t, err)
err = sqlDB.ExecuteMigrations(TargetVersion(version))