diff --git a/cmd/root.go b/cmd/root.go index 54d88b0b85..a2cc6f0a7f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -200,6 +200,9 @@ func recoverAndExit() { !viper.GetBool("DEBUG") { utils.CmdSuggestion = utils.SuggestDebugFlag } + if e, ok := err.(*errors.Error); ok && len(utils.Version) == 0 { + fmt.Fprintln(os.Stderr, string(e.Stack())) + } msg = err.Error() default: msg = fmt.Sprintf("%#v", err) diff --git a/internal/db/diff/diff.go b/internal/db/diff/diff.go index 646ee005da..01ce857ee5 100644 --- a/internal/db/diff/diff.go +++ b/internal/db/diff/diff.go @@ -31,22 +31,6 @@ import ( type DiffFunc func(context.Context, string, string, []string) (string, error) func Run(ctx context.Context, schema []string, file string, config pgconn.Config, differ DiffFunc, fsys afero.Fs, options ...func(*pgx.ConnConfig)) (err error) { - // Sanity checks. - if utils.IsLocalDatabase(config) { - if declared, err := loadDeclaredSchemas(fsys); err != nil { - return err - } else if container, err := createShadowIfNotExists(ctx, declared); err != nil { - return err - } else if len(container) > 0 { - defer utils.DockerRemove(container) - if err := start.WaitForHealthyService(ctx, start.HealthTimeout, container); err != nil { - return err - } - if err := migrateBaseDatabase(ctx, container, declared, fsys, options...); err != nil { - return err - } - } - } // 1. Load all user defined schemas if len(schema) == 0 { schema, err = loadSchema(ctx, config, options...) @@ -72,22 +56,6 @@ func Run(ctx context.Context, schema []string, file string, config pgconn.Config return nil } -func createShadowIfNotExists(ctx context.Context, migrations []string) (string, error) { - if len(migrations) == 0 { - return "", nil - } - if err := utils.AssertSupabaseDbIsRunning(); !errors.Is(err, utils.ErrNotRunning) { - return "", err - } - fmt.Fprintln(os.Stderr, "Creating local database from declarative schemas:") - msg := make([]string, len(migrations)) - for i, m := range migrations { - msg[i] = fmt.Sprintf(" • %s", utils.Bold(m)) - } - fmt.Fprintln(os.Stderr, strings.Join(msg, "\n")) - return CreateShadowDatabase(ctx, utils.Config.Db.Port) -} - func loadDeclaredSchemas(fsys afero.Fs) ([]string, error) { if schemas := utils.Config.Db.Migrations.SchemaPaths; len(schemas) > 0 { return schemas.Files(afero.NewIOFS(fsys)) @@ -140,7 +108,8 @@ func loadSchema(ctx context.Context, config pgconn.Config, options ...func(*pgx. } func CreateShadowDatabase(ctx context.Context, port uint16) (string, error) { - config := start.NewContainerConfig() + // Disable background workers in shadow database + config := start.NewContainerConfig("-c", "max_worker_processes=0") hostPort := strconv.FormatUint(uint64(port), 10) hostConfig := container.HostConfig{ PortBindings: nat.PortMap{"5432/tcp": []nat.PortBinding{{HostPort: hostPort}}}, @@ -148,7 +117,6 @@ func CreateShadowDatabase(ctx context.Context, port uint16) (string, error) { } networkingConfig := network.NetworkingConfig{} if utils.Config.Db.MajorVersion <= 14 { - config.Entrypoint = nil hostConfig.Tmpfs = map[string]string{"/docker-entrypoint-initdb.d": ""} } return utils.DockerStart(ctx, config, hostConfig, networkingConfig, "") @@ -164,6 +132,9 @@ func ConnectShadowDatabase(ctx context.Context, timeout time.Duration, options . return backoff.RetryWithData(connect, policy) } +// Required to bypass pg_cron check: https://github.com/citusdata/pg_cron/blob/main/pg_cron.sql#L3 +const CREATE_TEMPLATE = "CREATE DATABASE contrib_regression TEMPLATE postgres" + func MigrateShadowDatabase(ctx context.Context, container string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { migrations, err := migration.ListLocalMigrations(utils.MigrationsDir, afero.NewIOFS(fsys)) if err != nil { @@ -177,19 +148,10 @@ func MigrateShadowDatabase(ctx context.Context, container string, fsys afero.Fs, if err := start.SetupDatabase(ctx, conn, container[:12], os.Stderr, fsys); err != nil { return err } - return migration.ApplyMigrations(ctx, migrations, conn, afero.NewIOFS(fsys)) -} - -func migrateBaseDatabase(ctx context.Context, container string, migrations []string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { - conn, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{}, options...) - if err != nil { - return err - } - defer conn.Close(context.Background()) - if err := start.SetupDatabase(ctx, conn, container[:12], os.Stderr, fsys); err != nil { - return err + if _, err := conn.Exec(ctx, CREATE_TEMPLATE); err != nil { + return errors.Errorf("failed to create template database: %w", err) } - return migration.SeedGlobals(ctx, migrations, conn, afero.NewIOFS(fsys)) + return migration.ApplyMigrations(ctx, migrations, conn, afero.NewIOFS(fsys)) } func DiffDatabase(ctx context.Context, schema []string, config pgconn.Config, w io.Writer, fsys afero.Fs, differ func(context.Context, string, string, []string) (string, error), options ...func(*pgx.ConnConfig)) (string, error) { @@ -205,14 +167,41 @@ func DiffDatabase(ctx context.Context, schema []string, config pgconn.Config, w if err := MigrateShadowDatabase(ctx, shadow, fsys, options...); err != nil { return "", err } - fmt.Fprintln(w, "Diffing schemas:", strings.Join(schema, ",")) - source := utils.ToPostgresURL(pgconn.Config{ + shadowConfig := pgconn.Config{ Host: utils.Config.Hostname, Port: utils.Config.Db.ShadowPort, User: "postgres", Password: utils.Config.Db.Password, Database: "postgres", - }) + } + if utils.IsLocalDatabase(config) { + if declared, err := loadDeclaredSchemas(fsys); err != nil { + return "", err + } else if len(declared) > 0 { + config = shadowConfig + config.Database = "contrib_regression" + if err := migrateBaseDatabase(ctx, config, declared, fsys, options...); err != nil { + return "", err + } + } + } + fmt.Fprintln(w, "Diffing schemas:", strings.Join(schema, ",")) + source := utils.ToPostgresURL(shadowConfig) target := utils.ToPostgresURL(config) return differ(ctx, source, target, schema) } + +func migrateBaseDatabase(ctx context.Context, config pgconn.Config, migrations []string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { + fmt.Fprintln(os.Stderr, "Creating local database from declarative schemas:") + msg := make([]string, len(migrations)) + for i, m := range migrations { + msg[i] = fmt.Sprintf(" • %s", utils.Bold(m)) + } + fmt.Fprintln(os.Stderr, strings.Join(msg, "\n")) + conn, err := utils.ConnectLocalPostgres(ctx, config, options...) + if err != nil { + return err + } + defer conn.Close(context.Background()) + return migration.SeedGlobals(ctx, migrations, conn, afero.NewIOFS(fsys)) +} diff --git a/internal/db/diff/diff_test.go b/internal/db/diff/diff_test.go index 498c88da1b..daadaf6bbf 100644 --- a/internal/db/diff/diff_test.go +++ b/internal/db/diff/diff_test.go @@ -69,6 +69,8 @@ func TestRun(t *testing.T) { require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-migra", diff)) // Setup mock postgres conn := pgtest.NewConn() + conn.Query(CREATE_TEMPLATE). + Reply("CREATE DATABASE") defer conn.Close(t) // Run test err := Run(context.Background(), []string{"public"}, "file", dbConfig, DiffSchemaMigra, fsys, conn.Intercept) @@ -134,7 +136,9 @@ func TestMigrateShadow(t *testing.T) { conn.Query(utils.GlobalsSql). Reply("CREATE SCHEMA"). Query(utils.InitialSchemaPg14Sql). - Reply("CREATE SCHEMA") + Reply("CREATE SCHEMA"). + Query(CREATE_TEMPLATE). + Reply("CREATE DATABASE") helper.MockMigrationHistory(conn). Query(sql). Reply("CREATE SCHEMA"). @@ -308,7 +312,9 @@ create schema public`) conn.Query(utils.GlobalsSql). Reply("CREATE SCHEMA"). Query(utils.InitialSchemaPg14Sql). - Reply("CREATE SCHEMA") + Reply("CREATE SCHEMA"). + Query(CREATE_TEMPLATE). + Reply("CREATE DATABASE") helper.MockMigrationHistory(conn). Query(sql). Reply("CREATE SCHEMA"). diff --git a/internal/db/start/start.go b/internal/db/start/start.go index ff4b237f00..665c5f4f77 100644 --- a/internal/db/start/start.go +++ b/internal/db/start/start.go @@ -60,7 +60,11 @@ func Run(ctx context.Context, fromBackup string, fsys afero.Fs) error { return err } -func NewContainerConfig() container.Config { +func NewContainerConfig(args ...string) container.Config { + if utils.Config.Db.MajorVersion >= 14 { + // Extensions schema does not exist on PG13 and below + args = append(args, "-c", "search_path='$user,public,extensions'") + } env := []string{ "POSTGRES_PASSWORD=" + utils.Config.Db.Password, "POSTGRES_HOST=/var/run/postgresql", @@ -92,7 +96,7 @@ func NewContainerConfig() container.Config { cat <<'EOF' > /etc/postgresql.schema.sql && \ cat <<'EOF' > /etc/postgresql-custom/pgsodium_root.key && \ cat <<'EOF' >> /etc/postgresql/postgresql.conf && \ -docker-entrypoint.sh postgres -D /etc/postgresql +docker-entrypoint.sh postgres -D /etc/postgresql ` + strings.Join(args, " ") + ` ` + initialSchema + ` ` + webhookSchema + ` ` + _supabaseSchema + ` @@ -102,12 +106,15 @@ EOF ` + utils.Config.Db.Settings.ToPostgresConfig() + ` EOF`}, } - if utils.Config.Db.MajorVersion >= 14 { - config.Cmd = []string{"postgres", - "-c", "config_file=/etc/postgresql/postgresql.conf", - // Ref: https://postgrespro.com/list/thread-id/2448092 - "-c", `search_path="$user",public,extensions`, - } + if utils.Config.Db.MajorVersion <= 14 { + config.Entrypoint = []string{"sh", "-c", ` +cat <<'EOF' > /docker-entrypoint-initdb.d/supabase_schema.sql && \ +cat <<'EOF' >> /etc/postgresql/postgresql.conf && \ +docker-entrypoint.sh postgres -D /etc/postgresql ` + strings.Join(args, " ") + ` +` + _supabaseSchema + ` +EOF +` + utils.Config.Db.Settings.ToPostgresConfig() + ` +EOF`} } return config } @@ -122,6 +129,9 @@ func NewHostConfig() container.HostConfig { utils.ConfigId + ":/etc/postgresql-custom", }, } + if utils.Config.Db.MajorVersion <= 14 { + hostConfig.Tmpfs = map[string]string{"/docker-entrypoint-initdb.d": ""} + } return hostConfig } @@ -135,17 +145,6 @@ func StartDatabase(ctx context.Context, fromBackup string, fsys afero.Fs, w io.W }, }, } - if utils.Config.Db.MajorVersion <= 14 { - config.Entrypoint = []string{"sh", "-c", ` -cat <<'EOF' > /docker-entrypoint-initdb.d/supabase_schema.sql && \ -cat <<'EOF' >> /etc/postgresql/postgresql.conf && \ -docker-entrypoint.sh postgres -D /etc/postgresql -` + _supabaseSchema + ` -EOF -` + utils.Config.Db.Settings.ToPostgresConfig() + ` -EOF`} - hostConfig.Tmpfs = map[string]string{"/docker-entrypoint-initdb.d": ""} - } if len(fromBackup) > 0 { config.Entrypoint = []string{"sh", "-c", ` cat <<'EOF' > /etc/postgresql.schema.sql && \