/** * Pure helpers for the backup/restore commands. * * Everything in this file is side-effect free so it can be unit-tested * without a live Postgres server, matching the constraint that CI must * not require a database. */ /** * A parsed backup destination. `--out` (and the scheduled-backup config) * accepts either a local filesystem path or an object-storage URL. */ export type BackupDestination = { kind: "local"; path: string; } | { kind: "s3"; bucket: string; prefix: string; } | { kind: "gcs"; bucket: string; prefix: string; }; /** * Extract the database name from a Postgres connection string. * Returns `null` when the URL has no database path (e.g. bare host). */ export declare function parseDbNameFromUrl(connectionString: string): string | null; /** * Swap the database name in a connection string, preserving credentials, * host, port and query params. Used by `--target-db` / `--create-db` so a * restore can target a fresh database instead of clobbering the live one. */ export declare function withDatabaseName(connectionString: string, dbName: string): string; /** * Parse the major version out of `pg_dump --version` / `pg_restore --version` * output, e.g. `"pg_dump (PostgreSQL) 16.2"` → `16`. Handles the pre-10 * `9.6.x` scheme (returns `9`) and beta strings like `"17beta1"`. */ export declare function parsePgToolMajor(versionOutput: string): number | null; /** * Convert `SELECT current_setting('server_version_num')` (e.g. `160002`) * into a major version (`16`). Also accepts pre-10 encodings like `90603` * → `9`. */ export declare function serverVersionNumToMajor(versionNum: number | string): number | null; export interface VersionCompatibility { compatible: boolean; reason?: string; } /** * pg_dump / pg_restore must be **at least** as new as the server they talk * to. A newer client against an older server is supported; an older client * against a newer server is not and produces corrupt or rejected output. */ export declare function checkToolServerCompatibility(toolMajor: number | null, serverMajor: number | null): VersionCompatibility; /** * Build a deterministic, sortable backup file name: * `rebase--TZ.dump` * * The UTC timestamp is embedded so retention pruning can recover the * creation time from the object key alone, without extra metadata. */ export declare function buildBackupFilename(dbName: string, date?: Date): string; /** * Recover the creation timestamp encoded in a backup file name by * {@link buildBackupFilename}. Returns `null` for names that don't match, * so foreign objects in a shared prefix are never pruned. */ export declare function parseBackupTimestamp(fileName: string): Date | null; /** * Parse a destination string into a structured {@link BackupDestination}. * `s3://bucket/prefix` and `gs://bucket/prefix` map to object storage; * anything else is treated as a local path. */ export declare function parseBackupDestination(out: string): BackupDestination; /** * Join a storage prefix and a file name without producing a leading or * doubled slash. */ export declare function joinStorageKey(prefix: string, fileName: string): string; /** * The identity `pg_dump` reads rows as, when row security is left on. * * Not optional, and that is the whole design. `pg_dump --enable-row-security` * on its own is the dangerous command in this file: it turns the "query would * be affected by row-level security policy" *error* into a dump that exits 0 * and is silently missing every row the dumping role's policies exclude. A * backup that looks fine and restores most of your data is worse than one that * refused to run. * * So the flag is unreachable without a subject to evaluate the policies * against. Rebase's generated policies read `app.uid` and `app.user_roles`; * supplying an admin role satisfies the `admin_full_access` rule and the dump * sees everything that rule sees. */ export interface RowSecurityIdentity { /** Written to `app.uid`. Any non-empty value — it is only an audit trail. */ uid: string; /** Written to `app.user_roles`. Must include a role the policies admit. */ roles: string[]; } /** * `PGOPTIONS` carrying an identity, for a libpq tool that has no other way to * set a GUC. * * A backslash escape rather than quoting, which is what libpq's `-c` parsing * takes: a space inside a value ends the option otherwise, so a role list is * comma-joined and never spaced. */ export declare function buildRowSecurityPgOptions(identity: RowSecurityIdentity): string; /** * Assemble the `pg_dump` argument vector. Uses the custom format (`-Fc`), * which is compressed and restorable selectively via `pg_restore`. */ export declare function buildPgDumpArgs(opts: { connectionString: string; outFile: string; /** Extra schemas/tables to exclude, e.g. Atlas revision tables. */ excludeSchemas?: string[]; /** Number of parallel jobs (directory format only; ignored for -Fc). */ noOwner?: boolean; /** * Dump with row security on, as this identity. Omit — which is the default * — and `pg_dump` errors rather than skipping rows it cannot see. */ rowSecurity?: RowSecurityIdentity; }): string[]; /** * Whether a `pg_dump` failure is the row-security one, and what to do about it. * * The error text names the table and nothing else, so the first read of it is * "why would a backup be affected by RLS at all?" — the answer being that the * dumping role is not the tables' owner and has no `BYPASSRLS`, which is the * normal state of the `postgres` user on Cloud SQL, RDS and every other managed * Postgres. Nothing about that is visible from the message. * * Returns `null` for any other failure, so the caller reports it unchanged. */ export declare function diagnoseRowSecurityDumpFailure(error: unknown): string | null; /** * Assemble the `pg_restore` argument vector for a custom-format dump. */ export declare function buildPgRestoreArgs(opts: { connectionString: string; inputFile: string; /** Drop objects before recreating them (destructive but idempotent). */ clean?: boolean; /** * Abort on the first error instead of logging and continuing. Defaults * ON: a restore that silently skips failed GRANT/RLS statements (because * a role is missing) "succeeds" with RLS un-enforced — a security hole. * Fail loudly instead so the operator knows the restore is incomplete. */ exitOnError?: boolean; noOwner?: boolean; }): string[]; /** * Assemble the `pg_restore --list` argument vector. Reading a dump's table * of contents parses the whole archive without touching a database, so it is * a cheap integrity check that the file isn't truncated or corrupt. */ export declare function buildPgRestoreListArgs(inputFile: string): string[]; /** * Assemble the `pg_dumpall --globals-only` argument vector. Roles (and other * cluster-wide objects) live outside any single database, so a per-database * `pg_dump` omits them. Without the `rebase_user` role the RLS GRANT * statements in the main dump fail on restore and RLS is silently lost — so * every backup captures the globals into a sidecar `.globals.sql`. * * `--no-role-passwords` keeps role secrets out of the artifact (backups may * be shipped off-box); roles are recreated password-less and re-secured by * the operator. */ export declare function buildPgDumpallGlobalsArgs(opts: { connectionString: string; outFile: string; }): string[]; /** * Derive the globals sidecar path/key for a given `.dump` file. Keeps the * two artifacts adjacent so listing, uploading and pruning can find one from * the other. A name that doesn't end in `.dump` is returned unchanged with a * `.globals.sql` suffix appended. */ export declare function globalsFileForDump(dumpPath: string): string; /** * Split a `pg_dumpall --globals-only` script into individual statements. * Used when replaying globals on restore so each `CREATE ROLE` / `GRANT` * can run independently and a benign "role already exists" on one doesn't * abort the rest. Drops `--` comment lines and blank statements. */ export declare function splitGlobalsStatements(sql: string): string[]; /** * Resolve the Postgres connection string the backup commands should use, * mirroring the precedence the branch command already relies on. */ export declare function resolveConnectionString(env: Record): string | null;