@rebasepro/server-postgres 0.25.0-canary.ge06257e → 0.25.0
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-DNg4txyf.js → BranchService-iXXZeVYD.js} +222 -19
- package/dist/BranchService-iXXZeVYD.js.map +1 -0
- package/dist/backup/backup-cli.d.ts +33 -1
- package/dist/backup/backup-service.d.ts +68 -0
- package/dist/backup/pg-binary.d.ts +2 -2
- package/dist/backup/pg-tools.d.ts +60 -1
- package/dist/{backup-cli-HCEkPcQj.js → backup-cli-B2CAn9fR.js} +105 -14
- package/dist/backup-cli-B2CAn9fR.js.map +1 -0
- package/dist/{backup-service-D-UpT7DH.js → backup-service-Bb39plam.js} +358 -21
- package/dist/backup-service-Bb39plam.js.map +1 -0
- package/dist/{boot-ddl-Dnvpmm_T.js → boot-ddl-s_3Hkz94.js} +2 -2
- package/dist/{boot-ddl-Dnvpmm_T.js.map → boot-ddl-s_3Hkz94.js.map} +1 -1
- package/dist/branch-prune.d.ts +18 -0
- package/dist/{cli-console-C_ISGwJo.js → cli-console-DQ_K-sdM.js} +17 -10
- package/dist/cli-console-DQ_K-sdM.js.map +1 -0
- package/dist/cli-console.d.ts +4 -3
- package/dist/{cli-errors-mx4sSjB8.js → cli-errors-DwQnNATs.js} +80 -12
- package/dist/{cli-errors-mx4sSjB8.js.map → cli-errors-DwQnNATs.js.map} +1 -1
- package/dist/cli-errors.d.ts +12 -1
- package/dist/{cli-flags-DUozBFyK.js → cli-flags-D-b0qL9T.js} +3 -2
- package/dist/{cli-flags-DUozBFyK.js.map → cli-flags-D-b0qL9T.js.map} +1 -1
- package/dist/cli.js +106 -40
- package/dist/cli.js.map +1 -1
- package/dist/collections/validate-relations.d.ts +8 -2
- package/dist/{deferred-index-build-Ca6OqKyR.js → deferred-index-build-Cis_HoGn.js} +6 -6
- package/dist/{deferred-index-build-Ca6OqKyR.js.map → deferred-index-build-Cis_HoGn.js.map} +1 -1
- package/dist/{doctor-CJyuhV1n.js → doctor-DvJH-IQG.js} +6 -6
- package/dist/doctor-DvJH-IQG.js.map +1 -0
- package/dist/{ensure-collection-policies-C1ux3V_z.js → ensure-collection-policies-CCqayoUz.js} +54 -8
- package/dist/ensure-collection-policies-CCqayoUz.js.map +1 -0
- package/dist/{ensure-collection-tables-Do-Bvb64.js → ensure-collection-tables-DvvImXDe.js} +7 -7
- package/dist/{ensure-collection-tables-Do-Bvb64.js.map → ensure-collection-tables-DvvImXDe.js.map} +1 -1
- package/dist/{ensure-tables-Dl_Eh9ow.js → ensure-tables-Dpgwx5sy.js} +2 -2
- package/dist/{ensure-tables-Dl_Eh9ow.js.map → ensure-tables-Dpgwx5sy.js.map} +1 -1
- package/dist/{enum-labels-kept-D9eTIWCz.js → enum-labels-kept-CUlUlRkO.js} +3 -3
- package/dist/{enum-labels-kept-D9eTIWCz.js.map → enum-labels-kept-CUlUlRkO.js.map} +1 -1
- package/dist/{generate-drizzle-schema-Bf8canaR.js → generate-drizzle-schema-C3EAP9HE.js} +3 -3
- package/dist/{generate-drizzle-schema-Bf8canaR.js.map → generate-drizzle-schema-C3EAP9HE.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-BaT0H7Gq.js → generate-drizzle-schema-logic-DA3zM8zc.js} +3 -3
- package/dist/{generate-drizzle-schema-logic-BaT0H7Gq.js.map → generate-drizzle-schema-logic-DA3zM8zc.js.map} +1 -1
- package/dist/{generate-files-D50Pxw1G.js → generate-files-ClmOUHXj.js} +5 -5
- package/dist/{generate-files-D50Pxw1G.js.map → generate-files-ClmOUHXj.js.map} +1 -1
- package/dist/{generate-next-step-DCd7yiJ2.js → generate-next-step-DCeS4LrJ.js} +22 -14
- package/dist/{generate-next-step-DCd7yiJ2.js.map → generate-next-step-DCeS4LrJ.js.map} +1 -1
- package/dist/{generate-postgres-ddl-logic-B4k9djAV.js → generate-postgres-ddl-logic-4eUP4t8w.js} +3 -3
- package/dist/{generate-postgres-ddl-logic-B4k9djAV.js.map → generate-postgres-ddl-logic-4eUP4t8w.js.map} +1 -1
- package/dist/index.es.js +8 -8
- package/dist/{introspect-db-logic-j2vYhmdP.js → introspect-db-logic-CI5CU_n0.js} +2 -2
- package/dist/{introspect-db-logic-j2vYhmdP.js.map → introspect-db-logic-CI5CU_n0.js.map} +1 -1
- package/dist/{open-rebase-D8rytTP0.js → open-rebase-BKA7Nyoe.js} +47 -27
- package/dist/{open-rebase-D8rytTP0.js.map → open-rebase-BKA7Nyoe.js.map} +1 -1
- package/dist/{pg-error-utils-CdsGYMTm.js → pg-error-utils-Bv5n2PLn.js} +11 -4
- package/dist/pg-error-utils-Bv5n2PLn.js.map +1 -0
- package/dist/{plan-schema-CsNgK0qi.js → plan-schema-CXOucCw-.js} +2 -2
- package/dist/{plan-schema-CsNgK0qi.js.map → plan-schema-CXOucCw-.js.map} +1 -1
- package/dist/{policy-drift-CY5P1ZRu.js → policy-drift-vm0BcvcE.js} +2 -2
- package/dist/{policy-drift-CY5P1ZRu.js.map → policy-drift-vm0BcvcE.js.map} +1 -1
- package/dist/{push-index-prebuild-BDFcfRPo.js → push-index-prebuild-DIequ4bV.js} +4 -4
- package/dist/{push-index-prebuild-BDFcfRPo.js.map → push-index-prebuild-DIequ4bV.js.map} +1 -1
- package/dist/{push-legacy-renames-D4bvMWAb.js → push-legacy-renames-BqJut4F3.js} +4 -4
- package/dist/{push-legacy-renames-D4bvMWAb.js.map → push-legacy-renames-BqJut4F3.js.map} +1 -1
- package/dist/{push-policy-plan-DTXE6aVa.js → push-policy-plan-CvX8hiPb.js} +22 -6
- package/dist/push-policy-plan-CvX8hiPb.js.map +1 -0
- package/dist/{render-ddl-CrfEe__B.js → render-ddl-CQNwZdse.js} +3 -3
- package/dist/{render-ddl-CrfEe__B.js.map → render-ddl-CQNwZdse.js.map} +1 -1
- package/dist/schema/atlas-argv.d.ts +3 -1
- package/dist/schema/doctor-cli.js +4 -4
- package/dist/schema/ensure-collection-policies.d.ts +24 -0
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-next-step.d.ts +20 -8
- package/dist/schema/generate-postgres-ddl.js +2 -2
- package/dist/schema/introspect-db.js +5 -4
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/push-policy-plan.d.ts +5 -2
- package/dist/services/BranchService.d.ts +67 -0
- package/dist/{source-DB6xJv3p.js → source-Bv5KPeIx.js} +45 -2
- package/dist/source-Bv5KPeIx.js.map +1 -0
- package/dist/{websocket-DMeXOoyv.js → websocket-MC0zTBar.js} +3 -3
- package/dist/{websocket-DMeXOoyv.js.map → websocket-MC0zTBar.js.map} +1 -1
- package/package.json +7 -7
- package/dist/BranchService-DNg4txyf.js.map +0 -1
- package/dist/backup-cli-HCEkPcQj.js.map +0 -1
- package/dist/backup-service-D-UpT7DH.js.map +0 -1
- package/dist/cli-console-C_ISGwJo.js.map +0 -1
- package/dist/doctor-CJyuhV1n.js.map +0 -1
- package/dist/ensure-collection-policies-C1ux3V_z.js.map +0 -1
- package/dist/pg-error-utils-CdsGYMTm.js.map +0 -1
- package/dist/push-policy-plan-DTXE6aVa.js.map +0 -1
- package/dist/source-DB6xJv3p.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
3
|
import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
|
|
4
|
-
import {
|
|
4
|
+
import { $ as forLibpq, tt as splitPassword, z as resolveLocalBin } from "./cli-errors-DwQnNATs.js";
|
|
5
5
|
import { o as execa } from "./rls-bootstrap-sql-Bd-hJk08.js";
|
|
6
6
|
import path from "path";
|
|
7
7
|
import fs from "fs";
|
|
@@ -237,6 +237,12 @@ function buildPgRestoreListArgs(inputFile) {
|
|
|
237
237
|
}
|
|
238
238
|
/** `<id>; <catalog oid> <oid> <description> <namespace> <tag…> <owner>`, the shape of a TOC entry. */
|
|
239
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
|
+
/** A function's TOC entry: its tag is the name and the argument types, `two(integer, text)`. */
|
|
243
|
+
var TOC_ROUTINE = /^\d+;\s+\d+\s+\d+\s+(?:FUNCTION|PROCEDURE|AGGREGATE)\s+(\S+)\s+(.+?)\(.*\)\s+\S+$/;
|
|
244
|
+
/** A type's or a domain's TOC entry. */
|
|
245
|
+
var TOC_TYPE = /^\d+;\s+\d+\s+\d+\s+(?:TYPE|DOMAIN)\s+(\S+)\s+(.+)\s+(\S+)$/;
|
|
240
246
|
/**
|
|
241
247
|
* The schemas and tables in `pg_restore --list` output: the objects a
|
|
242
248
|
* restore without `--clean` fails on when the target already has them.
|
|
@@ -244,15 +250,36 @@ var TOC_ENTRY = /^\d+;\s+\d+\s+\d+\s+(SCHEMA|TABLE)\s+(?!DATA\s)(\S+)\s+(.+)\s+(
|
|
|
244
250
|
* A TOC line ends in the object's owner, after its namespace and its name;
|
|
245
251
|
* a schema's namespace is `-`. The name is what lies between the two, so a
|
|
246
252
|
* name with a space in it (`Odd Name`) reads whole. `TABLE DATA` and every
|
|
247
|
-
* other kind of entry are not objects of their own here.
|
|
253
|
+
* other kind of entry are not objects of their own here. Its relations,
|
|
254
|
+
* functions and types are read too, for what `restore --clean` would drop
|
|
255
|
+
* that the dump does not hold (`findCleanExtras`).
|
|
248
256
|
*/
|
|
249
257
|
function parseDumpObjects(list) {
|
|
250
258
|
const objects = {
|
|
251
259
|
schemas: [],
|
|
252
|
-
tables: []
|
|
260
|
+
tables: [],
|
|
261
|
+
relations: [],
|
|
262
|
+
routines: [],
|
|
263
|
+
types: []
|
|
253
264
|
};
|
|
254
265
|
for (const line of list.split("\n")) {
|
|
255
|
-
const
|
|
266
|
+
const trimmed = line.trim();
|
|
267
|
+
const relation = TOC_RELATION.exec(trimmed);
|
|
268
|
+
if (relation) objects.relations.push({
|
|
269
|
+
schema: relation[1],
|
|
270
|
+
name: relation[2]
|
|
271
|
+
});
|
|
272
|
+
const routine = TOC_ROUTINE.exec(trimmed);
|
|
273
|
+
if (routine) objects.routines.push({
|
|
274
|
+
schema: routine[1],
|
|
275
|
+
name: routine[2]
|
|
276
|
+
});
|
|
277
|
+
const type = TOC_TYPE.exec(trimmed);
|
|
278
|
+
if (type) objects.types.push({
|
|
279
|
+
schema: type[1],
|
|
280
|
+
name: type[2]
|
|
281
|
+
});
|
|
282
|
+
const entry = TOC_ENTRY.exec(trimmed);
|
|
256
283
|
if (!entry) continue;
|
|
257
284
|
const [, kind, namespace, name] = entry;
|
|
258
285
|
if (kind === "SCHEMA") objects.schemas.push(name);
|
|
@@ -263,6 +290,74 @@ function parseDumpObjects(list) {
|
|
|
263
290
|
}
|
|
264
291
|
return objects;
|
|
265
292
|
}
|
|
293
|
+
/** A Postgres identifier, double-quoted. */
|
|
294
|
+
function quoteIdentifier(name) {
|
|
295
|
+
return `"${name.replace(/"/g, "\"\"")}"`;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* How long `restore --clean` waits for a lock before it gives up.
|
|
299
|
+
*
|
|
300
|
+
* A `rebase dev` serving from the database holds connections, and one inside a
|
|
301
|
+
* transaction would otherwise make the restore wait without a word. The dump's
|
|
302
|
+
* own script sets `lock_timeout = 0` after this, for the objects it creates.
|
|
303
|
+
*/
|
|
304
|
+
var CLEAN_LOCK_TIMEOUT = "15s";
|
|
305
|
+
/**
|
|
306
|
+
* What `restore --clean` runs first, in the transaction the restore runs in:
|
|
307
|
+
* every schema the dump creates, dropped with `CASCADE`, and every table it
|
|
308
|
+
* creates in a schema it does not (`public`), the same.
|
|
309
|
+
*
|
|
310
|
+
* `CASCADE`, which `pg_restore --clean` never uses: a table the backup lacks
|
|
311
|
+
* in one of its schemas — `rebase.atlas_schema_revisions` from `rebase db
|
|
312
|
+
* migrate`, a table a newer runtime added — is exactly what stopped its plain
|
|
313
|
+
* `DROP SCHEMA rebase`, and a table that references one of the backup's is what
|
|
314
|
+
* stops a plain `DROP TABLE`. The dump's script follows, with its own `DROP …
|
|
315
|
+
* IF EXISTS` for everything else it creates.
|
|
316
|
+
*/
|
|
317
|
+
function cleanRestorePrelude(objects) {
|
|
318
|
+
const schemas = objects.schemas.filter((schema) => schema !== "public");
|
|
319
|
+
const dropped = new Set(schemas);
|
|
320
|
+
const tables = objects.tables.filter((table) => !dropped.has(table.schema));
|
|
321
|
+
return [
|
|
322
|
+
"SET client_min_messages = warning",
|
|
323
|
+
`SET lock_timeout = '15s'`,
|
|
324
|
+
...schemas.map((schema) => `DROP SCHEMA IF EXISTS ${quoteIdentifier(schema)} CASCADE`),
|
|
325
|
+
...tables.map((table) => `DROP TABLE IF EXISTS ${quoteIdentifier(table.schema)}.${quoteIdentifier(table.table)} CASCADE`)
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* `pg_restore` writing the dump as a SQL script on stdout, with the `DROP …
|
|
330
|
+
* IF EXISTS` that `--clean` adds, for `psql` to run. See {@link buildPsqlScriptArgs}.
|
|
331
|
+
*/
|
|
332
|
+
function buildPgRestoreScriptArgs(opts) {
|
|
333
|
+
return [
|
|
334
|
+
"--format=custom",
|
|
335
|
+
"--clean",
|
|
336
|
+
"--if-exists",
|
|
337
|
+
...opts.noOwner ? ["--no-owner"] : [],
|
|
338
|
+
"--file=-",
|
|
339
|
+
opts.inputFile
|
|
340
|
+
];
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* `psql` running a script read from stdin, stopping at its first error.
|
|
344
|
+
*
|
|
345
|
+
* `-X`, so no `.psqlrc` changes what runs; `-q`, so it prints errors and
|
|
346
|
+
* warnings, not a command tag per statement. The transaction is the script's
|
|
347
|
+
* own: `restore --clean` writes the `BEGIN`, and the `COMMIT` only once
|
|
348
|
+
* `pg_restore` has written the whole dump.
|
|
349
|
+
*/
|
|
350
|
+
function buildPsqlScriptArgs(connectionString) {
|
|
351
|
+
return [
|
|
352
|
+
"-X",
|
|
353
|
+
"-q",
|
|
354
|
+
"--no-password",
|
|
355
|
+
"-v",
|
|
356
|
+
"ON_ERROR_STOP=1",
|
|
357
|
+
`--dbname=${forLibpq(connectionString)}`,
|
|
358
|
+
"--file=-"
|
|
359
|
+
];
|
|
360
|
+
}
|
|
266
361
|
/**
|
|
267
362
|
* Assemble the `pg_dumpall --globals-only` argument vector. Roles (and other
|
|
268
363
|
* cluster-wide objects) live outside any single database, so a per-database
|
|
@@ -359,13 +454,14 @@ function selectBackupsToPrune(backups, options) {
|
|
|
359
454
|
var OVERRIDE_VARIABLE = {
|
|
360
455
|
pg_dump: "PG_DUMP_PATH",
|
|
361
456
|
pg_restore: "PG_RESTORE_PATH",
|
|
362
|
-
pg_dumpall: "PG_DUMPALL_PATH"
|
|
457
|
+
pg_dumpall: "PG_DUMPALL_PATH",
|
|
458
|
+
psql: "PSQL_PATH"
|
|
363
459
|
};
|
|
364
460
|
/** The variable that points at `tool` when it is not on the PATH. */
|
|
365
461
|
function pgToolVariable(tool) {
|
|
366
462
|
return OVERRIDE_VARIABLE[tool];
|
|
367
463
|
}
|
|
368
|
-
/** Locate `pg_dump` / `pg_restore` / `pg_dumpall`, honouring an env override. */
|
|
464
|
+
/** Locate `pg_dump` / `pg_restore` / `pg_dumpall` / `psql`, honouring an env override. */
|
|
369
465
|
function resolvePgBinary(tool, env = process.env) {
|
|
370
466
|
const override = env[OVERRIDE_VARIABLE[tool]];
|
|
371
467
|
if (override && fs.existsSync(override)) return override;
|
|
@@ -460,19 +556,23 @@ function discardPartialDumpWith(exists, remove, file) {
|
|
|
460
556
|
*/
|
|
461
557
|
var backup_service_exports = /* @__PURE__ */ __exportAll({
|
|
462
558
|
BackupToolError: () => BackupToolError,
|
|
559
|
+
CleanRestoreFailed: () => CleanRestoreFailed,
|
|
463
560
|
applyGlobals: () => applyGlobals,
|
|
464
561
|
createDump: () => createDump,
|
|
465
562
|
detectToolMajor: () => detectToolMajor,
|
|
466
563
|
discardPartialDump: () => discardPartialDump,
|
|
467
564
|
ensureDatabaseExists: () => ensureDatabaseExists,
|
|
565
|
+
findCleanExtras: () => findCleanExtras,
|
|
468
566
|
findRestoreConflicts: () => findRestoreConflicts,
|
|
469
567
|
getServerVersionMajor: () => getServerVersionMajor,
|
|
470
568
|
listBackups: () => listBackups,
|
|
471
569
|
preflight: () => preflight,
|
|
472
570
|
pruneBackups: () => pruneBackups,
|
|
473
571
|
readDumpObjects: () => readDumpObjects,
|
|
572
|
+
resetSharedSession: () => resetSharedSession,
|
|
474
573
|
resolvePgBinary: () => resolvePgBinary,
|
|
475
574
|
restoreDump: () => restoreDump,
|
|
575
|
+
restoreDumpReplacing: () => restoreDumpReplacing,
|
|
476
576
|
uploadBackup: () => uploadBackup,
|
|
477
577
|
validateDump: () => validateDump
|
|
478
578
|
});
|
|
@@ -531,6 +631,43 @@ async function getServerVersionMajor(connectionString) {
|
|
|
531
631
|
await client.end();
|
|
532
632
|
}
|
|
533
633
|
}
|
|
634
|
+
/** PGlite names itself in `version()`: `PostgreSQL 18.3 (PGlite 0.5.6) on wasm32-…`. */
|
|
635
|
+
var SHARED_SESSION_ENGINE = /\(PGlite\b/i;
|
|
636
|
+
/**
|
|
637
|
+
* Put back what a client tool left set on a database that is one session
|
|
638
|
+
* every connection shares, and do nothing on any other.
|
|
639
|
+
*
|
|
640
|
+
* The managed development database is PGlite: one session behind a socket
|
|
641
|
+
* server, so what `pg_dump` sets for itself — `search_path` emptied,
|
|
642
|
+
* `row_security` off, `PREPARE dumpEnumType …` — stays set for every client
|
|
643
|
+
* after it, a running `rebase dev` included. One `rebase db backup` there left
|
|
644
|
+
* every API request answering 500 "table does not exist", a restart did not
|
|
645
|
+
* help (the database outlives `rebase dev`), and the next backup failed on
|
|
646
|
+
* `prepared statement "dumpenumtype" already exists`.
|
|
647
|
+
*
|
|
648
|
+
* `RESET ALL` and `DEALLOCATE ALL`, not `DISCARD ALL`: that would also drop
|
|
649
|
+
* the `LISTEN` a running `rebase dev`'s realtime holds on the same session.
|
|
650
|
+
* Best effort: it runs after the tool, whose own outcome is what the caller
|
|
651
|
+
* reports.
|
|
652
|
+
*/
|
|
653
|
+
async function resetSharedSession(connectionString) {
|
|
654
|
+
const { Client } = await import("pg");
|
|
655
|
+
const client = new Client({
|
|
656
|
+
connectionString,
|
|
657
|
+
connectionTimeoutMillis: 1e4,
|
|
658
|
+
query_timeout: 15e3
|
|
659
|
+
});
|
|
660
|
+
client.on("error", () => void 0);
|
|
661
|
+
try {
|
|
662
|
+
await client.connect();
|
|
663
|
+
const { rows } = await client.query("SELECT version() AS version");
|
|
664
|
+
if (!SHARED_SESSION_ENGINE.test(rows[0]?.version ?? "")) return;
|
|
665
|
+
await client.query("RESET ALL");
|
|
666
|
+
await client.query("DEALLOCATE ALL");
|
|
667
|
+
} catch {} finally {
|
|
668
|
+
await client.end().catch(() => void 0);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
534
671
|
/**
|
|
535
672
|
* Verify the requested client tool exists and its major version is
|
|
536
673
|
* compatible with the live server. Throws {@link BackupToolError} — with a
|
|
@@ -577,10 +714,14 @@ async function createDump(opts) {
|
|
|
577
714
|
const dumpEnv = { ...tool.env };
|
|
578
715
|
if (opts.rowSecurity) dumpEnv.PGOPTIONS = [env.PGOPTIONS, buildRowSecurityPgOptions(opts.rowSecurity)].filter(Boolean).join(" ");
|
|
579
716
|
try {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
717
|
+
try {
|
|
718
|
+
await execa(bin, args, {
|
|
719
|
+
stdio: opts.inheritStdio ? "inherit" : "pipe",
|
|
720
|
+
env: dumpEnv
|
|
721
|
+
});
|
|
722
|
+
} finally {
|
|
723
|
+
await resetSharedSession(opts.connectionString);
|
|
724
|
+
}
|
|
584
725
|
} catch (error) {
|
|
585
726
|
discardPartialDump(localFile);
|
|
586
727
|
const diagnosis = diagnoseRowSecurityDumpFailure(error);
|
|
@@ -598,13 +739,17 @@ async function createDump(opts) {
|
|
|
598
739
|
const globalsFile = globalsFileForDump(localFile);
|
|
599
740
|
createOwnerOnly(globalsFile);
|
|
600
741
|
try {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
742
|
+
try {
|
|
743
|
+
await execa(dumpallBin, buildPgDumpallGlobalsArgs({
|
|
744
|
+
connectionString: tool.connectionString,
|
|
745
|
+
outFile: globalsFile
|
|
746
|
+
}), {
|
|
747
|
+
stdio: opts.inheritStdio ? "inherit" : "pipe",
|
|
748
|
+
env: tool.env
|
|
749
|
+
});
|
|
750
|
+
} finally {
|
|
751
|
+
await resetSharedSession(opts.connectionString);
|
|
752
|
+
}
|
|
608
753
|
} catch (error) {
|
|
609
754
|
discardPartialDump(globalsFile);
|
|
610
755
|
discardPartialDump(localFile);
|
|
@@ -701,16 +846,208 @@ async function restoreDump(opts) {
|
|
|
701
846
|
const bin = resolvePgBinary("pg_restore", env);
|
|
702
847
|
if (!bin) throw new BackupToolError("Could not find the 'pg_restore' binary.", "Install the PostgreSQL client tools or set PG_RESTORE_PATH.");
|
|
703
848
|
const tool = libpqInvocation(opts.connectionString, env);
|
|
704
|
-
|
|
849
|
+
const args = buildPgRestoreArgs({
|
|
705
850
|
connectionString: tool.connectionString,
|
|
706
851
|
inputFile: opts.inputFile,
|
|
707
852
|
clean: opts.clean,
|
|
708
853
|
noOwner: opts.noOwner,
|
|
709
854
|
exitOnError: opts.exitOnError
|
|
855
|
+
});
|
|
856
|
+
try {
|
|
857
|
+
await execa(bin, args, {
|
|
858
|
+
stdio: opts.inheritStdio ? "inherit" : "pipe",
|
|
859
|
+
env: tool.env
|
|
860
|
+
});
|
|
861
|
+
} finally {
|
|
862
|
+
await resetSharedSession(opts.connectionString);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* A `restore --clean` that stopped, and so was rolled back whole.
|
|
867
|
+
*
|
|
868
|
+
* `tool` is the one that stopped it: `pg_restore`, writing the dump's script,
|
|
869
|
+
* or `psql`, running it. `lockTimeout` when what `psql` stopped on was the
|
|
870
|
+
* wait for a lock — `CLEAN_LOCK_TIMEOUT` in `pg-tools.ts`.
|
|
871
|
+
*/
|
|
872
|
+
var CleanRestoreFailed = class extends Error {
|
|
873
|
+
tool;
|
|
874
|
+
exitCode;
|
|
875
|
+
lockTimeout;
|
|
876
|
+
constructor(tool, exitCode, lockTimeout) {
|
|
877
|
+
super(`${tool} exited with code ${exitCode ?? "unknown"}`);
|
|
878
|
+
this.tool = tool;
|
|
879
|
+
this.exitCode = exitCode;
|
|
880
|
+
this.lockTimeout = lockTimeout;
|
|
881
|
+
this.name = "CleanRestoreFailed";
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
/**
|
|
885
|
+
* Restore a dump over what the database holds, as one transaction:
|
|
886
|
+
* {@link cleanRestorePrelude} first, then the dump as `pg_restore` writes it
|
|
887
|
+
* for `psql`, and the `COMMIT` only after the last of it.
|
|
888
|
+
*
|
|
889
|
+
* One unit, so it either replaces what the dump holds or changes nothing:
|
|
890
|
+
* `pg_restore --clean` runs each statement on its own, so the drops it starts
|
|
891
|
+
* with had all happened when a later one failed — and its plain `DROP SCHEMA
|
|
892
|
+
* rebase` fails on any table the backup lacks, which left a database with
|
|
893
|
+
* every table dropped and nothing restored. Here a failure anywhere —
|
|
894
|
+
* `pg_restore` cannot read the archive, a statement fails, the connection
|
|
895
|
+
* drops — ends the transaction without a `COMMIT`, and Postgres rolls it back.
|
|
896
|
+
*
|
|
897
|
+
* Throws {@link CleanRestoreFailed} when it did not commit.
|
|
898
|
+
*/
|
|
899
|
+
async function restoreDumpReplacing(opts) {
|
|
900
|
+
const env = opts.env ?? process.env;
|
|
901
|
+
const restoreBin = resolvePgBinary("pg_restore", env);
|
|
902
|
+
const psqlBin = resolvePgBinary("psql", env);
|
|
903
|
+
if (!restoreBin || !psqlBin) {
|
|
904
|
+
const tool = restoreBin ? "psql" : "pg_restore";
|
|
905
|
+
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)}.`);
|
|
906
|
+
}
|
|
907
|
+
const tool = libpqInvocation(opts.connectionString, env);
|
|
908
|
+
const script = execa(restoreBin, buildPgRestoreScriptArgs({
|
|
909
|
+
inputFile: opts.inputFile,
|
|
910
|
+
noOwner: opts.noOwner
|
|
710
911
|
}), {
|
|
711
|
-
|
|
912
|
+
stdin: "ignore",
|
|
913
|
+
stdout: "pipe",
|
|
914
|
+
stderr: opts.inheritStdio ? "inherit" : "ignore",
|
|
915
|
+
buffer: false,
|
|
916
|
+
reject: false,
|
|
712
917
|
env: tool.env
|
|
713
918
|
});
|
|
919
|
+
const apply = execa(psqlBin, buildPsqlScriptArgs(tool.connectionString), {
|
|
920
|
+
stdin: "pipe",
|
|
921
|
+
stdout: "ignore",
|
|
922
|
+
stderr: opts.inheritStdio ? ["pipe", "inherit"] : "pipe",
|
|
923
|
+
reject: false,
|
|
924
|
+
env: tool.env
|
|
925
|
+
});
|
|
926
|
+
const sink = apply.stdin;
|
|
927
|
+
const source = script.stdout;
|
|
928
|
+
if (!sink || !source) throw new BackupToolError("Could not connect pg_restore to psql.");
|
|
929
|
+
sink.on("error", () => void 0);
|
|
930
|
+
apply.once("exit", () => {
|
|
931
|
+
source.unpipe(sink);
|
|
932
|
+
source.resume();
|
|
933
|
+
script.kill();
|
|
934
|
+
});
|
|
935
|
+
sink.write(["BEGIN", ...cleanRestorePrelude(opts.objects)].map((statement) => `${statement};\n`).join(""));
|
|
936
|
+
source.pipe(sink, { end: false });
|
|
937
|
+
const written = await script;
|
|
938
|
+
const committed = written.exitCode === 0 && sink.writable;
|
|
939
|
+
sink.end(committed ? "COMMIT;\n" : void 0);
|
|
940
|
+
const applied = await apply;
|
|
941
|
+
await resetSharedSession(opts.connectionString);
|
|
942
|
+
if (applied.exitCode !== 0) {
|
|
943
|
+
const stderr = typeof applied.stderr === "string" ? applied.stderr : "";
|
|
944
|
+
throw new CleanRestoreFailed("psql", applied.exitCode, /lock timeout/i.test(stderr));
|
|
945
|
+
}
|
|
946
|
+
if (!committed) throw new CleanRestoreFailed("pg_restore", written.exitCode, false);
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* What `restore --clean` drops that the backup does not hold, so it can say
|
|
950
|
+
* so before it runs: what a schema the backup replaces whole holds that it has
|
|
951
|
+
* no copy of — tables, views, sequences, functions, procedures, aggregates,
|
|
952
|
+
* types and domains — and the views and foreign keys elsewhere that reference
|
|
953
|
+
* one of the relations it replaces. As `<kind> <name>`.
|
|
954
|
+
*
|
|
955
|
+
* A function is matched by its name, so an overload the backup lacks beside
|
|
956
|
+
* one it holds is not named. Sequences a column owns go with their table,
|
|
957
|
+
* and what an extension owns with the extension, so neither is named.
|
|
958
|
+
*
|
|
959
|
+
* None for a database that does not exist yet.
|
|
960
|
+
*/
|
|
961
|
+
async function findCleanExtras(connectionString, objects) {
|
|
962
|
+
const schemas = objects.schemas.filter((schema) => schema !== "public");
|
|
963
|
+
if (schemas.length === 0 && objects.relations.length === 0) return [];
|
|
964
|
+
const { routines, types } = objects;
|
|
965
|
+
const { Client } = await import("pg");
|
|
966
|
+
const client = new Client({ connectionString });
|
|
967
|
+
try {
|
|
968
|
+
await client.connect();
|
|
969
|
+
} catch (err) {
|
|
970
|
+
if (err instanceof Error && "code" in err && err.code === "3D000") return [];
|
|
971
|
+
throw err;
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
const { rows } = await client.query(`WITH dumped AS (
|
|
975
|
+
SELECT c.oid
|
|
976
|
+
FROM pg_class c
|
|
977
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
978
|
+
JOIN unnest($2::text[], $3::text[]) AS d(schema_name, rel_name)
|
|
979
|
+
ON d.schema_name = n.nspname AND d.rel_name = c.relname
|
|
980
|
+
)
|
|
981
|
+
SELECT kind, name FROM (
|
|
982
|
+
SELECT CASE c.relkind WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view'
|
|
983
|
+
WHEN 'f' THEN 'foreign table' WHEN 'S' THEN 'sequence' ELSE 'table' END AS kind,
|
|
984
|
+
n.nspname || '.' || c.relname AS name
|
|
985
|
+
FROM pg_class c
|
|
986
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
987
|
+
WHERE n.nspname = ANY($1::text[])
|
|
988
|
+
AND c.relkind IN ('r', 'p', 'v', 'm', 'f', 'S')
|
|
989
|
+
AND c.oid NOT IN (SELECT oid FROM dumped)
|
|
990
|
+
AND NOT (c.relkind = 'S' AND EXISTS (
|
|
991
|
+
SELECT 1 FROM pg_depend owner
|
|
992
|
+
WHERE owner.classid = 'pg_class'::regclass AND owner.objid = c.oid
|
|
993
|
+
AND owner.deptype IN ('a', 'i')))
|
|
994
|
+
UNION
|
|
995
|
+
SELECT CASE p.prokind WHEN 'p' THEN 'procedure' WHEN 'a' THEN 'aggregate' ELSE 'function' END,
|
|
996
|
+
n.nspname || '.' || p.proname
|
|
997
|
+
FROM pg_proc p
|
|
998
|
+
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
999
|
+
WHERE n.nspname = ANY($1::text[])
|
|
1000
|
+
AND (n.nspname, p.proname) NOT IN (SELECT * FROM unnest($4::text[], $5::text[]))
|
|
1001
|
+
AND NOT EXISTS (
|
|
1002
|
+
SELECT 1 FROM pg_depend owner
|
|
1003
|
+
WHERE owner.classid = 'pg_proc'::regclass AND owner.objid = p.oid
|
|
1004
|
+
AND owner.deptype IN ('e', 'i'))
|
|
1005
|
+
UNION
|
|
1006
|
+
SELECT CASE t.typtype WHEN 'd' THEN 'domain' ELSE 'type' END,
|
|
1007
|
+
n.nspname || '.' || t.typname
|
|
1008
|
+
FROM pg_type t
|
|
1009
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
1010
|
+
WHERE n.nspname = ANY($1::text[])
|
|
1011
|
+
AND (t.typtype IN ('e', 'd', 'r')
|
|
1012
|
+
OR (t.typtype = 'c' AND EXISTS (
|
|
1013
|
+
SELECT 1 FROM pg_class tc WHERE tc.oid = t.typrelid AND tc.relkind = 'c')))
|
|
1014
|
+
AND (n.nspname, t.typname) NOT IN (SELECT * FROM unnest($6::text[], $7::text[]))
|
|
1015
|
+
AND NOT EXISTS (
|
|
1016
|
+
SELECT 1 FROM pg_depend owner
|
|
1017
|
+
WHERE owner.classid = 'pg_type'::regclass AND owner.objid = t.oid
|
|
1018
|
+
AND owner.deptype = 'e')
|
|
1019
|
+
UNION
|
|
1020
|
+
SELECT 'view', vn.nspname || '.' || v.relname
|
|
1021
|
+
FROM pg_depend dep
|
|
1022
|
+
JOIN pg_rewrite r ON r.oid = dep.objid AND dep.classid = 'pg_rewrite'::regclass
|
|
1023
|
+
JOIN pg_class v ON v.oid = r.ev_class
|
|
1024
|
+
JOIN pg_namespace vn ON vn.oid = v.relnamespace
|
|
1025
|
+
WHERE dep.refobjid IN (SELECT oid FROM dumped)
|
|
1026
|
+
AND v.oid NOT IN (SELECT oid FROM dumped)
|
|
1027
|
+
AND NOT vn.nspname = ANY($1::text[])
|
|
1028
|
+
UNION
|
|
1029
|
+
SELECT 'foreign key', con.conname || ' on ' || cn.nspname || '.' || cr.relname
|
|
1030
|
+
FROM pg_constraint con
|
|
1031
|
+
JOIN pg_class cr ON cr.oid = con.conrelid
|
|
1032
|
+
JOIN pg_namespace cn ON cn.oid = cr.relnamespace
|
|
1033
|
+
WHERE con.contype = 'f'
|
|
1034
|
+
AND con.confrelid IN (SELECT oid FROM dumped)
|
|
1035
|
+
AND con.conrelid NOT IN (SELECT oid FROM dumped)
|
|
1036
|
+
AND NOT cn.nspname = ANY($1::text[])
|
|
1037
|
+
) extra
|
|
1038
|
+
ORDER BY name`, [
|
|
1039
|
+
schemas,
|
|
1040
|
+
objects.relations.map((r) => r.schema),
|
|
1041
|
+
objects.relations.map((r) => r.name),
|
|
1042
|
+
routines.map((r) => r.schema),
|
|
1043
|
+
routines.map((r) => r.name),
|
|
1044
|
+
types.map((t) => t.schema),
|
|
1045
|
+
types.map((t) => t.name)
|
|
1046
|
+
]);
|
|
1047
|
+
return rows.map((row) => `${row.kind} ${row.name}`);
|
|
1048
|
+
} finally {
|
|
1049
|
+
await client.end();
|
|
1050
|
+
}
|
|
714
1051
|
}
|
|
715
1052
|
/** The schemas and tables a dump creates, from `pg_restore --list`. See {@link parseDumpObjects}. */
|
|
716
1053
|
async function readDumpObjects(inputFile, env = process.env) {
|
|
@@ -852,6 +1189,6 @@ async function pruneBackups(dest, options, storage) {
|
|
|
852
1189
|
return toDelete;
|
|
853
1190
|
}
|
|
854
1191
|
//#endregion
|
|
855
|
-
export {
|
|
1192
|
+
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 };
|
|
856
1193
|
|
|
857
|
-
//# sourceMappingURL=backup-service-
|
|
1194
|
+
//# sourceMappingURL=backup-service-Bb39plam.js.map
|