Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ const scenarios: ReadonlyArray<ParityScenario> = [
overrides: { db: { major_version: 0 } },
message: "Missing required field in config: db.major_version",
},
{
name: "db.major_version unsupported (13)",
toml: ["[db]", "major_version = 13"],
overrides: { db: { major_version: 13 } },
message:
"Postgres version 13.x is unsupported. To use the CLI, upgrade your project to Postgres 17, then set db.major_version = 17 in your project config: https://supabase.com/docs/guides/platform/upgrading.",
},
{
name: "db.major_version unsupported (16)",
toml: ["[db]", "major_version = 16"],
Expand Down
7 changes: 6 additions & 1 deletion apps/cli/src/command-internal/config-validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,12 @@ export function validateResolvedConfig(input: ConfigValidationInput): void {
"Postgres version 12.x is unsupported. To use the CLI, either start a new project or follow project migration steps here: https://supabase.com/docs/guides/database#migrating-between-projects.",
);
}
if (![13, 14, 15, 17].includes(input.db.majorVersion)) {
if (input.db.majorVersion === 13) {
throw new ConfigValidateError(
"Postgres version 13.x is unsupported. To use the CLI, upgrade your project to Postgres 17, then set db.major_version = 17 in your project config: https://supabase.com/docs/guides/platform/upgrading.",
);
}
Comment thread
jgoux marked this conversation as resolved.
Resolved
Comment thread
jgoux marked this conversation as resolved.
Resolved
if (![14, 15, 17].includes(input.db.majorVersion)) {
throw new ConfigValidateError(
`Failed reading config: Invalid db.major_version: ${input.db.majorVersion}.`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,13 @@ describe("validateResolvedConfig", () => {
).toThrow("Postgres version 12.x is unsupported.");
});

it.each([13, 14, 15, 17])("accepts the supported major_version %d", (majorVersion) => {
it("rejects the unsupported Postgres 13.x major_version with its dedicated message", () => {
expect(() =>
validateResolvedConfig(minimalInput({ db: { port: 5432, majorVersion: 13 } })),
).toThrow("Postgres version 13.x is unsupported.");
});

it.each([14, 15, 17])("accepts the supported major_version %d", (majorVersion) => {
expect(() =>
validateResolvedConfig(minimalInput({ db: { port: 5432, majorVersion } })),
).not.toThrow();
Expand Down
22 changes: 12 additions & 10 deletions apps/cli/src/command-internal/db-bootstrap/db-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import { resolvePinnedImage } from "./pinned-image.ts";
import { COMPOSE_PROJECT_LABEL } from "./container-lifecycle.ts";
import { REALTIME_TENANT_ID, buildRealtimeEnv } from "./realtime-env.ts";
import { START_DB_GLOBALS_SQL } from "./templates/db-globals.sql.ts";
import { START_DB_INITIAL_SCHEMA_13_SQL } from "./templates/db-initial-schema-13.sql.ts";
import { START_DB_INITIAL_SCHEMA_14_SQL } from "./templates/db-initial-schema-14.sql.ts";
import { STACK_DB_WEBHOOK_SQL } from "./templates/db-webhook.sql.ts";
import { startInternalDbPassword, startInternalDbUrl } from "./internal-db-connection.ts";
Expand Down Expand Up @@ -169,7 +168,7 @@ function resolveDbSetupImages(
*
* The banner prints before `jwks` resolves, so a JWKS failure (an unreachable `third_party`
* provider) surfaces after the banner instead of before it. The `majorVersion >= 15` gate means
* JWKS's potential live HTTP request never runs at all on PG13/14, even with realtime enabled.
* JWKS's potential live HTTP request never runs at all on PG14, even with realtime enabled.
*/
export const resolveDbSetupPrelude = <E>(setup: {
readonly majorVersion: number;
Expand Down Expand Up @@ -207,7 +206,7 @@ export interface SetupDatabaseInput {
readonly config: CliConfig;
/** Effective `[experimental.webhooks].enabled`, including supported environment overrides. */
readonly webhooksEnabled: boolean;
/** `db.major_version` (13-17), resolved by the caller once, ahead of the `db` container's own image tag selection. */
/** `db.major_version` (14-17), resolved by the caller once, ahead of the `db` container's own image tag selection. */
readonly majorVersion: number;
/**
* The internal Docker-network address the PG15+ one-shot jobs connect through. The real local
Expand Down Expand Up @@ -338,11 +337,15 @@ export const initSchema14 = Effect.fnUntraced(function* (
fs: FileSystem.FileSystem,
path: Path.Path,
tmpDir: string,
majorVersion: number,
) {
const schemaSql =
majorVersion === 13 ? START_DB_INITIAL_SCHEMA_13_SQL : START_DB_INITIAL_SCHEMA_14_SQL;
yield* execSqlConstant(session, fs, path, tmpDir, "initial-schema.sql", schemaSql);
yield* execSqlConstant(
session,
fs,
path,
tmpDir,
"initial-schema.sql",
START_DB_INITIAL_SCHEMA_14_SQL,
);
});

/**
Expand All @@ -355,10 +358,9 @@ const startInitSchemaPre15 = Effect.fnUntraced(function* (
fs: FileSystem.FileSystem,
path: Path.Path,
tmpDir: string,
majorVersion: number,
) {
yield* execSqlConstant(session, fs, path, tmpDir, "globals.sql", START_DB_GLOBALS_SQL);
yield* initSchema14(session, fs, path, tmpDir, majorVersion);
yield* initSchema14(session, fs, path, tmpDir);
});

/**
Expand Down Expand Up @@ -581,7 +583,7 @@ const startInitSchema = Effect.fn("DbSetup.initSchema")(function* (
) {
yield* Effect.annotateCurrentSpan("db.major_version", input.majorVersion);
if (input.majorVersion <= 14) {
yield* startInitSchemaPre15(input.session, input.fs, input.path, tmpDir, input.majorVersion);
yield* startInitSchemaPre15(input.session, input.fs, input.path, tmpDir);
return;
}
yield* startInitSchema15(spawner, input);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,11 @@ const decodeConfig = Schema.decodeUnknownSync(CliConfigSchema);
* Fingerprints unique to each transcribed SQL constant, with no trailing `;` (`splitAndTrim`
* strips it before `session.exec` sees it).
*
* `GLOBALS`/`SCHEMA_13`/`REVOKE_PRIVILEGES` are checked as substrings, since each is embedded in
* `GLOBALS`/`REVOKE_PRIVILEGES` are checked as substrings, since each is embedded in
* a larger executed block. `SCHEMA_14` is checked with `.endsWith` instead: a substring check
* would also match 14.sql's unrelated `graphql_public` schema statement.
*/
const GLOBALS_FINGERPRINT = "CREATE ROLE anon";
const SCHEMA_13_FINGERPRINT =
"CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION";
const SCHEMA_14_FINGERPRINT_SUFFIX = "CREATE SCHEMA IF NOT EXISTS graphql";
const REVOKE_PRIVILEGES_FINGERPRINT =
"revoke execute on functions from anon, authenticated, service_role";
Expand Down Expand Up @@ -249,24 +247,6 @@ describe("startSetupLocalDatabase", () => {
const execSql = calls.filter((c) => c.kind === "exec").map((c) => c.sql);
expect(execSql.some((sql) => sql.includes(GLOBALS_FINGERPRINT))).toBe(true);
expect(execSql.some((sql) => sql.endsWith(SCHEMA_14_FINGERPRINT_SUFFIX))).toBe(true);
expect(execSql.some((sql) => sql.includes(SCHEMA_13_FINGERPRINT))).toBe(false);
expect(docker.runs.length).toBe(0);
yield* fs.remove(workdir, { recursive: true, force: true });
}).pipe(Effect.provide(BunServices.layer));
});

it.effect("PG13: execs globals + the PG13 initial schema, not the PG14 one", () => {
return Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const workdir = yield* makeWorkdir();
const { session, calls } = fakeSession();
const out = mockOutput();
const docker = mockDockerRun();
yield* run(baseInput(workdir, session, { majorVersion: 13 }), out, docker);
const execSql = calls.filter((c) => c.kind === "exec").map((c) => c.sql);
expect(execSql.some((sql) => sql.includes(GLOBALS_FINGERPRINT))).toBe(true);
expect(execSql.some((sql) => sql.includes(SCHEMA_13_FINGERPRINT))).toBe(true);
expect(execSql.some((sql) => sql.endsWith(SCHEMA_14_FINGERPRINT_SUFFIX))).toBe(false);
expect(docker.runs.length).toBe(0);
yield* fs.remove(workdir, { recursive: true, force: true });
}).pipe(Effect.provide(BunServices.layer));
Expand All @@ -282,7 +262,6 @@ describe("startSetupLocalDatabase", () => {
yield* run(baseInput(workdir, session, { majorVersion: 17 }), out, docker);
const execSql = calls.filter((c) => c.kind === "exec").map((c) => c.sql);
expect(execSql.some((sql) => sql.includes(GLOBALS_FINGERPRINT))).toBe(false);
expect(execSql.some((sql) => sql.includes(SCHEMA_13_FINGERPRINT))).toBe(false);
expect(execSql.some((sql) => sql.endsWith(SCHEMA_14_FINGERPRINT_SUFFIX))).toBe(false);
// Default config: realtime, storage, and auth are all enabled.
expect(docker.runs.length).toBe(3);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ const recreateLocalDatabase14 = <E>(
}),
),
);
yield* initSchema14(session, fs, path, tmpDir, setup.majorVersion);
yield* initSchema14(session, fs, path, tmpDir);
// Same drop-then-conditionally-recreate sequence fresh setup runs
// (`db-setup.ts`'s `requiresPg14WebhooksCleanup`): the PG14 dump installs pg_net
// unconditionally, so without this drop a reset with webhooks disabled would leave
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { buildLocalDbContainerInputs } from "./local-container-inputs.ts";
import { removeContainer } from "./container-lifecycle.ts";
import { awaitDockerHealthSettled } from "./health-check.ts";
import { localDbState } from "./local-db-running.ts";
import { warnIfPostgres14Deprecated } from "../db-image.ts";
import { rollbackDatabaseStart } from "./rollback.ts";
import { backupVolumeExistsError, startDatabase } from "./start-database.ts";

Expand Down Expand Up @@ -119,6 +120,7 @@ const startLocalDatabaseInScope = Effect.fn("DbBootstrap.startLocalDatabase")(fu
) {
yield* output.raw("WARN: no SMS provider is enabled. Disabling phone login\n", "stderr");
}
yield* warnIfPostgres14Deprecated(config.db.major_version);
const { timebox, inactivity_timeout: inactivityTimeout } = config.auth.sessions ?? {};
if (timebox !== undefined) {
yield* wrapDbConfigParse("auth.sessions.timebox", () => parseDuration(timebox));
Expand Down
Loading
Loading