Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
23 changes: 18 additions & 5 deletions apps/cli-go/internal/db/pull/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,20 @@ func run(ctx context.Context, schema []string, path string, conn *pgx.Conn, useP
config := conn.Config().Config
// 1. Assert `supabase/migrations` and `schema_migrations` are in sync.
if err := assertRemoteInSync(ctx, conn, fsys); errors.Is(err, errMissing) {
// Ignore schemas flag when working on the initial pull
if err = dumpRemoteSchema(ctx, path, config, fsys); err != nil {
return err
// pg_dump strips ownership when restored as a non-superuser, so platform
// objects (FDWs, wasm wrappers, system-owned ACLs) leak into the migration
// and later break `supabase db reset`. pg-delta speaks pg_catalog directly
// and the supabase integration filter drops these by owner, so the diff
// against an empty shadow yields a clean initial migration on its own.
if !usePgDeltaDiff {
// Ignore schemas flag when working on the initial pull
if err = dumpRemoteSchema(ctx, path, config, fsys); err != nil {
return err
}
}
// Run a second pass to pull in changes from default privileges and managed schemas
// For the legacy path this is a second pass that captures changes
// pg_dump cannot emit (default privileges, managed schemas). For the
// pg-delta path this is the only pass and produces the full schema.
if err = diffRemoteSchema(ctx, nil, path, config, usePgDeltaDiff, differ, fsys); errors.Is(err, errInSync) {
err = nil
}
Expand Down Expand Up @@ -153,7 +162,11 @@ func diffRemoteSchema(ctx context.Context, schema []string, path string, config
if trimmed := strings.TrimSpace(output); len(trimmed) == 0 {
return errors.New(errInSync)
}
// Append to existing migration file since we run this after dump
if err := utils.MkdirIfNotExistFS(fsys, filepath.Dir(path)); err != nil {
return err
}
// Append to existing migration file when we run this after dumpRemoteSchema;
// for the pg-delta path this is the only writer and creates the file fresh.
f, err := fsys.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return errors.Errorf("failed to open migration file: %w", err)
Expand Down
28 changes: 28 additions & 0 deletions apps/cli-go/internal/db/pull/pull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,34 @@ func TestPullSchema(t *testing.T) {
assert.Equal(t, []byte("test"), contents)
})

t.Run("skips pg_dump for pg-delta diff engine on initial pull", func(t *testing.T) {
errNetwork := errors.New("network error")
// Setup in-memory fs
fsys := afero.NewMemMapFs()
// Setup mock docker. Only mock the image inspect call that
// CreateShadowDatabase makes; do NOT mock the pg_dump container so
// the test fails loudly if pg_dump is still invoked.
require.NoError(t, apitest.MockDocker(utils.Docker))
defer gock.OffAll()
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/images/" + utils.GetRegistryImageUrl(utils.Config.Db.Image) + "/json").
ReplyError(errNetwork)
// Setup mock postgres (no local migrations -> initial pull path)
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(migration.LIST_MIGRATION_VERSION).
Reply("SELECT 0")
// Run test with usePgDeltaDiff=true
err := run(context.Background(), nil, "0_test.sql", conn.MockClient(t), true, diff.DiffPgDelta, fsys)
// Failure must come from shadow-creation image inspect (proving we
// reached the diff step), not from pg_dump.
assert.ErrorIs(t, err, errNetwork)
assert.Empty(t, apitest.ListUnmatchedRequests())
exists, err := afero.Exists(fsys, "0_test.sql")
assert.NoError(t, err)
assert.False(t, exists, "pg_dump should be skipped for pg-delta diff engine")
})

t.Run("throws error on diff failure", func(t *testing.T) {
// Setup in-memory fs
fsys := afero.NewMemMapFs()
Expand Down
Loading