Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: track seeded files in migration table #2728

Merged
merged 5 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions internal/db/push/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ func Run(ctx context.Context, dryRun, ignoreVersionMismatch bool, includeRoles,
if err != nil {
return err
}
if len(pending) == 0 {
var seeds []migration.SeedFile
if includeSeed {
seeds, err = migration.GetPendingSeeds(ctx, utils.Config.Db.Seed.SqlPaths, conn, afero.NewIOFS(fsys))
if err != nil {
return err
}
}
if len(pending) == 0 && len(seeds) == 0 {
fmt.Println("Remote database is up to date.")
return nil
}
Expand All @@ -38,10 +45,13 @@ func Run(ctx context.Context, dryRun, ignoreVersionMismatch bool, includeRoles,
if includeRoles {
fmt.Fprintln(os.Stderr, "Would create custom roles "+utils.Bold(utils.CustomRolesPath)+"...")
}
fmt.Fprintln(os.Stderr, "Would push these migrations:")
fmt.Fprint(os.Stderr, utils.Bold(confirmPushAll(pending)))
if includeSeed {
fmt.Fprintf(os.Stderr, "Would seed data %v...\n", utils.Config.Db.Seed.SqlPaths)
if len(pending) > 0 {
fmt.Fprintln(os.Stderr, "Would push these migrations:")
fmt.Fprint(os.Stderr, utils.Bold(confirmPushAll(pending)))
}
if includeSeed && len(seeds) > 0 {
fmt.Fprintln(os.Stderr, "Would seed these files:")
fmt.Fprint(os.Stderr, utils.Bold(confirmSeedAll(seeds)))
}
} else {
msg := fmt.Sprintf("Do you want to push these migrations to the remote database?\n%s\n", confirmPushAll(pending))
Expand All @@ -59,7 +69,7 @@ func Run(ctx context.Context, dryRun, ignoreVersionMismatch bool, includeRoles,
return err
}
if includeSeed {
if err := apply.SeedDatabase(ctx, conn, fsys); err != nil {
if err := migration.SeedData(ctx, seeds, conn, afero.NewIOFS(fsys)); err != nil {
return err
}
}
Expand All @@ -75,3 +85,14 @@ func confirmPushAll(pending []string) (msg string) {
}
return msg
}

func confirmSeedAll(pending []migration.SeedFile) (msg string) {
for _, seed := range pending {
notice := seed.Path
if seed.Dirty {
notice += " (hash update)"
}
msg += fmt.Sprintf(" • %s\n", notice)
}
return msg
}
15 changes: 11 additions & 4 deletions internal/db/push/push_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package push

import (
"context"
"crypto/sha256"
"encoding/hex"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -161,24 +163,29 @@ func TestPushAll(t *testing.T) {
})

t.Run("throws error on seed failure", func(t *testing.T) {
digest := hex.EncodeToString(sha256.New().Sum(nil))
seedPath := filepath.Join(utils.SupabaseDirPath, "seed.sql")
utils.Config.Db.Seed.SqlPaths = []string{seedPath}
// Setup in-memory fs
fsys := &fstest.OpenErrorFs{DenyPath: seedPath}
_, _ = fsys.Create(seedPath)
fsys := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fsys, seedPath, []byte{}, 0644))
path := filepath.Join(utils.MigrationsDir, "0_test.sql")
require.NoError(t, afero.WriteFile(fsys, path, []byte{}, 0644))
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(migration.LIST_MIGRATION_VERSION).
Reply("SELECT 0").
Query(migration.SELECT_SEED_TABLE).
Reply("SELECT 0")
helper.MockMigrationHistory(conn).
Query(migration.INSERT_MIGRATION_VERSION, "0", "test", nil).
Reply("INSERT 0 1")
Reply("INSERT 0 1").
Query(migration.UPSERT_SEED_FILE, seedPath, digest).
ReplyError(pgerrcode.NotNullViolation, `null value in column "hash" of relation "seed_files"`)
// Run test
err := Run(context.Background(), false, false, false, true, dbConfig, fsys, conn.Intercept)
// Check error
assert.ErrorIs(t, err, os.ErrPermission)
assert.ErrorContains(t, err, `ERROR: null value in column "hash" of relation "seed_files" (SQLSTATE 23502)`)
})
}
15 changes: 1 addition & 14 deletions internal/db/reset/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,6 @@ func resetDatabase14(ctx context.Context, version string, fsys afero.Fs, options
return err
}
defer conn.Close(context.Background())
if utils.Config.Db.MajorVersion > 14 {
if err := start.SetupDatabase(ctx, conn, utils.DbId, os.Stderr, fsys); err != nil {
return err
}
}
return apply.MigrateAndSeed(ctx, version, conn, fsys)
}

