@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.
Files changed (89) hide show
  1. package/dist/{BranchService-DNg4txyf.js → BranchService-iXXZeVYD.js} +222 -19
  2. package/dist/BranchService-iXXZeVYD.js.map +1 -0
  3. package/dist/backup/backup-cli.d.ts +33 -1
  4. package/dist/backup/backup-service.d.ts +68 -0
  5. package/dist/backup/pg-binary.d.ts +2 -2
  6. package/dist/backup/pg-tools.d.ts +60 -1
  7. package/dist/{backup-cli-HCEkPcQj.js → backup-cli-B2CAn9fR.js} +105 -14
  8. package/dist/backup-cli-B2CAn9fR.js.map +1 -0
  9. package/dist/{backup-service-D-UpT7DH.js → backup-service-Bb39plam.js} +358 -21
  10. package/dist/backup-service-Bb39plam.js.map +1 -0
  11. package/dist/{boot-ddl-Dnvpmm_T.js → boot-ddl-s_3Hkz94.js} +2 -2
  12. package/dist/{boot-ddl-Dnvpmm_T.js.map → boot-ddl-s_3Hkz94.js.map} +1 -1
  13. package/dist/branch-prune.d.ts +18 -0
  14. package/dist/{cli-console-C_ISGwJo.js → cli-console-DQ_K-sdM.js} +17 -10
  15. package/dist/cli-console-DQ_K-sdM.js.map +1 -0
  16. package/dist/cli-console.d.ts +4 -3
  17. package/dist/{cli-errors-mx4sSjB8.js → cli-errors-DwQnNATs.js} +80 -12
  18. package/dist/{cli-errors-mx4sSjB8.js.map → cli-errors-DwQnNATs.js.map} +1 -1
  19. package/dist/cli-errors.d.ts +12 -1
  20. package/dist/{cli-flags-DUozBFyK.js → cli-flags-D-b0qL9T.js} +3 -2
  21. package/dist/{cli-flags-DUozBFyK.js.map → cli-flags-D-b0qL9T.js.map} +1 -1
  22. package/dist/cli.js +106 -40
  23. package/dist/cli.js.map +1 -1
  24. package/dist/collections/validate-relations.d.ts +8 -2
  25. package/dist/{deferred-index-build-Ca6OqKyR.js → deferred-index-build-Cis_HoGn.js} +6 -6
  26. package/dist/{deferred-index-build-Ca6OqKyR.js.map → deferred-index-build-Cis_HoGn.js.map} +1 -1
  27. package/dist/{doctor-CJyuhV1n.js → doctor-DvJH-IQG.js} +6 -6
  28. package/dist/doctor-DvJH-IQG.js.map +1 -0
  29. package/dist/{ensure-collection-policies-C1ux3V_z.js → ensure-collection-policies-CCqayoUz.js} +54 -8
  30. package/dist/ensure-collection-policies-CCqayoUz.js.map +1 -0
  31. package/dist/{ensure-collection-tables-Do-Bvb64.js → ensure-collection-tables-DvvImXDe.js} +7 -7
  32. package/dist/{ensure-collection-tables-Do-Bvb64.js.map → ensure-collection-tables-DvvImXDe.js.map} +1 -1
  33. package/dist/{ensure-tables-Dl_Eh9ow.js → ensure-tables-Dpgwx5sy.js} +2 -2
  34. package/dist/{ensure-tables-Dl_Eh9ow.js.map → ensure-tables-Dpgwx5sy.js.map} +1 -1
  35. package/dist/{enum-labels-kept-D9eTIWCz.js → enum-labels-kept-CUlUlRkO.js} +3 -3
  36. package/dist/{enum-labels-kept-D9eTIWCz.js.map → enum-labels-kept-CUlUlRkO.js.map} +1 -1
  37. package/dist/{generate-drizzle-schema-Bf8canaR.js → generate-drizzle-schema-C3EAP9HE.js} +3 -3
  38. package/dist/{generate-drizzle-schema-Bf8canaR.js.map → generate-drizzle-schema-C3EAP9HE.js.map} +1 -1
  39. package/dist/{generate-drizzle-schema-logic-BaT0H7Gq.js → generate-drizzle-schema-logic-DA3zM8zc.js} +3 -3
  40. package/dist/{generate-drizzle-schema-logic-BaT0H7Gq.js.map → generate-drizzle-schema-logic-DA3zM8zc.js.map} +1 -1
  41. package/dist/{generate-files-D50Pxw1G.js → generate-files-ClmOUHXj.js} +5 -5
  42. package/dist/{generate-files-D50Pxw1G.js.map → generate-files-ClmOUHXj.js.map} +1 -1
  43. package/dist/{generate-next-step-DCd7yiJ2.js → generate-next-step-DCeS4LrJ.js} +22 -14
  44. package/dist/{generate-next-step-DCd7yiJ2.js.map → generate-next-step-DCeS4LrJ.js.map} +1 -1
  45. package/dist/{generate-postgres-ddl-logic-B4k9djAV.js → generate-postgres-ddl-logic-4eUP4t8w.js} +3 -3
  46. package/dist/{generate-postgres-ddl-logic-B4k9djAV.js.map → generate-postgres-ddl-logic-4eUP4t8w.js.map} +1 -1
  47. package/dist/index.es.js +8 -8
  48. package/dist/{introspect-db-logic-j2vYhmdP.js → introspect-db-logic-CI5CU_n0.js} +2 -2
  49. package/dist/{introspect-db-logic-j2vYhmdP.js.map → introspect-db-logic-CI5CU_n0.js.map} +1 -1
  50. package/dist/{open-rebase-D8rytTP0.js → open-rebase-BKA7Nyoe.js} +47 -27
  51. package/dist/{open-rebase-D8rytTP0.js.map → open-rebase-BKA7Nyoe.js.map} +1 -1
  52. package/dist/{pg-error-utils-CdsGYMTm.js → pg-error-utils-Bv5n2PLn.js} +11 -4
  53. package/dist/pg-error-utils-Bv5n2PLn.js.map +1 -0
  54. package/dist/{plan-schema-CsNgK0qi.js → plan-schema-CXOucCw-.js} +2 -2
  55. package/dist/{plan-schema-CsNgK0qi.js.map → plan-schema-CXOucCw-.js.map} +1 -1
  56. package/dist/{policy-drift-CY5P1ZRu.js → policy-drift-vm0BcvcE.js} +2 -2
  57. package/dist/{policy-drift-CY5P1ZRu.js.map → policy-drift-vm0BcvcE.js.map} +1 -1
  58. package/dist/{push-index-prebuild-BDFcfRPo.js → push-index-prebuild-DIequ4bV.js} +4 -4
  59. package/dist/{push-index-prebuild-BDFcfRPo.js.map → push-index-prebuild-DIequ4bV.js.map} +1 -1
  60. package/dist/{push-legacy-renames-D4bvMWAb.js → push-legacy-renames-BqJut4F3.js} +4 -4
  61. package/dist/{push-legacy-renames-D4bvMWAb.js.map → push-legacy-renames-BqJut4F3.js.map} +1 -1
  62. package/dist/{push-policy-plan-DTXE6aVa.js → push-policy-plan-CvX8hiPb.js} +22 -6
  63. package/dist/push-policy-plan-CvX8hiPb.js.map +1 -0
  64. package/dist/{render-ddl-CrfEe__B.js → render-ddl-CQNwZdse.js} +3 -3
  65. package/dist/{render-ddl-CrfEe__B.js.map → render-ddl-CQNwZdse.js.map} +1 -1
  66. package/dist/schema/atlas-argv.d.ts +3 -1
  67. package/dist/schema/doctor-cli.js +4 -4
  68. package/dist/schema/ensure-collection-policies.d.ts +24 -0
  69. package/dist/schema/generate-drizzle-schema.js +1 -1
  70. package/dist/schema/generate-next-step.d.ts +20 -8
  71. package/dist/schema/generate-postgres-ddl.js +2 -2
  72. package/dist/schema/introspect-db.js +5 -4
  73. package/dist/schema/introspect-db.js.map +1 -1
  74. package/dist/schema/push-policy-plan.d.ts +5 -2
  75. package/dist/services/BranchService.d.ts +67 -0
  76. package/dist/{source-DB6xJv3p.js → source-Bv5KPeIx.js} +45 -2
  77. package/dist/source-Bv5KPeIx.js.map +1 -0
  78. package/dist/{websocket-DMeXOoyv.js → websocket-MC0zTBar.js} +3 -3
  79. package/dist/{websocket-DMeXOoyv.js.map → websocket-MC0zTBar.js.map} +1 -1
  80. package/package.json +7 -7
  81. package/dist/BranchService-DNg4txyf.js.map +0 -1
  82. package/dist/backup-cli-HCEkPcQj.js.map +0 -1
  83. package/dist/backup-service-D-UpT7DH.js.map +0 -1
  84. package/dist/cli-console-C_ISGwJo.js.map +0 -1
  85. package/dist/doctor-CJyuhV1n.js.map +0 -1
  86. package/dist/ensure-collection-policies-C1ux3V_z.js.map +0 -1
  87. package/dist/pg-error-utils-CdsGYMTm.js.map +0 -1
  88. package/dist/push-policy-plan-DTXE6aVa.js.map +0 -1
  89. 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 { Q as forLibpq, R as resolveLocalBin, et as splitPassword } from "./cli-errors-mx4sSjB8.js";
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 entry = TOC_ENTRY.exec(line.trim());
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
- await execa(bin, args, {
581
- stdio: opts.inheritStdio ? "inherit" : "pipe",
582
- env: dumpEnv
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
- await execa(dumpallBin, buildPgDumpallGlobalsArgs({
602
- connectionString: tool.connectionString,
603
- outFile: globalsFile
604
- }), {
605
- stdio: opts.inheritStdio ? "inherit" : "pipe",
606
- env: tool.env
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
- await execa(bin, buildPgRestoreArgs({
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
- stdio: opts.inheritStdio ? "inherit" : "pipe",
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 { parseBackupDestination as A, buildPgRestoreArgs as C, diagnoseRowSecurityDumpFailure as D, checkToolServerCompatibility as E, resolveConnectionString as F, serverVersionNumToMajor as I, splitGlobalsStatements as L, parseDbNameFromUrl as M, parseDumpObjects as N, globalsFileForDump as O, parsePgToolMajor as P, withDatabaseName as R, buildPgDumpallGlobalsArgs as S, buildRowSecurityPgOptions as T, pgToolVariable as _, detectToolMajor as a, buildBackupFilename as b, findRestoreConflicts as c, preflight as d, pruneBackups as f, validateDump as g, uploadBackup as h, createDump as i, parseBackupTimestamp as j, joinStorageKey as k, getServerVersionMajor as l, restoreDump as m, applyGlobals as n, discardPartialDump as o, readDumpObjects as p, backup_service_exports as r, ensureDatabaseExists as s, BackupToolError as t, listBackups as u, resolvePgBinary as v, buildPgRestoreListArgs as w, buildPgDumpArgs as x, selectBackupsToPrune as y };
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-D-UpT7DH.js.map
1194
+ //# sourceMappingURL=backup-service-Bb39plam.js.map