@rebasepro/server-postgres 0.24.1-canary.gcb918fc → 0.25.0-canary.g70dad01
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BranchService-Bsn2wfaj.js → BranchService-DO_3_CXt.js} +118 -18
- package/dist/BranchService-DO_3_CXt.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +13 -0
- package/dist/PostgresBootstrapper.d.ts +10 -0
- package/dist/backup/backup-cli.d.ts +120 -2
- package/dist/backup/backup-service.d.ts +74 -1
- package/dist/backup/pg-binary.d.ts +2 -2
- package/dist/backup/pg-tools.d.ts +65 -0
- package/dist/backup-cli-Ap2WX2uW.js +647 -0
- package/dist/backup-cli-Ap2WX2uW.js.map +1 -0
- package/dist/{backup-service-BTBB7RXn.js → backup-service-DytMimsL.js} +414 -24
- package/dist/backup-service-DytMimsL.js.map +1 -0
- package/dist/{boot-ddl-DVpsFN6r.js → boot-ddl-D1lMCZLd.js} +3 -3
- package/dist/{boot-ddl-DVpsFN6r.js.map → boot-ddl-D1lMCZLd.js.map} +1 -1
- package/dist/branch-argv.d.ts +18 -6
- package/dist/branch-prune.d.ts +18 -0
- package/dist/cli-console-CQGsBzSY.js +262 -0
- package/dist/cli-console-CQGsBzSY.js.map +1 -0
- package/dist/cli-console.d.ts +11 -0
- package/dist/{cli-errors-CaI4ntyD.js → cli-errors-CJTKAKax.js} +499 -107
- package/dist/cli-errors-CJTKAKax.js.map +1 -0
- package/dist/cli-errors.d.ts +42 -5
- package/dist/{cli-flags-JNI-pwSf.js → cli-flags-D-b0qL9T.js} +71 -6
- package/dist/cli-flags-D-b0qL9T.js.map +1 -0
- package/dist/cli-flags.d.ts +21 -0
- package/dist/cli-helpers.d.ts +53 -24
- package/dist/{arg-Dni7MzLB.js → cli-prompt-egtNKkZl.js} +49 -3
- package/dist/cli-prompt-egtNKkZl.js.map +1 -0
- package/dist/cli-prompt.d.ts +19 -0
- package/dist/cli-scratch-database.d.ts +47 -8
- package/dist/cli.js +615 -100
- package/dist/cli.js.map +1 -1
- package/dist/collections/validate-relations.d.ts +8 -2
- package/dist/{column-plan-helpers-drZIsSNf.js → column-plan-helpers-D_V4oaZh.js} +2 -2
- package/dist/{column-plan-helpers-drZIsSNf.js.map → column-plan-helpers-D_V4oaZh.js.map} +1 -1
- package/dist/{connection-C9mT8bgf.js → connection-C938nR3E.js} +2 -2
- package/dist/{connection-C9mT8bgf.js.map → connection-C938nR3E.js.map} +1 -1
- package/dist/{databasePoolManager-BIuewidb.js → databasePoolManager-Yqdw14wY.js} +4 -4
- package/dist/{databasePoolManager-BIuewidb.js.map → databasePoolManager-Yqdw14wY.js.map} +1 -1
- package/dist/{deferred-index-build-DP1_SNFo.js → deferred-index-build-BiTqxqdl.js} +8 -8
- package/dist/{deferred-index-build-DP1_SNFo.js.map → deferred-index-build-BiTqxqdl.js.map} +1 -1
- package/dist/{doctor-C4WiftHm.js → doctor-N3J88BN_.js} +8 -8
- package/dist/doctor-N3J88BN_.js.map +1 -0
- package/dist/{ensure-collection-policies-CXpbeNag.js → ensure-collection-policies-CVP6kIPT.js} +125 -18
- package/dist/ensure-collection-policies-CVP6kIPT.js.map +1 -0
- package/dist/{ensure-collection-tables-BhsU6qUE.js → ensure-collection-tables-1WVNALOW.js} +10 -10
- package/dist/{ensure-collection-tables-BhsU6qUE.js.map → ensure-collection-tables-1WVNALOW.js.map} +1 -1
- package/dist/{ensure-tables-CVm_yvzb.js → ensure-tables-D-Nm2k3S.js} +5 -5
- package/dist/{ensure-tables-CVm_yvzb.js.map → ensure-tables-D-Nm2k3S.js.map} +1 -1
- package/dist/{enum-labels-kept-D8fI1MoX.js → enum-labels-kept-e_93v2Ic.js} +3 -3
- package/dist/{enum-labels-kept-D8fI1MoX.js.map → enum-labels-kept-e_93v2Ic.js.map} +1 -1
- package/dist/{generate-drizzle-schema-Bz52jH2h.js → generate-drizzle-schema-C3EAP9HE.js} +3 -3
- package/dist/{generate-drizzle-schema-Bz52jH2h.js.map → generate-drizzle-schema-C3EAP9HE.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-B74WN37S.js → generate-drizzle-schema-logic-DA3zM8zc.js} +4 -4
- package/dist/{generate-drizzle-schema-logic-B74WN37S.js.map → generate-drizzle-schema-logic-DA3zM8zc.js.map} +1 -1
- package/dist/{generate-files-CdEckX6c.js → generate-files-ClmOUHXj.js} +6 -47
- package/dist/generate-files-ClmOUHXj.js.map +1 -0
- package/dist/generate-next-step-DCeS4LrJ.js +74 -0
- package/dist/generate-next-step-DCeS4LrJ.js.map +1 -0
- package/dist/{generate-postgres-ddl-logic-B64O2oZF.js → generate-postgres-ddl-logic-4eUP4t8w.js} +4 -4
- package/dist/{generate-postgres-ddl-logic-B64O2oZF.js.map → generate-postgres-ddl-logic-4eUP4t8w.js.map} +1 -1
- package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-FlxM2i4i.js} +102 -12
- package/dist/{generated-schema-staleness-Di9c4ZxN.js.map → generated-schema-staleness-FlxM2i4i.js.map} +1 -1
- package/dist/index.es.js +11 -11
- package/dist/{introspect-db-logic-C-Di8dA9.js → introspect-db-logic-CI5CU_n0.js} +145 -9
- package/dist/introspect-db-logic-CI5CU_n0.js.map +1 -0
- package/dist/{introspect-db-queries-D6kAqReI.js → introspect-db-queries-DevUrzll.js} +41 -6
- package/dist/introspect-db-queries-DevUrzll.js.map +1 -0
- package/dist/{open-rebase-CbOur0TU.js → open-rebase-Cwnqo5LU.js} +116 -51
- package/dist/open-rebase-Cwnqo5LU.js.map +1 -0
- package/dist/open-rebase.d.ts +12 -4
- package/dist/{pg-error-utils-vJezlXdI.js → pg-error-utils-Cmo8wZPy.js} +19 -5
- package/dist/pg-error-utils-Cmo8wZPy.js.map +1 -0
- package/dist/{plan-schema-B1YF_tYq.js → plan-schema-CXOucCw-.js} +4 -4
- package/dist/{plan-schema-B1YF_tYq.js.map → plan-schema-CXOucCw-.js.map} +1 -1
- package/dist/{policy-drift-Dm8i-s1J.js → policy-drift-vm0BcvcE.js} +28 -14
- package/dist/{policy-drift-Dm8i-s1J.js.map → policy-drift-vm0BcvcE.js.map} +1 -1
- package/dist/{push-index-prebuild-Db4AQF4i.js → push-index-prebuild-SXRlWBjc.js} +4 -4
- package/dist/{push-index-prebuild-Db4AQF4i.js.map → push-index-prebuild-SXRlWBjc.js.map} +1 -1
- package/dist/{push-legacy-renames-BK6ilQq2.js → push-legacy-renames-B37yKDac.js} +4 -4
- package/dist/{push-legacy-renames-BK6ilQq2.js.map → push-legacy-renames-B37yKDac.js.map} +1 -1
- package/dist/push-policy-plan-jXzHAWSu.js +127 -0
- package/dist/push-policy-plan-jXzHAWSu.js.map +1 -0
- package/dist/{render-ddl-CtC3y9Ol.js → render-ddl-CQNwZdse.js} +10 -9
- package/dist/render-ddl-CQNwZdse.js.map +1 -0
- package/dist/{rls-bootstrap-sql-RQUljnJ1.js → rls-bootstrap-sql-Bd-hJk08.js} +3 -58
- package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +1 -0
- package/dist/{rls-enforcement-DvzqlaL1.js → rls-enforcement-D7P4-ZYX.js} +2 -2
- package/dist/{rls-enforcement-DvzqlaL1.js.map → rls-enforcement-D7P4-ZYX.js.map} +1 -1
- package/dist/{__vite-browser-external-BnuHet1e.js → rolldown-runtime-V7rFbMRe.js} +1 -8
- package/dist/schema/atlas-apply-report.d.ts +23 -0
- package/dist/schema/atlas-argv.d.ts +24 -1
- package/dist/schema/atlas-exclude.d.ts +42 -0
- package/dist/schema/atlas-revisions.d.ts +4 -0
- package/dist/schema/carved-out-migration.d.ts +5 -4
- package/dist/schema/destructive-sql.d.ts +23 -1
- package/dist/schema/doctor-cli.js +5 -5
- package/dist/schema/ensure-collection-policies.d.ts +74 -0
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-next-step.d.ts +23 -0
- package/dist/schema/generate-postgres-ddl.js +2 -2
- package/dist/schema/generated-schema-diff.d.ts +7 -0
- package/dist/schema/generated-schema-staleness.d.ts +7 -1
- package/dist/schema/introspect-db-logic.d.ts +32 -1
- package/dist/schema/introspect-db-queries.d.ts +18 -5
- package/dist/schema/introspect-db.js +55 -53
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migrate-baseline.d.ts +51 -0
- package/dist/schema/plan/render-ddl.d.ts +2 -1
- package/dist/schema/push-policy-plan.d.ts +70 -0
- package/dist/schema/undeclarable-constraints.d.ts +59 -0
- package/dist/security/policy-drift.d.ts +5 -0
- package/dist/services/BranchService.d.ts +38 -0
- package/dist/{source-DBDbBTL4.js → source-Bv5KPeIx.js} +109 -4
- package/dist/source-Bv5KPeIx.js.map +1 -0
- package/dist/utils/connection-string.d.ts +47 -0
- package/dist/{vector-index-CMJKb3C9.js → vector-index-BFhyEaiz.js} +2 -2
- package/dist/{vector-index-CMJKb3C9.js.map → vector-index-BFhyEaiz.js.map} +1 -1
- package/dist/{websocket-BLGc5yhs.js → websocket-BT85Yv_G.js} +41 -13
- package/dist/websocket-BT85Yv_G.js.map +1 -0
- package/package.json +7 -7
- package/dist/BranchService-Bsn2wfaj.js.map +0 -1
- package/dist/__vite-browser-external-BnuHet1e.js.map +0 -1
- package/dist/arg-Dni7MzLB.js.map +0 -1
- package/dist/backup-cli-DnpJi6I7.js +0 -420
- package/dist/backup-cli-DnpJi6I7.js.map +0 -1
- package/dist/backup-service-BTBB7RXn.js.map +0 -1
- package/dist/cli-console-Ca6ZlAVU.js +0 -154
- package/dist/cli-console-Ca6ZlAVU.js.map +0 -1
- package/dist/cli-errors-CaI4ntyD.js.map +0 -1
- package/dist/cli-flags-JNI-pwSf.js.map +0 -1
- package/dist/doctor-C4WiftHm.js.map +0 -1
- package/dist/ensure-collection-policies-CXpbeNag.js.map +0 -1
- package/dist/generate-files-CdEckX6c.js.map +0 -1
- package/dist/introspect-db-logic-C-Di8dA9.js.map +0 -1
- package/dist/introspect-db-queries-D6kAqReI.js.map +0 -1
- package/dist/open-rebase-CbOur0TU.js.map +0 -1
- package/dist/pg-error-utils-vJezlXdI.js.map +0 -1
- package/dist/render-ddl-CtC3y9Ol.js.map +0 -1
- package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +0 -1
- package/dist/source-DBDbBTL4.js.map +0 -1
- package/dist/websocket-BLGc5yhs.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { o as execa
|
|
3
|
+
import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
|
|
4
|
+
import { Q as forLibpq, R as resolveLocalBin, et as splitPassword } from "./cli-errors-CJTKAKax.js";
|
|
5
|
+
import { o as execa } from "./rls-bootstrap-sql-Bd-hJk08.js";
|
|
6
6
|
import path from "path";
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import os from "os";
|
|
@@ -235,6 +235,111 @@ function buildPgRestoreArgs(opts) {
|
|
|
235
235
|
function buildPgRestoreListArgs(inputFile) {
|
|
236
236
|
return ["--list", inputFile];
|
|
237
237
|
}
|
|
238
|
+
/** `<id>; <catalog oid> <oid> <description> <namespace> <tag…> <owner>`, the shape of a TOC entry. */
|
|
239
|
+
var TOC_ENTRY = /^\d+;\s+\d+\s+\d+\s+(SCHEMA|TABLE)\s+(?!DATA\s)(\S+)\s+(.+)\s+(\S+)$/;
|
|
240
|
+
/** A TOC entry for a relation of any kind. `TABLE DATA`, `SEQUENCE SET` and `SEQUENCE OWNED BY` are not relations. */
|
|
241
|
+
var TOC_RELATION = /^\d+;\s+\d+\s+\d+\s+(?:FOREIGN TABLE|MATERIALIZED VIEW|TABLE|VIEW|SEQUENCE)\s+(?!DATA\s|SET\s|OWNED BY\s)(\S+)\s+(.+)\s+(\S+)$/;
|
|
242
|
+
/**
|
|
243
|
+
* The schemas and tables in `pg_restore --list` output: the objects a
|
|
244
|
+
* restore without `--clean` fails on when the target already has them.
|
|
245
|
+
*
|
|
246
|
+
* A TOC line ends in the object's owner, after its namespace and its name;
|
|
247
|
+
* a schema's namespace is `-`. The name is what lies between the two, so a
|
|
248
|
+
* name with a space in it (`Odd Name`) reads whole. `TABLE DATA` and every
|
|
249
|
+
* other kind of entry are not objects of their own here.
|
|
250
|
+
*/
|
|
251
|
+
function parseDumpObjects(list) {
|
|
252
|
+
const objects = {
|
|
253
|
+
schemas: [],
|
|
254
|
+
tables: [],
|
|
255
|
+
relations: []
|
|
256
|
+
};
|
|
257
|
+
for (const line of list.split("\n")) {
|
|
258
|
+
const trimmed = line.trim();
|
|
259
|
+
const relation = TOC_RELATION.exec(trimmed);
|
|
260
|
+
if (relation) objects.relations.push({
|
|
261
|
+
schema: relation[1],
|
|
262
|
+
name: relation[2]
|
|
263
|
+
});
|
|
264
|
+
const entry = TOC_ENTRY.exec(trimmed);
|
|
265
|
+
if (!entry) continue;
|
|
266
|
+
const [, kind, namespace, name] = entry;
|
|
267
|
+
if (kind === "SCHEMA") objects.schemas.push(name);
|
|
268
|
+
else objects.tables.push({
|
|
269
|
+
schema: namespace,
|
|
270
|
+
table: name
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return objects;
|
|
274
|
+
}
|
|
275
|
+
/** A Postgres identifier, double-quoted. */
|
|
276
|
+
function quoteIdentifier(name) {
|
|
277
|
+
return `"${name.replace(/"/g, "\"\"")}"`;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* How long `restore --clean` waits for a lock before it gives up.
|
|
281
|
+
*
|
|
282
|
+
* A `rebase dev` serving from the database holds connections, and one inside a
|
|
283
|
+
* transaction would otherwise make the restore wait without a word. The dump's
|
|
284
|
+
* own script sets `lock_timeout = 0` after this, for the objects it creates.
|
|
285
|
+
*/
|
|
286
|
+
var CLEAN_LOCK_TIMEOUT = "15s";
|
|
287
|
+
/**
|
|
288
|
+
* What `restore --clean` runs first, in the transaction the restore runs in:
|
|
289
|
+
* every schema the dump creates, dropped with `CASCADE`, and every table it
|
|
290
|
+
* creates in a schema it does not (`public`), the same.
|
|
291
|
+
*
|
|
292
|
+
* `CASCADE`, which `pg_restore --clean` never uses: a table the backup lacks
|
|
293
|
+
* in one of its schemas — `rebase.atlas_schema_revisions` from `rebase db
|
|
294
|
+
* migrate`, a table a newer runtime added — is exactly what stopped its plain
|
|
295
|
+
* `DROP SCHEMA rebase`, and a table that references one of the backup's is what
|
|
296
|
+
* stops a plain `DROP TABLE`. The dump's script follows, with its own `DROP …
|
|
297
|
+
* IF EXISTS` for everything else it creates.
|
|
298
|
+
*/
|
|
299
|
+
function cleanRestorePrelude(objects) {
|
|
300
|
+
const schemas = objects.schemas.filter((schema) => schema !== "public");
|
|
301
|
+
const dropped = new Set(schemas);
|
|
302
|
+
const tables = objects.tables.filter((table) => !dropped.has(table.schema));
|
|
303
|
+
return [
|
|
304
|
+
"SET client_min_messages = warning",
|
|
305
|
+
`SET lock_timeout = '15s'`,
|
|
306
|
+
...schemas.map((schema) => `DROP SCHEMA IF EXISTS ${quoteIdentifier(schema)} CASCADE`),
|
|
307
|
+
...tables.map((table) => `DROP TABLE IF EXISTS ${quoteIdentifier(table.schema)}.${quoteIdentifier(table.table)} CASCADE`)
|
|
308
|
+
];
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* `pg_restore` writing the dump as a SQL script on stdout, with the `DROP …
|
|
312
|
+
* IF EXISTS` that `--clean` adds, for `psql` to run. See {@link buildPsqlScriptArgs}.
|
|
313
|
+
*/
|
|
314
|
+
function buildPgRestoreScriptArgs(opts) {
|
|
315
|
+
return [
|
|
316
|
+
"--format=custom",
|
|
317
|
+
"--clean",
|
|
318
|
+
"--if-exists",
|
|
319
|
+
...opts.noOwner ? ["--no-owner"] : [],
|
|
320
|
+
"--file=-",
|
|
321
|
+
opts.inputFile
|
|
322
|
+
];
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* `psql` running a script read from stdin, stopping at its first error.
|
|
326
|
+
*
|
|
327
|
+
* `-X`, so no `.psqlrc` changes what runs; `-q`, so it prints errors and
|
|
328
|
+
* warnings, not a command tag per statement. The transaction is the script's
|
|
329
|
+
* own: `restore --clean` writes the `BEGIN`, and the `COMMIT` only once
|
|
330
|
+
* `pg_restore` has written the whole dump.
|
|
331
|
+
*/
|
|
332
|
+
function buildPsqlScriptArgs(connectionString) {
|
|
333
|
+
return [
|
|
334
|
+
"-X",
|
|
335
|
+
"-q",
|
|
336
|
+
"--no-password",
|
|
337
|
+
"-v",
|
|
338
|
+
"ON_ERROR_STOP=1",
|
|
339
|
+
`--dbname=${forLibpq(connectionString)}`,
|
|
340
|
+
"--file=-"
|
|
341
|
+
];
|
|
342
|
+
}
|
|
238
343
|
/**
|
|
239
344
|
* Assemble the `pg_dumpall --globals-only` argument vector. Roles (and other
|
|
240
345
|
* cluster-wide objects) live outside any single database, so a per-database
|
|
@@ -331,13 +436,14 @@ function selectBackupsToPrune(backups, options) {
|
|
|
331
436
|
var OVERRIDE_VARIABLE = {
|
|
332
437
|
pg_dump: "PG_DUMP_PATH",
|
|
333
438
|
pg_restore: "PG_RESTORE_PATH",
|
|
334
|
-
pg_dumpall: "PG_DUMPALL_PATH"
|
|
439
|
+
pg_dumpall: "PG_DUMPALL_PATH",
|
|
440
|
+
psql: "PSQL_PATH"
|
|
335
441
|
};
|
|
336
442
|
/** The variable that points at `tool` when it is not on the PATH. */
|
|
337
443
|
function pgToolVariable(tool) {
|
|
338
444
|
return OVERRIDE_VARIABLE[tool];
|
|
339
445
|
}
|
|
340
|
-
/** Locate `pg_dump` / `pg_restore` / `pg_dumpall`, honouring an env override. */
|
|
446
|
+
/** Locate `pg_dump` / `pg_restore` / `pg_dumpall` / `psql`, honouring an env override. */
|
|
341
447
|
function resolvePgBinary(tool, env = process.env) {
|
|
342
448
|
const override = env[OVERRIDE_VARIABLE[tool]];
|
|
343
449
|
if (override && fs.existsSync(override)) return override;
|
|
@@ -432,21 +538,45 @@ function discardPartialDumpWith(exists, remove, file) {
|
|
|
432
538
|
*/
|
|
433
539
|
var backup_service_exports = /* @__PURE__ */ __exportAll({
|
|
434
540
|
BackupToolError: () => BackupToolError,
|
|
541
|
+
CleanRestoreFailed: () => CleanRestoreFailed,
|
|
435
542
|
applyGlobals: () => applyGlobals,
|
|
436
543
|
createDump: () => createDump,
|
|
437
544
|
detectToolMajor: () => detectToolMajor,
|
|
438
545
|
discardPartialDump: () => discardPartialDump,
|
|
439
546
|
ensureDatabaseExists: () => ensureDatabaseExists,
|
|
547
|
+
findCleanExtras: () => findCleanExtras,
|
|
548
|
+
findRestoreConflicts: () => findRestoreConflicts,
|
|
440
549
|
getServerVersionMajor: () => getServerVersionMajor,
|
|
441
550
|
listBackups: () => listBackups,
|
|
442
551
|
preflight: () => preflight,
|
|
443
552
|
pruneBackups: () => pruneBackups,
|
|
553
|
+
readDumpObjects: () => readDumpObjects,
|
|
554
|
+
resetSharedSession: () => resetSharedSession,
|
|
444
555
|
resolvePgBinary: () => resolvePgBinary,
|
|
445
556
|
restoreDump: () => restoreDump,
|
|
557
|
+
restoreDumpReplacing: () => restoreDumpReplacing,
|
|
446
558
|
uploadBackup: () => uploadBackup,
|
|
447
559
|
validateDump: () => validateDump
|
|
448
560
|
});
|
|
449
561
|
/**
|
|
562
|
+
* How a libpq tool is invoked for a connection string: the URL its command
|
|
563
|
+
* line carries, and the environment it runs in.
|
|
564
|
+
*
|
|
565
|
+
* The password goes in `PGPASSWORD`, never on the command line, where every
|
|
566
|
+
* process listing could read it and every failure printed it back (execa's
|
|
567
|
+
* error repeats the command). See {@link splitPassword}.
|
|
568
|
+
*/
|
|
569
|
+
function libpqInvocation(connectionString, env) {
|
|
570
|
+
const split = splitPassword(connectionString);
|
|
571
|
+
const toolEnv = {};
|
|
572
|
+
for (const [key, value] of Object.entries(env)) if (value !== void 0) toolEnv[key] = value;
|
|
573
|
+
if (split.password !== null) toolEnv.PGPASSWORD = split.password;
|
|
574
|
+
return {
|
|
575
|
+
connectionString: split.connectionString,
|
|
576
|
+
env: toolEnv
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
450
580
|
* Remove a dump artifact abandoned by a failed tool run. See
|
|
451
581
|
* {@link discardPartialDumpWith} for why this exists and why the logic lives
|
|
452
582
|
* in the execa-free module.
|
|
@@ -483,6 +613,43 @@ async function getServerVersionMajor(connectionString) {
|
|
|
483
613
|
await client.end();
|
|
484
614
|
}
|
|
485
615
|
}
|
|
616
|
+
/** PGlite names itself in `version()`: `PostgreSQL 18.3 (PGlite 0.5.6) on wasm32-…`. */
|
|
617
|
+
var SHARED_SESSION_ENGINE = /\(PGlite\b/i;
|
|
618
|
+
/**
|
|
619
|
+
* Put back what a client tool left set on a database that is one session
|
|
620
|
+
* every connection shares, and do nothing on any other.
|
|
621
|
+
*
|
|
622
|
+
* The managed development database is PGlite: one session behind a socket
|
|
623
|
+
* server, so what `pg_dump` sets for itself — `search_path` emptied,
|
|
624
|
+
* `row_security` off, `PREPARE dumpEnumType …` — stays set for every client
|
|
625
|
+
* after it, a running `rebase dev` included. One `rebase db backup` there left
|
|
626
|
+
* every API request answering 500 "table does not exist", a restart did not
|
|
627
|
+
* help (the database outlives `rebase dev`), and the next backup failed on
|
|
628
|
+
* `prepared statement "dumpenumtype" already exists`.
|
|
629
|
+
*
|
|
630
|
+
* `RESET ALL` and `DEALLOCATE ALL`, not `DISCARD ALL`: that would also drop
|
|
631
|
+
* the `LISTEN` a running `rebase dev`'s realtime holds on the same session.
|
|
632
|
+
* Best effort: it runs after the tool, whose own outcome is what the caller
|
|
633
|
+
* reports.
|
|
634
|
+
*/
|
|
635
|
+
async function resetSharedSession(connectionString) {
|
|
636
|
+
const { Client } = await import("pg");
|
|
637
|
+
const client = new Client({
|
|
638
|
+
connectionString,
|
|
639
|
+
connectionTimeoutMillis: 1e4,
|
|
640
|
+
query_timeout: 15e3
|
|
641
|
+
});
|
|
642
|
+
client.on("error", () => void 0);
|
|
643
|
+
try {
|
|
644
|
+
await client.connect();
|
|
645
|
+
const { rows } = await client.query("SELECT version() AS version");
|
|
646
|
+
if (!SHARED_SESSION_ENGINE.test(rows[0]?.version ?? "")) return;
|
|
647
|
+
await client.query("RESET ALL");
|
|
648
|
+
await client.query("DEALLOCATE ALL");
|
|
649
|
+
} catch {} finally {
|
|
650
|
+
await client.end().catch(() => void 0);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
486
653
|
/**
|
|
487
654
|
* Verify the requested client tool exists and its major version is
|
|
488
655
|
* compatible with the live server. Throws {@link BackupToolError} — with a
|
|
@@ -517,20 +684,26 @@ async function createDump(opts) {
|
|
|
517
684
|
const outDir = opts.outDir ?? fs.mkdtempSync(path.join(os.tmpdir(), "rebase-backup-"));
|
|
518
685
|
fs.mkdirSync(outDir, { recursive: true });
|
|
519
686
|
const localFile = path.join(outDir, fileName);
|
|
687
|
+
createOwnerOnly(localFile);
|
|
688
|
+
const tool = libpqInvocation(opts.connectionString, env);
|
|
520
689
|
const args = buildPgDumpArgs({
|
|
521
|
-
connectionString:
|
|
690
|
+
connectionString: tool.connectionString,
|
|
522
691
|
outFile: localFile,
|
|
523
692
|
excludeSchemas: opts.excludeSchemas,
|
|
524
693
|
noOwner: opts.noOwner,
|
|
525
694
|
rowSecurity: opts.rowSecurity
|
|
526
695
|
});
|
|
527
|
-
const dumpEnv = { ...env };
|
|
696
|
+
const dumpEnv = { ...tool.env };
|
|
528
697
|
if (opts.rowSecurity) dumpEnv.PGOPTIONS = [env.PGOPTIONS, buildRowSecurityPgOptions(opts.rowSecurity)].filter(Boolean).join(" ");
|
|
529
698
|
try {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
699
|
+
try {
|
|
700
|
+
await execa(bin, args, {
|
|
701
|
+
stdio: opts.inheritStdio ? "inherit" : "pipe",
|
|
702
|
+
env: dumpEnv
|
|
703
|
+
});
|
|
704
|
+
} finally {
|
|
705
|
+
await resetSharedSession(opts.connectionString);
|
|
706
|
+
}
|
|
534
707
|
} catch (error) {
|
|
535
708
|
discardPartialDump(localFile);
|
|
536
709
|
const diagnosis = diagnoseRowSecurityDumpFailure(error);
|
|
@@ -546,14 +719,19 @@ async function createDump(opts) {
|
|
|
546
719
|
const dumpallBin = resolvePgBinary("pg_dumpall", env);
|
|
547
720
|
if (!dumpallBin) throw new BackupToolError("Could not find the 'pg_dumpall' binary needed to capture cluster roles.", "Install the PostgreSQL client tools or set PG_DUMPALL_PATH. To take a role-incomplete backup anyway, pass includeGlobals: false.");
|
|
548
721
|
const globalsFile = globalsFileForDump(localFile);
|
|
722
|
+
createOwnerOnly(globalsFile);
|
|
549
723
|
try {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
724
|
+
try {
|
|
725
|
+
await execa(dumpallBin, buildPgDumpallGlobalsArgs({
|
|
726
|
+
connectionString: tool.connectionString,
|
|
727
|
+
outFile: globalsFile
|
|
728
|
+
}), {
|
|
729
|
+
stdio: opts.inheritStdio ? "inherit" : "pipe",
|
|
730
|
+
env: tool.env
|
|
731
|
+
});
|
|
732
|
+
} finally {
|
|
733
|
+
await resetSharedSession(opts.connectionString);
|
|
734
|
+
}
|
|
557
735
|
} catch (error) {
|
|
558
736
|
discardPartialDump(globalsFile);
|
|
559
737
|
discardPartialDump(localFile);
|
|
@@ -565,6 +743,21 @@ async function createDump(opts) {
|
|
|
565
743
|
return result;
|
|
566
744
|
}
|
|
567
745
|
/**
|
|
746
|
+
* Create `file` empty and readable by its owner only, before a tool writes it.
|
|
747
|
+
*
|
|
748
|
+
* A dump holds every row, and the `.globals.sql` sidecar every role's password
|
|
749
|
+
* hash. pg_dump and pg_dumpall create their `--file=` target with the
|
|
750
|
+
* process's umask — 0644 on most machines, readable by every user — and keep
|
|
751
|
+
* the mode of a file that is already there. Created 0600 first, the file is
|
|
752
|
+
* never readable by anyone else, not even while the dump runs; chmod-ing it
|
|
753
|
+
* afterwards would leave it open for as long as that takes. A file that
|
|
754
|
+
* already existed is narrowed too.
|
|
755
|
+
*/
|
|
756
|
+
function createOwnerOnly(file) {
|
|
757
|
+
fs.closeSync(fs.openSync(file, "w", 384));
|
|
758
|
+
fs.chmodSync(file, 384);
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
568
761
|
* Cheap integrity check on a freshly written dump: it must be non-empty and
|
|
569
762
|
* `pg_restore --list` must parse its table of contents without error. Used
|
|
570
763
|
* before pruning older backups so a corrupt-but-exit-0 dump never becomes
|
|
@@ -634,16 +827,213 @@ async function restoreDump(opts) {
|
|
|
634
827
|
const env = opts.env ?? process.env;
|
|
635
828
|
const bin = resolvePgBinary("pg_restore", env);
|
|
636
829
|
if (!bin) throw new BackupToolError("Could not find the 'pg_restore' binary.", "Install the PostgreSQL client tools or set PG_RESTORE_PATH.");
|
|
637
|
-
|
|
638
|
-
|
|
830
|
+
const tool = libpqInvocation(opts.connectionString, env);
|
|
831
|
+
const args = buildPgRestoreArgs({
|
|
832
|
+
connectionString: tool.connectionString,
|
|
639
833
|
inputFile: opts.inputFile,
|
|
640
834
|
clean: opts.clean,
|
|
641
835
|
noOwner: opts.noOwner,
|
|
642
836
|
exitOnError: opts.exitOnError
|
|
837
|
+
});
|
|
838
|
+
try {
|
|
839
|
+
await execa(bin, args, {
|
|
840
|
+
stdio: opts.inheritStdio ? "inherit" : "pipe",
|
|
841
|
+
env: tool.env
|
|
842
|
+
});
|
|
843
|
+
} finally {
|
|
844
|
+
await resetSharedSession(opts.connectionString);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* A `restore --clean` that stopped, and so was rolled back whole.
|
|
849
|
+
*
|
|
850
|
+
* `tool` is the one that stopped it: `pg_restore`, writing the dump's script,
|
|
851
|
+
* or `psql`, running it. `lockTimeout` when what `psql` stopped on was the
|
|
852
|
+
* wait for a lock — `CLEAN_LOCK_TIMEOUT` in `pg-tools.ts`.
|
|
853
|
+
*/
|
|
854
|
+
var CleanRestoreFailed = class extends Error {
|
|
855
|
+
tool;
|
|
856
|
+
exitCode;
|
|
857
|
+
lockTimeout;
|
|
858
|
+
constructor(tool, exitCode, lockTimeout) {
|
|
859
|
+
super(`${tool} exited with code ${exitCode ?? "unknown"}`);
|
|
860
|
+
this.tool = tool;
|
|
861
|
+
this.exitCode = exitCode;
|
|
862
|
+
this.lockTimeout = lockTimeout;
|
|
863
|
+
this.name = "CleanRestoreFailed";
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
/**
|
|
867
|
+
* Restore a dump over what the database holds, as one transaction:
|
|
868
|
+
* {@link cleanRestorePrelude} first, then the dump as `pg_restore` writes it
|
|
869
|
+
* for `psql`, and the `COMMIT` only after the last of it.
|
|
870
|
+
*
|
|
871
|
+
* One unit, so it either replaces what the dump holds or changes nothing:
|
|
872
|
+
* `pg_restore --clean` runs each statement on its own, so the drops it starts
|
|
873
|
+
* with had all happened when a later one failed — and its plain `DROP SCHEMA
|
|
874
|
+
* rebase` fails on any table the backup lacks, which left a database with
|
|
875
|
+
* every table dropped and nothing restored. Here a failure anywhere —
|
|
876
|
+
* `pg_restore` cannot read the archive, a statement fails, the connection
|
|
877
|
+
* drops — ends the transaction without a `COMMIT`, and Postgres rolls it back.
|
|
878
|
+
*
|
|
879
|
+
* Throws {@link CleanRestoreFailed} when it did not commit.
|
|
880
|
+
*/
|
|
881
|
+
async function restoreDumpReplacing(opts) {
|
|
882
|
+
const env = opts.env ?? process.env;
|
|
883
|
+
const restoreBin = resolvePgBinary("pg_restore", env);
|
|
884
|
+
const psqlBin = resolvePgBinary("psql", env);
|
|
885
|
+
if (!restoreBin || !psqlBin) {
|
|
886
|
+
const tool = restoreBin ? "psql" : "pg_restore";
|
|
887
|
+
throw new BackupToolError(`Could not find the '${tool}' binary${tool === "psql" ? ", which runs a --clean restore as one transaction" : ""}.`, `Install the PostgreSQL client tools or set ${pgToolVariable(tool)}.`);
|
|
888
|
+
}
|
|
889
|
+
const tool = libpqInvocation(opts.connectionString, env);
|
|
890
|
+
const script = execa(restoreBin, buildPgRestoreScriptArgs({
|
|
891
|
+
inputFile: opts.inputFile,
|
|
892
|
+
noOwner: opts.noOwner
|
|
643
893
|
}), {
|
|
644
|
-
|
|
645
|
-
|
|
894
|
+
stdin: "ignore",
|
|
895
|
+
stdout: "pipe",
|
|
896
|
+
stderr: opts.inheritStdio ? "inherit" : "ignore",
|
|
897
|
+
buffer: false,
|
|
898
|
+
reject: false,
|
|
899
|
+
env: tool.env
|
|
900
|
+
});
|
|
901
|
+
const apply = execa(psqlBin, buildPsqlScriptArgs(tool.connectionString), {
|
|
902
|
+
stdin: "pipe",
|
|
903
|
+
stdout: "ignore",
|
|
904
|
+
stderr: opts.inheritStdio ? ["pipe", "inherit"] : "pipe",
|
|
905
|
+
reject: false,
|
|
906
|
+
env: tool.env
|
|
907
|
+
});
|
|
908
|
+
const sink = apply.stdin;
|
|
909
|
+
const source = script.stdout;
|
|
910
|
+
if (!sink || !source) throw new BackupToolError("Could not connect pg_restore to psql.");
|
|
911
|
+
sink.on("error", () => void 0);
|
|
912
|
+
apply.once("exit", () => {
|
|
913
|
+
source.unpipe(sink);
|
|
914
|
+
source.resume();
|
|
915
|
+
script.kill();
|
|
916
|
+
});
|
|
917
|
+
sink.write(["BEGIN", ...cleanRestorePrelude(opts.objects)].map((statement) => `${statement};\n`).join(""));
|
|
918
|
+
source.pipe(sink, { end: false });
|
|
919
|
+
const written = await script;
|
|
920
|
+
const committed = written.exitCode === 0 && sink.writable;
|
|
921
|
+
sink.end(committed ? "COMMIT;\n" : void 0);
|
|
922
|
+
const applied = await apply;
|
|
923
|
+
await resetSharedSession(opts.connectionString);
|
|
924
|
+
if (applied.exitCode !== 0) {
|
|
925
|
+
const stderr = typeof applied.stderr === "string" ? applied.stderr : "";
|
|
926
|
+
throw new CleanRestoreFailed("psql", applied.exitCode, /lock timeout/i.test(stderr));
|
|
927
|
+
}
|
|
928
|
+
if (!committed) throw new CleanRestoreFailed("pg_restore", written.exitCode, false);
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* What `restore --clean` drops that the backup does not hold, so it can say
|
|
932
|
+
* so before it runs: the tables and views in a schema the backup replaces
|
|
933
|
+
* whole that it has no copy of, and the views and foreign keys elsewhere that
|
|
934
|
+
* reference one of the relations it replaces. As `<kind> <name>`.
|
|
935
|
+
*
|
|
936
|
+
* None for a database that does not exist yet.
|
|
937
|
+
*/
|
|
938
|
+
async function findCleanExtras(connectionString, objects) {
|
|
939
|
+
const schemas = objects.schemas.filter((schema) => schema !== "public");
|
|
940
|
+
if (schemas.length === 0 && objects.relations.length === 0) return [];
|
|
941
|
+
const { Client } = await import("pg");
|
|
942
|
+
const client = new Client({ connectionString });
|
|
943
|
+
try {
|
|
944
|
+
await client.connect();
|
|
945
|
+
} catch (err) {
|
|
946
|
+
if (err instanceof Error && "code" in err && err.code === "3D000") return [];
|
|
947
|
+
throw err;
|
|
948
|
+
}
|
|
949
|
+
try {
|
|
950
|
+
const { rows } = await client.query(`WITH dumped AS (
|
|
951
|
+
SELECT c.oid
|
|
952
|
+
FROM pg_class c
|
|
953
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
954
|
+
JOIN unnest($2::text[], $3::text[]) AS d(schema_name, rel_name)
|
|
955
|
+
ON d.schema_name = n.nspname AND d.rel_name = c.relname
|
|
956
|
+
)
|
|
957
|
+
SELECT kind, name FROM (
|
|
958
|
+
SELECT CASE c.relkind WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view'
|
|
959
|
+
WHEN 'f' THEN 'foreign table' ELSE 'table' END AS kind,
|
|
960
|
+
n.nspname || '.' || c.relname AS name
|
|
961
|
+
FROM pg_class c
|
|
962
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
963
|
+
WHERE n.nspname = ANY($1::text[])
|
|
964
|
+
AND c.relkind IN ('r', 'p', 'v', 'm', 'f')
|
|
965
|
+
AND c.oid NOT IN (SELECT oid FROM dumped)
|
|
966
|
+
UNION
|
|
967
|
+
SELECT 'view', vn.nspname || '.' || v.relname
|
|
968
|
+
FROM pg_depend dep
|
|
969
|
+
JOIN pg_rewrite r ON r.oid = dep.objid AND dep.classid = 'pg_rewrite'::regclass
|
|
970
|
+
JOIN pg_class v ON v.oid = r.ev_class
|
|
971
|
+
JOIN pg_namespace vn ON vn.oid = v.relnamespace
|
|
972
|
+
WHERE dep.refobjid IN (SELECT oid FROM dumped)
|
|
973
|
+
AND v.oid NOT IN (SELECT oid FROM dumped)
|
|
974
|
+
AND NOT vn.nspname = ANY($1::text[])
|
|
975
|
+
UNION
|
|
976
|
+
SELECT 'foreign key', con.conname || ' on ' || cn.nspname || '.' || cr.relname
|
|
977
|
+
FROM pg_constraint con
|
|
978
|
+
JOIN pg_class cr ON cr.oid = con.conrelid
|
|
979
|
+
JOIN pg_namespace cn ON cn.oid = cr.relnamespace
|
|
980
|
+
WHERE con.contype = 'f'
|
|
981
|
+
AND con.confrelid IN (SELECT oid FROM dumped)
|
|
982
|
+
AND con.conrelid NOT IN (SELECT oid FROM dumped)
|
|
983
|
+
AND NOT cn.nspname = ANY($1::text[])
|
|
984
|
+
) extra
|
|
985
|
+
ORDER BY name`, [
|
|
986
|
+
schemas,
|
|
987
|
+
objects.relations.map((r) => r.schema),
|
|
988
|
+
objects.relations.map((r) => r.name)
|
|
989
|
+
]);
|
|
990
|
+
return rows.map((row) => `${row.kind} ${row.name}`);
|
|
991
|
+
} finally {
|
|
992
|
+
await client.end();
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
/** The schemas and tables a dump creates, from `pg_restore --list`. See {@link parseDumpObjects}. */
|
|
996
|
+
async function readDumpObjects(inputFile, env = process.env) {
|
|
997
|
+
const bin = resolvePgBinary("pg_restore", env);
|
|
998
|
+
if (!bin) throw new BackupToolError("Could not find the 'pg_restore' binary.", "Install the PostgreSQL client tools or set PG_RESTORE_PATH.");
|
|
999
|
+
const toolEnv = Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== void 0));
|
|
1000
|
+
const { stdout } = await execa(bin, buildPgRestoreListArgs(inputFile), {
|
|
1001
|
+
stdio: "pipe",
|
|
1002
|
+
env: toolEnv
|
|
646
1003
|
});
|
|
1004
|
+
return parseDumpObjects(stdout);
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Which of a dump's schemas and tables the target database already holds,
|
|
1008
|
+
* as `schema <name>` / `table <schema>.<name>`: what a restore without
|
|
1009
|
+
* `--clean` would stop on. `public` is left out of the schemas — every
|
|
1010
|
+
* database has it, and pg_restore does not create it. None for a database
|
|
1011
|
+
* that does not exist yet.
|
|
1012
|
+
*/
|
|
1013
|
+
async function findRestoreConflicts(connectionString, objects) {
|
|
1014
|
+
const schemas = objects.schemas.filter((schema) => schema !== "public");
|
|
1015
|
+
if (schemas.length === 0 && objects.tables.length === 0) return [];
|
|
1016
|
+
const { Client } = await import("pg");
|
|
1017
|
+
const client = new Client({ connectionString });
|
|
1018
|
+
try {
|
|
1019
|
+
await client.connect();
|
|
1020
|
+
} catch (err) {
|
|
1021
|
+
if (err instanceof Error && "code" in err && err.code === "3D000") return [];
|
|
1022
|
+
throw err;
|
|
1023
|
+
}
|
|
1024
|
+
try {
|
|
1025
|
+
const existingSchemas = await client.query("SELECT nspname FROM pg_namespace WHERE nspname = ANY($1::text[]) ORDER BY nspname", [schemas]);
|
|
1026
|
+
const existingTables = await client.query(`SELECT n.nspname, c.relname
|
|
1027
|
+
FROM pg_class c
|
|
1028
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1029
|
+
JOIN unnest($1::text[], $2::text[]) AS d(schema_name, table_name)
|
|
1030
|
+
ON d.schema_name = n.nspname AND d.table_name = c.relname
|
|
1031
|
+
WHERE c.relkind IN ('r', 'p')
|
|
1032
|
+
ORDER BY n.nspname, c.relname`, [objects.tables.map((t) => t.schema), objects.tables.map((t) => t.table)]);
|
|
1033
|
+
return [...existingSchemas.rows.map((row) => `schema ${row.nspname}`), ...existingTables.rows.map((row) => `table ${row.nspname}.${row.relname}`)];
|
|
1034
|
+
} finally {
|
|
1035
|
+
await client.end();
|
|
1036
|
+
}
|
|
647
1037
|
}
|
|
648
1038
|
/**
|
|
649
1039
|
* Create a database (if absent) by connecting to the maintenance
|
|
@@ -742,6 +1132,6 @@ async function pruneBackups(dest, options, storage) {
|
|
|
742
1132
|
return toDelete;
|
|
743
1133
|
}
|
|
744
1134
|
//#endregion
|
|
745
|
-
export {
|
|
1135
|
+
export { buildPgRestoreScriptArgs as A, parseDbNameFromUrl as B, selectBackupsToPrune as C, buildPgDumpallGlobalsArgs as D, buildPgDumpArgs as E, diagnoseRowSecurityDumpFailure as F, splitGlobalsStatements as G, parsePgToolMajor as H, globalsFileForDump as I, withDatabaseName as K, joinStorageKey as L, buildRowSecurityPgOptions as M, checkToolServerCompatibility as N, buildPgRestoreArgs as O, cleanRestorePrelude as P, parseBackupDestination as R, resolvePgBinary as S, buildBackupFilename as T, resolveConnectionString as U, parseDumpObjects as V, serverVersionNumToMajor as W, restoreDump as _, createDump as a, validateDump as b, ensureDatabaseExists as c, getServerVersionMajor as d, listBackups as f, resetSharedSession as g, readDumpObjects as h, backup_service_exports as i, buildPsqlScriptArgs as j, buildPgRestoreListArgs as k, findCleanExtras as l, pruneBackups as m, CleanRestoreFailed as n, detectToolMajor as o, preflight as p, applyGlobals as r, discardPartialDump as s, BackupToolError as t, findRestoreConflicts as u, restoreDumpReplacing as v, CLEAN_LOCK_TIMEOUT as w, pgToolVariable as x, uploadBackup as y, parseBackupTimestamp as z };
|
|
746
1136
|
|
|
747
|
-
//# sourceMappingURL=backup-service-
|
|
1137
|
+
//# sourceMappingURL=backup-service-DytMimsL.js.map
|