Move migrations to internal/db/migrations (closes #96)
check / check (push) Successful in 3m13s
check / check (push) Successful in 3m13s
REPO_POLICIES.md puts migrations in internal/db/migrations/ as 000_migration.sql and 001_schema.sql. The two files move there with their contents unchanged. go:embed cannot reach outside its own package, so internal/db/migrations has a small package that embeds them, and internal/database reads them through its FS(). The database package stays where CONVENTIONS.md puts it; moving it would change existing test files in other packages. The version still comes from the filename prefix, so a database that has recorded versions 0 and 1 runs neither again. A new test applies the migrations twice to one database file and checks that the second run applies nothing. Model: opus-5-5
This commit is contained in:
@@ -4,9 +4,9 @@ package database
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -15,14 +15,12 @@ import (
|
||||
|
||||
"go.uber.org/fx"
|
||||
"sneak.berlin/go/pixa/internal/config"
|
||||
"sneak.berlin/go/pixa/internal/db/migrations"
|
||||
"sneak.berlin/go/pixa/internal/logger"
|
||||
|
||||
_ "modernc.org/sqlite" // SQLite driver registration
|
||||
)
|
||||
|
||||
//go:embed schema/*.sql
|
||||
var schemaFS embed.FS
|
||||
|
||||
// bootstrapVersion is the migration that creates the schema_migrations
|
||||
// table itself. It is applied before the normal migration loop.
|
||||
const bootstrapVersion = 0
|
||||
@@ -113,29 +111,29 @@ func New(lc fx.Lifecycle, params Params) (*Database, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// collectMigrations reads the embedded schema directory and returns
|
||||
// collectMigrations reads the embedded migrations directory and returns
|
||||
// migration filenames sorted lexicographically.
|
||||
func collectMigrations() ([]string, error) {
|
||||
entries, err := schemaFS.ReadDir("schema")
|
||||
entries, err := fs.ReadDir(migrations.FS(), ".")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read schema directory: %w", err)
|
||||
return nil, fmt.Errorf("failed to read migrations directory: %w", err)
|
||||
}
|
||||
|
||||
var migrations []string
|
||||
var filenames []string
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".sql") {
|
||||
migrations = append(migrations, entry.Name())
|
||||
filenames = append(filenames, entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(migrations)
|
||||
sort.Strings(filenames)
|
||||
|
||||
return migrations, nil
|
||||
return filenames, nil
|
||||
}
|
||||
|
||||
// bootstrapMigrationsTable ensures the schema_migrations table exists
|
||||
// by applying 000.sql if the table is missing.
|
||||
// by applying 000_migration.sql if the table is missing.
|
||||
func bootstrapMigrationsTable(ctx context.Context, db *sql.DB, log *slog.Logger) error {
|
||||
var tableExists int
|
||||
|
||||
@@ -150,9 +148,9 @@ func bootstrapMigrationsTable(ctx context.Context, db *sql.DB, log *slog.Logger)
|
||||
return nil
|
||||
}
|
||||
|
||||
content, err := schemaFS.ReadFile("schema/000.sql")
|
||||
content, err := fs.ReadFile(migrations.FS(), "000_migration.sql")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read bootstrap migration 000.sql: %w", err)
|
||||
return fmt.Errorf("failed to read bootstrap migration 000_migration.sql: %w", err)
|
||||
}
|
||||
|
||||
if log != nil {
|
||||
@@ -177,12 +175,12 @@ func ApplyMigrations(ctx context.Context, db *sql.DB, log *slog.Logger) error {
|
||||
return err
|
||||
}
|
||||
|
||||
migrations, err := collectMigrations()
|
||||
filenames, err := collectMigrations()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, migration := range migrations {
|
||||
for _, migration := range filenames {
|
||||
version, parseErr := ParseMigrationVersion(migration)
|
||||
if parseErr != nil {
|
||||
return parseErr
|
||||
@@ -208,7 +206,7 @@ func ApplyMigrations(ctx context.Context, db *sql.DB, log *slog.Logger) error {
|
||||
}
|
||||
|
||||
// Read and apply migration.
|
||||
content, readErr := schemaFS.ReadFile(filepath.Join("schema", migration))
|
||||
content, readErr := fs.ReadFile(migrations.FS(), migration)
|
||||
if readErr != nil {
|
||||
return fmt.Errorf("failed to read migration %s: %w", migration, readErr)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user