Expand Down Expand Up @@ -132,15 +127,7 @@ func resetDatabase15(ctx context.Context, version string, fsys afero.Fs, options
if err := start.WaitForHealthyService(ctx, start.HealthTimeout, utils.DbId); err != nil {
return err
}
conn, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{}, options...)
if err != nil {
return err
}
defer conn.Close(context.Background())
if err := start.SetupDatabase(ctx, conn, utils.DbId, os.Stderr, fsys); err != nil {
return err
}
if err := apply.MigrateAndSeed(ctx, version, conn, fsys); err != nil {
if err := start.SetupLocalDatabase(ctx, version, fsys, os.Stderr, options...); err != nil {
avallete marked this conversation as resolved.
Show resolved Hide resolved
return err
}
fmt.Fprintln(os.Stderr, "Restarting containers...")
Expand Down
6 changes: 3 additions & 3 deletions internal/db/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ EOF
}
// Initialize if we are on PG14 and there's no existing db volume
if utils.NoBackupVolume {
if err := setupDatabase(ctx, fsys, w, options...); err != nil {
if err := SetupLocalDatabase(ctx, "", fsys, w, options...); err != nil {
return err
}
}
Expand Down Expand Up @@ -310,7 +310,7 @@ func initSchema15(ctx context.Context, host string) error {
return nil
}

func setupDatabase(ctx context.Context, fsys afero.Fs, w io.Writer, options ...func(*pgx.ConnConfig)) error {
func SetupLocalDatabase(ctx context.Context, version string, fsys afero.Fs, w io.Writer, options ...func(*pgx.ConnConfig)) error {
conn, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{}, options...)
if err != nil {
return err
Expand All @@ -319,7 +319,7 @@ func setupDatabase(ctx context.Context, fsys afero.Fs, w io.Writer, options ...f
if err := SetupDatabase(ctx, conn, utils.DbId, w, fsys); err != nil {
return err
}
return apply.MigrateAndSeed(ctx, "", conn, fsys)
return apply.MigrateAndSeed(ctx, version, conn, fsys)
}

func SetupDatabase(ctx context.Context, conn *pgx.Conn, host string, w io.Writer, fsys afero.Fs) error {
Expand Down
18 changes: 6 additions & 12 deletions internal/db/start/start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"io"
"net/http"
"os"
"path/filepath"
"testing"

"github.com/docker/docker/api/types"
Expand Down Expand Up @@ -52,8 +51,6 @@ func TestInitBranch(t *testing.T) {

func TestStartDatabase(t *testing.T) {
t.Run("initialize main branch", func(t *testing.T) {
seedPath := filepath.Join(utils.SupabaseDirPath, "seed.sql")
utils.Config.Db.Seed.SqlPaths = []string{seedPath}
utils.Config.Db.MajorVersion = 15
utils.DbId = "supabase_db_test"
utils.ConfigId = "supabase_config_test"
Expand All @@ -62,8 +59,6 @@ func TestStartDatabase(t *testing.T) {
fsys := afero.NewMemMapFs()
roles := "create role test"
require.NoError(t, afero.WriteFile(fsys, utils.CustomRolesPath, []byte(roles), 0644))
seed := "INSERT INTO employees(name) VALUES ('Alice')"
require.NoError(t, afero.WriteFile(fsys, seedPath, []byte(seed), 0644))
// Setup mock docker
require.NoError(t, apitest.MockDocker(utils.Docker))
defer gock.OffAll()
Expand Down Expand Up @@ -91,9 +86,7 @@ func TestStartDatabase(t *testing.T) {
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(roles).
Reply("CREATE ROLE").
Query(seed).
Reply("INSERT 0 1")
Reply("CREATE ROLE")
// Run test
err := StartDatabase(context.Background(), fsys, io.Discard, conn.Intercept)
// Check error
Expand Down Expand Up @@ -259,7 +252,7 @@ func TestSetupDatabase(t *testing.T) {
Query(roles).
Reply("CREATE ROLE")
// Run test
err := setupDatabase(context.Background(), fsys, io.Discard, conn.Intercept)
err := SetupLocalDatabase(context.Background(), "", fsys, io.Discard, conn.Intercept)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
Expand All @@ -268,12 +261,13 @@ func TestSetupDatabase(t *testing.T) {
t.Run("throws error on connect failure", func(t *testing.T) {
utils.Config.Db.Port = 0
// Run test
err := setupDatabase(context.Background(), nil, io.Discard)
err := SetupLocalDatabase(context.Background(), "", nil, io.Discard)
// Check error
assert.ErrorContains(t, err, "invalid port (outside range)")
})

t.Run("throws error on init failure", func(t *testing.T) {
utils.Config.Realtime.Enabled = true
utils.Config.Db.Port = 5432
// Setup mock docker
require.NoError(t, apitest.MockDocker(utils.Docker))
Expand All @@ -285,7 +279,7 @@ func TestSetupDatabase(t *testing.T) {
conn := pgtest.NewConn()
defer conn.Close(t)
// Run test
err := setupDatabase(context.Background(), nil, io.Discard, conn.Intercept)
err := SetupLocalDatabase(context.Background(), "", nil, io.Discard, conn.Intercept)
// Check error
assert.ErrorContains(t, err, "network error")
assert.Empty(t, apitest.ListUnmatchedRequests())
Expand All @@ -308,7 +302,7 @@ func TestSetupDatabase(t *testing.T) {
conn := pgtest.NewConn()
defer conn.Close(t)
// Run test
err := setupDatabase(context.Background(), fsys, io.Discard, conn.Intercept)
err := SetupLocalDatabase(context.Background(), "", fsys, io.Discard, conn.Intercept)
// Check error
assert.ErrorIs(t, err, os.ErrPermission)
assert.Empty(t, apitest.ListUnmatchedRequests())
Expand Down
3 changes: 2 additions & 1 deletion internal/link/link_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,8 @@ func TestLinkDatabase(t *testing.T) {
Query(migration.CREATE_VERSION_TABLE).
ReplyError(pgerrcode.InsufficientPrivilege, "permission denied for relation supabase_migrations").
Query(migration.ADD_STATEMENTS_COLUMN).
Query(migration.ADD_NAME_COLUMN)
Query(migration.ADD_NAME_COLUMN).
Query(migration.CREATE_SEED_TABLE)
// Run test
err := linkDatabase(context.Background(), dbConfig, conn.Intercept)
// Check error
Expand Down
14 changes: 9 additions & 5 deletions internal/migration/apply/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ func MigrateAndSeed(ctx context.Context, version string, conn *pgx.Conn, fsys af
if err := migration.ApplyMigrations(ctx, migrations, conn, afero.NewIOFS(fsys)); err != nil {
return err
}
return applySeedFiles(ctx, conn, fsys)
}

func applySeedFiles(ctx context.Context, conn *pgx.Conn, fsys afero.Fs) error {
if !utils.Config.Db.Seed.Enabled {
return nil
}
return SeedDatabase(ctx, conn, fsys)
}

func SeedDatabase(ctx context.Context, conn *pgx.Conn, fsys afero.Fs) error {
return migration.SeedData(ctx, utils.Config.Db.Seed.SqlPaths, conn, afero.NewIOFS(fsys))
seeds, err := migration.GetPendingSeeds(ctx, utils.Config.Db.Seed.SqlPaths, conn, afero.NewIOFS(fsys))
if err != nil {
return err
}
return migration.SeedData(ctx, seeds, conn, afero.NewIOFS(fsys))
}

func CreateCustomRoles(ctx context.Context, conn *pgx.Conn, fsys afero.Fs) error {
Expand Down
62 changes: 0 additions & 62 deletions internal/migration/apply/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"path/filepath"
"testing"

"github.com/jackc/pgerrcode"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -75,64 +74,3 @@ func TestMigrateDatabase(t *testing.T) {
assert.ErrorIs(t, err, os.ErrPermission)
})
}

func TestSeedDatabase(t *testing.T) {
seedPath := filepath.Join(utils.SupabaseDirPath, "seed.sql")
utils.Config.Db.Seed.SqlPaths = []string{seedPath}

t.Run("seeds from file", func(t *testing.T) {
// Setup in-memory fs
fsys := afero.NewMemMapFs()
// Setup seed file
sql := "INSERT INTO employees(name) VALUES ('Alice')"
require.NoError(t, afero.WriteFile(fsys, seedPath, []byte(sql), 0644))
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(sql).
Reply("INSERT 0 1")
// Run test
err := SeedDatabase(context.Background(), conn.MockClient(t), fsys)
// Check error
assert.NoError(t, err)
})

t.Run("ignores missing seed", func(t *testing.T) {
sqlPaths := utils.Config.Db.Seed.SqlPaths
utils.Config.Db.Seed.SqlPaths = []string{}
t.Cleanup(func() { utils.Config.Db.Seed.SqlPaths = sqlPaths })
// Setup in-memory fs
fsys := afero.NewMemMapFs()
// Run test
err := SeedDatabase(context.Background(), nil, fsys)
// Check error
assert.NoError(t, err)
})

t.Run("throws error on read failure", func(t *testing.T) {
// Setup in-memory fs
fsys := &fstest.OpenErrorFs{DenyPath: seedPath}
_, _ = fsys.Create(seedPath)
// Run test
err := SeedDatabase(context.Background(), nil, fsys)
// Check error
assert.ErrorIs(t, err, os.ErrPermission)
})

t.Run("throws error on insert failure", func(t *testing.T) {
// Setup in-memory fs
fsys := afero.NewMemMapFs()
// Setup seed file
sql := "INSERT INTO employees(name) VALUES ('Alice')"
require.NoError(t, afero.WriteFile(fsys, seedPath, []byte(sql), 0644))
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(sql).
ReplyError(pgerrcode.NotNullViolation, `null value in column "age" of relation "employees"`)
// Run test
err := SeedDatabase(context.Background(), conn.MockClient(t), fsys)
// Check error
assert.ErrorContains(t, err, `ERROR: null value in column "age" of relation "employees" (SQLSTATE 23502)`)
})
}
4 changes: 3 additions & 1 deletion internal/testing/helper/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ func MockMigrationHistory(conn *pgtest.MockConn) *pgtest.MockConn {
Query(migration.ADD_STATEMENTS_COLUMN).
Reply("ALTER TABLE").
Query(migration.ADD_NAME_COLUMN).
Reply("ALTER TABLE")
Reply("ALTER TABLE").
Query(migration.CREATE_SEED_TABLE).
Reply("CREATE TABLE")
return conn
}
7 changes: 5 additions & 2 deletions pkg/migration/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ func TestApplyMigrations(t *testing.T) {
Query(CREATE_VERSION_TABLE).
ReplyError(pgerrcode.InsufficientPrivilege, "permission denied for relation supabase_migrations").
Query(ADD_STATEMENTS_COLUMN).
Query(ADD_NAME_COLUMN)
Query(ADD_NAME_COLUMN).
Query(CREATE_SEED_TABLE)
// Run test
err := ApplyMigrations(context.Background(), pending, conn.MockClient(t), fsys)
// Check error
Expand Down Expand Up @@ -175,6 +176,8 @@ func mockMigrationHistory(conn *pgtest.MockConn) *pgtest.MockConn {
Query(ADD_STATEMENTS_COLUMN).
Reply("ALTER TABLE").
Query(ADD_NAME_COLUMN).
Reply("ALTER TABLE")
Reply("ALTER TABLE").
Query(CREATE_SEED_TABLE).
Reply("CREATE TABLE")
return conn
}
Loading