@intentius/chant-lexicon-sql 0.124.31 → 0.124.32
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/change-set.js +250 -0
- package/dist/clickhouse/access.js +722 -0
- package/dist/clickhouse/apply/apply.js +243 -0
- package/dist/clickhouse/apply/mutations.js +66 -0
- package/dist/clickhouse/apply/statements.js +545 -0
- package/dist/clickhouse/catalog-types.js +7 -0
- package/dist/clickhouse/container.js +152 -0
- package/dist/clickhouse/engine-syntax.js +159 -0
- package/dist/clickhouse/entities.js +593 -0
- package/dist/clickhouse/expressions.js +107 -0
- package/dist/clickhouse/http.js +55 -0
- package/dist/clickhouse/import/generator.js +192 -0
- package/dist/clickhouse/import/inlined.js +207 -0
- package/dist/clickhouse/import/ir.js +160 -0
- package/dist/clickhouse/import/live-export.js +131 -0
- package/dist/clickhouse/import/parser.js +17 -0
- package/dist/clickhouse/live/bind.js +116 -0
- package/dist/clickhouse/live/catalog.js +223 -0
- package/dist/clickhouse/live/describe-resources.js +91 -0
- package/dist/clickhouse/live/undeclarable.js +59 -0
- package/dist/clickhouse/overlays/codecs.js +57 -0
- package/dist/clickhouse/overlays/engines.js +101 -0
- package/dist/clickhouse/overlays/kinds.js +1 -0
- package/dist/clickhouse/overlays/projections.js +26 -0
- package/dist/clickhouse/overlays/skip-indexes.js +40 -0
- package/dist/clickhouse/overlays/ttl.js +34 -0
- package/dist/clickhouse/overlays/types.js +59 -0
- package/dist/clickhouse/ownership.js +83 -0
- package/dist/clickhouse/parser.js +674 -0
- package/dist/clickhouse/plan/commands.js +159 -0
- package/dist/clickhouse/plan/deep.js +345 -0
- package/dist/clickhouse/plan/diff.js +312 -0
- package/dist/clickhouse/plan/disruption.js +73 -0
- package/dist/clickhouse/plan/normalize.js +545 -0
- package/dist/clickhouse/plan/rebuild-handoff.js +57 -0
- package/dist/clickhouse/plan/report.js +26 -0
- package/dist/clickhouse/plan/rules.js +72 -0
- package/dist/clickhouse/plan/schema.js +67 -0
- package/dist/clickhouse/plan/server-format.js +35 -0
- package/dist/clickhouse/rebuild/backfill.js +265 -0
- package/dist/clickhouse/rebuild/engines.js +146 -0
- package/dist/clickhouse/rebuild/observe.js +260 -0
- package/dist/clickhouse/rebuild/op.js +152 -0
- package/dist/clickhouse/rebuild/partitions.js +65 -0
- package/dist/clickhouse/rebuild/receipts.js +120 -0
- package/dist/clickhouse/rebuild/replicas.js +121 -0
- package/dist/clickhouse/rebuild/shards.js +76 -0
- package/dist/clickhouse/rebuild/steps.js +383 -0
- package/dist/clickhouse/rebuild/verify.js +279 -0
- package/dist/clickhouse/row-types.js +151 -0
- package/dist/clickhouse/testing/fake-server.js +47 -0
- package/dist/clickhouse/testing/roundtrip-schema.js +25 -0
- package/dist/clickhouse/testing/writable-server.js +123 -0
- package/dist/clickhouse/tokens.js +23 -0
- package/dist/clickhouse/topology.js +463 -0
- package/dist/clickhouse/type-syntax.js +73 -0
- package/dist/clickhouse.js +25 -0
- package/dist/codegen/classifier-page.js +93 -0
- package/dist/codegen/clickhouse-module.js +249 -0
- package/dist/codegen/coverage-page.js +70 -0
- package/dist/codegen/docs-cli.js +3 -0
- package/dist/codegen/docs.js +222 -0
- package/dist/codegen/generate-cli.js +13 -0
- package/dist/codegen/generate.js +97 -0
- package/dist/codegen/package.js +28 -0
- package/dist/codegen/postgres-classifier-page.js +95 -0
- package/dist/codegen/postgres-module.js +418 -0
- package/dist/codegen/registry.js +52 -0
- package/dist/composites/audit-log-table.js +35 -0
- package/dist/composites/catalog.js +524 -0
- package/dist/composites/cdc-mirror.js +32 -0
- package/dist/composites/clickhouse.js +6 -0
- package/dist/composites/events-table.js +26 -0
- package/dist/composites/index.js +8 -0
- package/dist/composites/join-table.js +25 -0
- package/dist/composites/postgres.js +6 -0
- package/dist/composites/refreshed-view.js +24 -0
- package/dist/composites/replacing-table.js +28 -0
- package/dist/composites/rollup-view.js +29 -0
- package/dist/composites/sharded-table.js +27 -0
- package/dist/composites/soft-delete-table.js +34 -0
- package/dist/composites/tenant-table.js +26 -0
- package/dist/composites/typed.js +8 -0
- package/dist/config.js +206 -0
- package/dist/core/apply.js +93 -0
- package/dist/core/classifier.js +115 -0
- package/dist/core/container.js +69 -0
- package/dist/core/cursor.js +261 -0
- package/dist/core/diff.js +48 -0
- package/dist/core/entity.js +58 -0
- package/dist/core/find-templates.js +118 -0
- package/dist/core/foreign-tables.js +27 -0
- package/dist/core/handoff.js +32 -0
- package/dist/core/index.js +64 -0
- package/dist/core/interpolation.js +191 -0
- package/dist/core/normalize.js +43 -0
- package/dist/core/ownership.js +119 -0
- package/dist/core/receipts.js +22 -0
- package/dist/core/references.js +61 -0
- package/dist/core/statements.js +42 -0
- package/dist/core/template.js +79 -0
- package/dist/core/testing/http-stub.js +21 -0
- package/dist/core/tokens.js +202 -0
- package/dist/core/typed.js +40 -0
- package/dist/core/undeclarable.js +46 -0
- package/dist/coverage.js +161 -0
- package/dist/detect.js +32 -0
- package/dist/dialects.js +18 -0
- package/dist/files/build-root.js +72 -0
- package/dist/files/clickhouse.js +138 -0
- package/dist/files/common.js +58 -0
- package/dist/files/postgres.js +389 -0
- package/dist/generated/clickhouse-types.js +5 -0
- package/dist/generated/clickhouse.js +4361 -0
- package/dist/generated/postgres-types.js +227 -0
- package/dist/generated/postgres.js +3667 -0
- package/dist/import-generator.js +14 -0
- package/dist/import-parser.js +71 -0
- package/dist/index.js +24 -0
- package/dist/init-templates.js +290 -0
- package/dist/integrity.json +2 -2
- package/dist/lint/audit-catalog.js +31 -0
- package/dist/lint/audit-lineage.js +22 -0
- package/dist/lint/post-synth/audit-entries.js +34 -0
- package/dist/lint/post-synth/clickhouse-helpers.js +79 -0
- package/dist/lint/post-synth/clickhouse-type-check.js +162 -0
- package/dist/lint/post-synth/index.js +112 -0
- package/dist/lint/post-synth/postgres-audit-entries.js +35 -0
- package/dist/lint/post-synth/postgres-helpers.js +65 -0
- package/dist/lint/post-synth/postgres-names.js +507 -0
- package/dist/lint/post-synth/sql-helpers.js +23 -0
- package/dist/lint/post-synth/sql101.js +59 -0
- package/dist/lint/post-synth/sql102.js +34 -0
- package/dist/lint/post-synth/sqlch101.js +34 -0
- package/dist/lint/post-synth/sqlch102.js +23 -0
- package/dist/lint/post-synth/sqlch103.js +45 -0
- package/dist/lint/post-synth/sqlch104.js +41 -0
- package/dist/lint/post-synth/sqlch105.js +36 -0
- package/dist/lint/post-synth/sqlch106.js +27 -0
- package/dist/lint/post-synth/sqlch107.js +39 -0
- package/dist/lint/post-synth/sqlch108.js +28 -0
- package/dist/lint/post-synth/sqlch109.js +21 -0
- package/dist/lint/post-synth/sqlch110.js +31 -0
- package/dist/lint/post-synth/sqlch111.js +41 -0
- package/dist/lint/post-synth/sqlch112.js +28 -0
- package/dist/lint/post-synth/sqlch113.js +19 -0
- package/dist/lint/post-synth/sqlch114.js +26 -0
- package/dist/lint/post-synth/sqlch115.js +30 -0
- package/dist/lint/post-synth/sqlch116.js +22 -0
- package/dist/lint/post-synth/sqlch117.js +26 -0
- package/dist/lint/post-synth/sqlch118.js +27 -0
- package/dist/lint/post-synth/sqlch119.js +32 -0
- package/dist/lint/post-synth/sqlch120.js +20 -0
- package/dist/lint/post-synth/sqlch121.js +11 -0
- package/dist/lint/post-synth/sqlch122.js +11 -0
- package/dist/lint/post-synth/sqlch123.js +10 -0
- package/dist/lint/post-synth/sqlch124.js +21 -0
- package/dist/lint/post-synth/sqlch125.js +55 -0
- package/dist/lint/post-synth/sqlch126.js +62 -0
- package/dist/lint/post-synth/sqlch127.js +132 -0
- package/dist/lint/post-synth/sqlpg101.js +25 -0
- package/dist/lint/post-synth/sqlpg102.js +35 -0
- package/dist/lint/post-synth/sqlpg103.js +23 -0
- package/dist/lint/post-synth/sqlpg104.js +23 -0
- package/dist/lint/post-synth/sqlpg105.js +22 -0
- package/dist/lint/post-synth/sqlpg106.js +22 -0
- package/dist/lint/post-synth/sqlpg107.js +22 -0
- package/dist/lint/post-synth/sqlpg108.js +28 -0
- package/dist/lint/post-synth/sqlpg109.js +39 -0
- package/dist/lint/post-synth/sqlpg110.js +38 -0
- package/dist/lint/post-synth/sqlpg111.js +23 -0
- package/dist/lint/post-synth/sqlpg112.js +25 -0
- package/dist/lint/post-synth/sqlpg113.js +29 -0
- package/dist/lint/post-synth/sqlpg114.js +39 -0
- package/dist/lint/post-synth/sqlpg115.js +48 -0
- package/dist/lint/post-synth/sqlpg116.js +26 -0
- package/dist/lint/post-synth/sqlpg117.js +24 -0
- package/dist/lint/post-synth/sqlpg118.js +22 -0
- package/dist/lint/post-synth/sqlpg119.js +41 -0
- package/dist/lint/post-synth/sqlpg120.js +83 -0
- package/dist/lint/post-synth/sqlpg121.js +33 -0
- package/dist/lint/post-synth/sqlpg122.js +27 -0
- package/dist/lint/post-synth/sqlpg123.js +69 -0
- package/dist/lint/post-synth/sqlpg124.js +50 -0
- package/dist/lint/post-synth/sqlpg125.js +41 -0
- package/dist/lint/post-synth/sqlpg126.js +38 -0
- package/dist/lint/rules/index.js +12 -0
- package/dist/lint/rules/postgres-templates.js +27 -0
- package/dist/lint/rules/sqlch001.js +79 -0
- package/dist/lint/rules/sqlch002.js +124 -0
- package/dist/lint/rules/sqlch003.js +69 -0
- package/dist/lint/rules/sqlch004.js +83 -0
- package/dist/lint/rules/sqlpg001.js +68 -0
- package/dist/lint/rules/sqlpg002.js +69 -0
- package/dist/lint/rules/sqlpg003.js +58 -0
- package/dist/lint/rules/sqlpg004.js +77 -0
- package/dist/lint/rules/sqlpg005.js +86 -0
- package/dist/lint/rules/templates.js +25 -0
- package/dist/lint/rules/typed-names.js +132 -0
- package/dist/live-dialect.js +32 -0
- package/dist/lsp/catalog.js +44 -0
- package/dist/lsp/completions.js +136 -0
- package/dist/lsp/hover.js +115 -0
- package/dist/lsp/postgres-catalog.js +176 -0
- package/dist/lsp/postgres-template.js +171 -0
- package/dist/lsp/postgres.js +266 -0
- package/dist/lsp/refs.js +60 -0
- package/dist/lsp/registry.d.ts.map +1 -1
- package/dist/lsp/registry.js +19 -0
- package/dist/lsp/template.js +143 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +293 -0
- package/dist/mcp/postgres.js +117 -0
- package/dist/migration-statements.js +243 -0
- package/dist/op/activities/clickhouse-apply.js +83 -0
- package/dist/op/activities/clickhouse-emulator.js +53 -0
- package/dist/op/activities/clickhouse-rebuild.js +119 -0
- package/dist/op/activities/index.js +26 -0
- package/dist/op/activities/postgres-apply.js +126 -0
- package/dist/op/activities/postgres-emulator.js +70 -0
- package/dist/op/activities/postgres-migration.js +156 -0
- package/dist/op/activities/sql-exec.js +57 -0
- package/dist/op/activity-contracts.js +103 -0
- package/dist/package-cli.js +14 -0
- package/dist/plugin.js +259 -0
- package/dist/postgres/access/acl.js +344 -0
- package/dist/postgres/access/changes.js +30 -0
- package/dist/postgres/access/import.js +61 -0
- package/dist/postgres/access/live.js +96 -0
- package/dist/postgres/apply/apply.js +557 -0
- package/dist/postgres/apply/statements.js +882 -0
- package/dist/postgres/catalog-types.js +18 -0
- package/dist/postgres/container.js +58 -0
- package/dist/postgres/entities.js +1390 -0
- package/dist/postgres/entity-types.js +28 -0
- package/dist/postgres/import/generator.js +283 -0
- package/dist/postgres/import/ir.js +53 -0
- package/dist/postgres/import/live-export.js +83 -0
- package/dist/postgres/keywords.js +32 -0
- package/dist/postgres/live/bind.js +130 -0
- package/dist/postgres/live/catalog-kinds.js +301 -0
- package/dist/postgres/live/catalog.js +644 -0
- package/dist/postgres/live/client.js +151 -0
- package/dist/postgres/live/describe-resources.js +135 -0
- package/dist/postgres/live/undeclarable.js +111 -0
- package/dist/postgres/migrate/backfill.js +170 -0
- package/dist/postgres/migrate/batches.js +122 -0
- package/dist/postgres/migrate/carry.js +518 -0
- package/dist/postgres/migrate/handoff.js +59 -0
- package/dist/postgres/migrate/names.js +61 -0
- package/dist/postgres/migrate/observe.js +360 -0
- package/dist/postgres/migrate/op.js +178 -0
- package/dist/postgres/migrate/receipts.js +117 -0
- package/dist/postgres/migrate/replication.js +106 -0
- package/dist/postgres/migrate/steps.js +514 -0
- package/dist/postgres/overlays/columns.js +31 -0
- package/dist/postgres/overlays/constraints.js +52 -0
- package/dist/postgres/overlays/extension-objects.js +88 -0
- package/dist/postgres/overlays/indexes.js +25 -0
- package/dist/postgres/overlays/kinds.js +2 -0
- package/dist/postgres/overlays/partitioning.js +23 -0
- package/dist/postgres/overlays/relations.js +25 -0
- package/dist/postgres/overlays/storage.js +65 -0
- package/dist/postgres/overlays/type-ddl.js +36 -0
- package/dist/postgres/overlays/types.js +74 -0
- package/dist/postgres/parser.js +1824 -0
- package/dist/postgres/plan/commands.js +137 -0
- package/dist/postgres/plan/deep.js +324 -0
- package/dist/postgres/plan/diff.js +499 -0
- package/dist/postgres/plan/disruption.js +64 -0
- package/dist/postgres/plan/normalize.js +889 -0
- package/dist/postgres/plan/report.js +21 -0
- package/dist/postgres/plan/rules.js +115 -0
- package/dist/postgres/plan/schema.js +105 -0
- package/dist/postgres/plan/server-normalize.js +166 -0
- package/dist/postgres/providers/aurora.js +44 -0
- package/dist/postgres/providers/azure.js +53 -0
- package/dist/postgres/providers/cloud-sql.js +41 -0
- package/dist/postgres/providers/index.js +73 -0
- package/dist/postgres/providers/neon.js +44 -0
- package/dist/postgres/providers/rds.js +47 -0
- package/dist/postgres/providers/supabase.js +45 -0
- package/dist/postgres/providers/types.js +21 -0
- package/dist/postgres/row-types.js +107 -0
- package/dist/postgres/testing/fake-client.js +34 -0
- package/dist/postgres/testing/migration.js +48 -0
- package/dist/postgres/testing/roundtrip-schema.js +64 -0
- package/dist/postgres/testing/server.js +47 -0
- package/dist/postgres/testing/writable-server.js +160 -0
- package/dist/postgres/tokens.js +41 -0
- package/dist/postgres.js +15 -0
- package/dist/read/clickhouse.js +82 -0
- package/dist/read/objects.js +44 -0
- package/dist/read/postgres.js +104 -0
- package/dist/read.js +52 -0
- package/dist/receipts.js +207 -0
- package/dist/serializer.js +172 -0
- package/dist/skill-defs.js +93 -0
- package/dist/spec/catalog.js +191 -0
- package/dist/spec/fetch.d.ts.map +1 -1
- package/dist/spec/fetch.js +90 -0
- package/dist/spec/pin.js +42 -0
- package/dist/spec/postgres-catalog.js +171 -0
- package/dist/spec/postgres-fetch.js +91 -0
- package/dist/spec/postgres-pin.js +82 -0
- package/dist/sql-files.js +206 -0
- package/dist/sql-types-command.js +97 -0
- package/dist/sql-types.js +323 -0
- package/dist/token-source.js +156 -0
- package/dist/validate-cli.js +5 -0
- package/dist/validate.js +151 -0
- package/dist/workspace-outputs.js +191 -0
- package/package.json +7 -7
- package/src/codegen/package.ts +2 -2
- package/src/lint/rules/typescript-7.test.ts +2 -1
- package/src/lsp/catalog.ts +2 -2
- package/src/lsp/postgres-catalog.ts +2 -2
- package/src/lsp/registry.ts +2 -1
- package/src/mcp/index.ts +4 -2
- package/src/spec/fetch.ts +2 -2
- package/src/spec/postgres-fetch.ts +2 -2
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Postgres applier (#3280): make a server hold what a build declares, for
|
|
3
|
+
* every change the classifier (`../plan/rules.ts`) does not refuse, and
|
|
4
|
+
* refuse the rest.
|
|
5
|
+
*
|
|
6
|
+
* Per declared object, exactly one verdict (lexicon-authoring/applying.mdx):
|
|
7
|
+
*
|
|
8
|
+
* - APPLIED `created`: absent on the server; created from its declared
|
|
9
|
+
* statements, with chant's ownership marker set by `COMMENT ON`.
|
|
10
|
+
* - APPLIED `updated`: present, and the statements for its changes ran and
|
|
11
|
+
* committed.
|
|
12
|
+
* - APPLIED `unchanged`: present, nothing to change, the marker in place.
|
|
13
|
+
* - NOT-ATTEMPTED `unsupported-kind`: a change only expand and contract
|
|
14
|
+
* makes (a column rename, a type change across kinds, a NOT NULL column
|
|
15
|
+
* with no default), or one with no in-place statement. Nothing is sent for
|
|
16
|
+
* the object; the detail names each rule, the restriction and the
|
|
17
|
+
* expand-and-contract migration Op (#3281).
|
|
18
|
+
* - NOT-ATTEMPTED `filtered`: the change drops a column, which destroys its
|
|
19
|
+
* data, and the apply was not allowed to delete; or another tool keeps the
|
|
20
|
+
* object (an ORM's revision table, a managed provider's schema).
|
|
21
|
+
* - NOT-ATTEMPTED `dependency-failed`: an object it references is not on the
|
|
22
|
+
* server, or its statements ran in a transaction that rolled back because
|
|
23
|
+
* another object's statement failed. The detail names that statement.
|
|
24
|
+
* - NOT-ATTEMPTED with the binding's reason: no server to apply to.
|
|
25
|
+
*
|
|
26
|
+
* A statement the server refuses fails its object, which was attempted and
|
|
27
|
+
* did not converge. Its transaction rolls back, the rest of the apply goes on,
|
|
28
|
+
* and the applier then throws {@link PostgresApplyError}, which carries the
|
|
29
|
+
* outcome so far.
|
|
30
|
+
*
|
|
31
|
+
* ## Transactions
|
|
32
|
+
*
|
|
33
|
+
* Statements run in the build's order, each object's in the order its
|
|
34
|
+
* changes need, and are grouped:
|
|
35
|
+
*
|
|
36
|
+
* 1. Consecutive statements that change only the catalog (create, metadata,
|
|
37
|
+
* drop) share one transaction, across objects, so they land together or
|
|
38
|
+
* not at all.
|
|
39
|
+
* 2. A statement that reads or rewrites a table's rows (an `ACCESS
|
|
40
|
+
* EXCLUSIVE` rewrite or scan, a validation, a non-concurrent index build)
|
|
41
|
+
* runs in a transaction of its own object, so a long scan holds locks on
|
|
42
|
+
* that one table and the changes before it have committed.
|
|
43
|
+
* 3. A statement that refuses a transaction block (`CREATE INDEX
|
|
44
|
+
* CONCURRENTLY`, `DROP INDEX CONCURRENTLY`) runs alone, outside any
|
|
45
|
+
* transaction, and so does `ALTER TYPE ... ADD VALUE`, whose new label
|
|
46
|
+
* cannot be used in the transaction that adds it.
|
|
47
|
+
*
|
|
48
|
+
* ## Timeouts
|
|
49
|
+
*
|
|
50
|
+
* Every statement runs with its own `lock_timeout` and `statement_timeout`
|
|
51
|
+
* (`SET LOCAL` in a transaction, `SET` before one outside), so a statement
|
|
52
|
+
* blocked behind another session's lock fails fast instead of queueing every
|
|
53
|
+
* query behind it. Defaults: a lock is waited for 5 s; a catalog-only
|
|
54
|
+
* statement may run 60 s; a statement that reads or rewrites rows has no
|
|
55
|
+
* limit. `sql.profiles.<env>` sets them (`lockTimeoutMs`,
|
|
56
|
+
* `statementTimeoutMs`, `scanTimeoutMs`), and the outcome records the values
|
|
57
|
+
* each statement ran with.
|
|
58
|
+
*
|
|
59
|
+
* ## Prune
|
|
60
|
+
*
|
|
61
|
+
* When asked for, prune drops the objects in the declared schemas that the
|
|
62
|
+
* build no longer declares and whose comment carries this project's marker
|
|
63
|
+
* (stack and env): views first, schemas last, never `CASCADE`, so an object
|
|
64
|
+
* something else still depends on is kept and reported. An object without
|
|
65
|
+
* the marker is not chant's and is never touched; one another tool keeps is
|
|
66
|
+
* never read as an orphan at all.
|
|
67
|
+
*/
|
|
68
|
+
import { SqlApplyError, dependencyFailedDetail, missingDependencies, readBuildObjects } from "../../core/apply.js";
|
|
69
|
+
import { carriesMarker, isChantManaged } from "../../core/ownership.js";
|
|
70
|
+
import { DEFAULT_LOCK_TIMEOUT_MS, PostgresQueryError, withCatalogLockTimeout } from "../live/client.js";
|
|
71
|
+
import { liveProps } from "../plan/deep.js";
|
|
72
|
+
import { diffObject, qualifiedKey } from "../plan/schema.js";
|
|
73
|
+
import { POSTGRES_LATEST_MAJOR } from "../../spec/postgres-pin.js";
|
|
74
|
+
import { matchPgObjects } from "../plan/diff.js";
|
|
75
|
+
import { planAgainstClient } from "../plan/commands.js";
|
|
76
|
+
import { quoteIdent } from "../keywords.js";
|
|
77
|
+
import { planPgStatements, scansRows } from "./statements.js";
|
|
78
|
+
import { ACCESS_KINDS, ACCESS_MANAGED_KINDS } from "../plan/normalize.js";
|
|
79
|
+
import { declaredAccess, diffAccess, targetKey } from "../access/acl.js";
|
|
80
|
+
import { readLiveAccess } from "../access/live.js";
|
|
81
|
+
export const DEFAULT_POSTGRES_APPLY_TIMEOUTS = { lockTimeoutMs: DEFAULT_LOCK_TIMEOUT_MS, statementTimeoutMs: 60000, scanTimeoutMs: 0 };
|
|
82
|
+
/** An apply in which the server refused a statement. The outcome says what did and did not happen. */
|
|
83
|
+
export class PostgresApplyError extends SqlApplyError {
|
|
84
|
+
constructor(outcome) {
|
|
85
|
+
super("Postgres", outcome);
|
|
86
|
+
this.name = "PostgresApplyError";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The name an apply result gives an object: `schema.name`, or a schema's or
|
|
91
|
+
* extension's own name; a routine's with its parameter types
|
|
92
|
+
* (`app.touch()`), a trigger's with its table (`touch ON app.users`).
|
|
93
|
+
*/
|
|
94
|
+
export const refName = (o) => o.kind === "trigger" || o.kind === "policy"
|
|
95
|
+
? `${o.name}${o.signature ?? ""}`
|
|
96
|
+
: o.kind === "schema" || o.kind === "extension" || o.kind === "role" || o.schema === undefined
|
|
97
|
+
? o.name
|
|
98
|
+
: `${o.schema}.${o.name}${o.kind === "function" || o.kind === "procedure" || o.kind === "aggregate" ? (o.signature ?? "") : ""}`;
|
|
99
|
+
/**
|
|
100
|
+
* The objects a Postgres `chant build` output declares, in creation order,
|
|
101
|
+
* each parsed with its tag and in canonical form. Takes the sql lexicon's
|
|
102
|
+
* primary output, or a multi-lexicon output holding it under `sql`.
|
|
103
|
+
*/
|
|
104
|
+
export function declaredObjects(json, defaultSchema = "public") {
|
|
105
|
+
return readBuildObjects(json, "postgres", (o) => {
|
|
106
|
+
const canonical = { ...diffObject(o.type, o.ddl, defaultSchema), exportName: o.export };
|
|
107
|
+
return {
|
|
108
|
+
exportName: o.export,
|
|
109
|
+
type: o.type,
|
|
110
|
+
name: refName(canonical),
|
|
111
|
+
key: qualifiedKey(canonical),
|
|
112
|
+
ddl: o.ddl,
|
|
113
|
+
props: liveProps({ type: o.type, statement: o.ddl }),
|
|
114
|
+
canonical,
|
|
115
|
+
dependsOn: o.dependsOn,
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/** The plan axis: one ref per declared object, by its type and its name on the server. */
|
|
120
|
+
export const planRefs = (declared) => declared.map((o) => ({ kind: o.type, name: o.name }));
|
|
121
|
+
const SQLSTATE_LOCK_NOT_AVAILABLE = "55P03";
|
|
122
|
+
const SQLSTATE_QUERY_CANCELED = "57014";
|
|
123
|
+
const SQLSTATE_DEPENDENT_OBJECTS = "2BP01";
|
|
124
|
+
const SQLSTATE_UNDEFINED = new Set(["42P01", "42704", "3F000"]);
|
|
125
|
+
const ms = (n) => `'${Math.max(0, Math.floor(n))}ms'`;
|
|
126
|
+
/** A server error as a detail: its message, and what the timeouts mean when one fired. */
|
|
127
|
+
function errorText(err, t) {
|
|
128
|
+
const message = err instanceof Error ? err.message.split("\n")[0] : String(err);
|
|
129
|
+
const code = err instanceof PostgresQueryError ? err.code : undefined;
|
|
130
|
+
if (code === SQLSTATE_LOCK_NOT_AVAILABLE)
|
|
131
|
+
return `${message} (lock_timeout ${t.lockTimeoutMs}ms: another session holds a lock this statement needs; SQLSTATE 55P03)`;
|
|
132
|
+
if (code === SQLSTATE_QUERY_CANCELED)
|
|
133
|
+
return `${message} (statement_timeout ${t.statementTimeoutMs}ms; SQLSTATE 57014)`;
|
|
134
|
+
return code ? `${message} (SQLSTATE ${code})` : message;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Apply the declarations to the server `client` is connected to. Returns
|
|
138
|
+
* every declared object in exactly one of applied or not attempted, and every
|
|
139
|
+
* pruned orphan; throws {@link PostgresApplyError} when the server refused a
|
|
140
|
+
* statement. The client is left open; the caller ends it.
|
|
141
|
+
*/
|
|
142
|
+
export async function applyPostgres(client, target, declared, opts = {}) {
|
|
143
|
+
const log = opts.log ?? (() => undefined);
|
|
144
|
+
const timeouts = { ...DEFAULT_POSTGRES_APPLY_TIMEOUTS, ...target.timeouts, ...opts.timeouts };
|
|
145
|
+
const outcome = {
|
|
146
|
+
target: target.endpoint.url,
|
|
147
|
+
source: target.source,
|
|
148
|
+
applied: [],
|
|
149
|
+
pruned: [],
|
|
150
|
+
notAttempted: [],
|
|
151
|
+
failed: [],
|
|
152
|
+
timeouts,
|
|
153
|
+
statements: [],
|
|
154
|
+
transactions: [],
|
|
155
|
+
};
|
|
156
|
+
// ── Plan: the same diff `chant sql plan` shows. ──
|
|
157
|
+
// The catalog reads wait under lockTimeoutMs, as a plan's do (#3726): one blocked behind ACCESS EXCLUSIVE fails naming the holder instead of hanging.
|
|
158
|
+
const reader = await withCatalogLockTimeout(client, timeouts.lockTimeoutMs);
|
|
159
|
+
const build = declared.map((o) => ({ key: o.exportName, canonical: o.canonical }));
|
|
160
|
+
const plan = await planAgainstClient(reader, target, build, {
|
|
161
|
+
...(opts.major !== undefined ? { major: opts.major } : {}),
|
|
162
|
+
...(opts.readLive ? { readLive: opts.readLive } : {}),
|
|
163
|
+
...(opts.serverNormalize ? { serverNormalize: opts.serverNormalize } : {}),
|
|
164
|
+
});
|
|
165
|
+
const normalizedByKey = new Map(plan.declared.map((o) => [o.key, o.canonical]));
|
|
166
|
+
const liveRawByKey = new Map(plan.liveObjects.map((o) => [qualifiedKey(liveCanonicalAddress(o)), o]));
|
|
167
|
+
const managed = target.access === true;
|
|
168
|
+
// Access declarations a profile does not manage are not applied; grants and default privileges run after every object, as access.
|
|
169
|
+
for (const o of declared) {
|
|
170
|
+
if (!managed && ACCESS_MANAGED_KINDS.has(o.canonical.kind)) {
|
|
171
|
+
outcome.notAttempted.push({
|
|
172
|
+
kind: o.type,
|
|
173
|
+
name: o.name,
|
|
174
|
+
reason: "filtered",
|
|
175
|
+
detail: `access is not managed in this environment: set sql.profiles.<env>.access to plan and apply policies, roles, grants and row-level security`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const objects = declared
|
|
180
|
+
.filter((o) => !ACCESS_KINDS.has(o.canonical.kind) && (managed || !ACCESS_MANAGED_KINDS.has(o.canonical.kind)))
|
|
181
|
+
.map((original) => ({ ...original, canonical: { ...original.canonical, ...normalizedByKey.get(original.key) } }));
|
|
182
|
+
const matchedLive = new Set();
|
|
183
|
+
for (const m of matchPgObjects(plan.live, plan.declared))
|
|
184
|
+
if (m.kind === "matched")
|
|
185
|
+
matchedLive.add(m.after.key);
|
|
186
|
+
// An extension's own comment stays under the trailer when the declaration sets none.
|
|
187
|
+
const extensionComments = await extensionDefaults(reader, objects.filter((o) => o.canonical.kind === "extension" && !matchedLive.has(o.key)).map((o) => o.canonical.name));
|
|
188
|
+
// ── Each object's statements, or its verdict when it has none to send. ──
|
|
189
|
+
const statements = planPgStatements({
|
|
190
|
+
declared: objects,
|
|
191
|
+
changes: plan.diff.changes,
|
|
192
|
+
current: plan.live,
|
|
193
|
+
major: plan.major ?? POSTGRES_LATEST_MAJOR,
|
|
194
|
+
allowDestructive: opts.prune === true,
|
|
195
|
+
carriesMarker: (_live, key) => carriesMarker(liveRawByKey.get(key)?.comment, opts.marker),
|
|
196
|
+
extensionComment: (name) => extensionComments.get(name),
|
|
197
|
+
access: managed,
|
|
198
|
+
...(opts.marker ? { marker: opts.marker } : {}),
|
|
199
|
+
});
|
|
200
|
+
const units = [];
|
|
201
|
+
const onServer = new Set();
|
|
202
|
+
const exportNames = new Set(declared.map((o) => o.exportName));
|
|
203
|
+
for (const entry of statements.objects) {
|
|
204
|
+
const obj = entry.obj;
|
|
205
|
+
const ref = { kind: obj.type, name: obj.name };
|
|
206
|
+
const liveRaw = entry.live ? liveRawByKey.get(qualifiedKey(entry.live)) : undefined;
|
|
207
|
+
switch (entry.verdict) {
|
|
208
|
+
case "foreign":
|
|
209
|
+
case "withheld":
|
|
210
|
+
case "unsupported":
|
|
211
|
+
onServer.add(obj.exportName);
|
|
212
|
+
outcome.notAttempted.push({ ...ref, reason: entry.verdict === "unsupported" ? "unsupported-kind" : "filtered", detail: entry.detail });
|
|
213
|
+
continue;
|
|
214
|
+
case "refused":
|
|
215
|
+
if (entry.live)
|
|
216
|
+
onServer.add(obj.exportName);
|
|
217
|
+
outcome.notAttempted.push({ ...ref, reason: "unsupported-kind", detail: entry.detail });
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (entry.steps.length === 0) {
|
|
221
|
+
onServer.add(obj.exportName);
|
|
222
|
+
outcome.applied.push({ ...ref, action: "unchanged", ...(liveRaw ? { physicalId: liveRaw.oid } : {}), statements: [] });
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
units.push({ obj, ref, created: !entry.live, ...(liveRaw ? { physicalId: liveRaw.oid } : {}), steps: entry.steps, ran: [], pending: [], done: 0, state: "planned" });
|
|
226
|
+
}
|
|
227
|
+
// ── Run them, grouped into transactions. ──
|
|
228
|
+
// Statements use the default schema for a bare name, as the declarations do; the catalog was read with an empty search_path.
|
|
229
|
+
await client.query("SELECT pg_catalog.set_config('search_path', $1, false)", [quoteIdent(target.defaultSchema)]);
|
|
230
|
+
let tx;
|
|
231
|
+
const finish = (u) => {
|
|
232
|
+
if (u.state !== "planned" || u.done < u.steps.length || u.pending.length > 0)
|
|
233
|
+
return;
|
|
234
|
+
u.state = "applied";
|
|
235
|
+
onServer.add(u.obj.exportName);
|
|
236
|
+
outcome.applied.push({ ...u.ref, action: u.created ? "created" : "updated", ...(u.physicalId ? { physicalId: u.physicalId } : {}), statements: u.ran });
|
|
237
|
+
};
|
|
238
|
+
const fail = (u, error) => {
|
|
239
|
+
u.state = "failed";
|
|
240
|
+
if (!u.created)
|
|
241
|
+
onServer.add(u.obj.exportName);
|
|
242
|
+
outcome.failed.push({ ...u.ref, error, statements: u.ran });
|
|
243
|
+
};
|
|
244
|
+
const commit = async () => {
|
|
245
|
+
if (!tx)
|
|
246
|
+
return;
|
|
247
|
+
const t = tx;
|
|
248
|
+
tx = undefined;
|
|
249
|
+
try {
|
|
250
|
+
log("COMMIT");
|
|
251
|
+
await client.query("COMMIT");
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
await rollBack(t, "COMMIT", err instanceof Error ? err.message : String(err), undefined);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
outcome.transactions.push({ objects: [...t.members].map((u) => u.ref.name), result: "committed" });
|
|
258
|
+
for (const u of t.members) {
|
|
259
|
+
u.ran.push(...u.pending);
|
|
260
|
+
u.pending = [];
|
|
261
|
+
finish(u);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
/** Roll the open transaction back: the failing object fails, the others in it are rolled back with it. */
|
|
265
|
+
const rollBack = async (t, sql, error, failing) => {
|
|
266
|
+
log("ROLLBACK");
|
|
267
|
+
await client.query("ROLLBACK").catch(() => undefined);
|
|
268
|
+
outcome.transactions.push({ objects: [...t.members].map((u) => u.ref.name), result: "rolled-back", failedAt: { sql, error } });
|
|
269
|
+
for (const u of t.members) {
|
|
270
|
+
u.pending = [];
|
|
271
|
+
if (u === failing)
|
|
272
|
+
continue;
|
|
273
|
+
if (u.state !== "planned" && u.state !== "applied")
|
|
274
|
+
continue;
|
|
275
|
+
if (u.ran.length > 0)
|
|
276
|
+
fail(u, `rolled back with the transaction that failed at ${sql}: ${error}`);
|
|
277
|
+
else {
|
|
278
|
+
u.state = "rolled-back";
|
|
279
|
+
outcome.notAttempted.push({
|
|
280
|
+
...u.ref,
|
|
281
|
+
reason: "dependency-failed",
|
|
282
|
+
detail: `its statements ran in a transaction that rolled back, so nothing of it was kept: the transaction failed at ${sql}: ${error}`,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (failing)
|
|
287
|
+
fail(failing, error);
|
|
288
|
+
};
|
|
289
|
+
for (const u of units) {
|
|
290
|
+
for (const s of u.steps) {
|
|
291
|
+
opts.signal?.throwIfAborted();
|
|
292
|
+
if (u.state !== "planned")
|
|
293
|
+
break;
|
|
294
|
+
const heavy = scansRows(s.class);
|
|
295
|
+
const t = { lockTimeoutMs: timeouts.lockTimeoutMs, statementTimeoutMs: heavy ? timeouts.scanTimeoutMs : timeouts.statementTimeoutMs };
|
|
296
|
+
// Close the open transaction when this statement cannot join it.
|
|
297
|
+
if (tx && (!s.transactional || tx.heavy !== heavy || (heavy && tx.owner !== u)))
|
|
298
|
+
await commit();
|
|
299
|
+
if (u.state !== "planned")
|
|
300
|
+
break;
|
|
301
|
+
if (u.done === 0 && !tx?.members.has(u)) {
|
|
302
|
+
// An object whose statements all ran in the open transaction is on the server as far as this transaction sees.
|
|
303
|
+
const seen = new Set([...onServer, ...[...(tx?.members ?? [])].filter((m) => m.state === "planned" && m.done === m.steps.length).map((m) => m.obj.exportName)]);
|
|
304
|
+
const missing = missingDependencies(u.obj.exportName, u.obj.dependsOn, exportNames, seen);
|
|
305
|
+
if (missing.length > 0) {
|
|
306
|
+
u.state = "skipped";
|
|
307
|
+
if (!u.created)
|
|
308
|
+
onServer.add(u.obj.exportName);
|
|
309
|
+
outcome.notAttempted.push({ ...u.ref, reason: "dependency-failed", detail: dependencyFailedDetail(missing) });
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (!s.transactional) {
|
|
314
|
+
const record = { object: u.ref.name, sql: s.sql, class: s.class, ...t };
|
|
315
|
+
try {
|
|
316
|
+
for (const set of [`SET lock_timeout = ${ms(t.lockTimeoutMs)}`, `SET statement_timeout = ${ms(t.statementTimeoutMs)}`]) {
|
|
317
|
+
log(set);
|
|
318
|
+
await client.query(set);
|
|
319
|
+
}
|
|
320
|
+
log(s.sql);
|
|
321
|
+
outcome.statements.push(record);
|
|
322
|
+
await client.query(s.sql);
|
|
323
|
+
u.ran.push(s.sql);
|
|
324
|
+
u.done++;
|
|
325
|
+
finish(u);
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
if (opts.signal?.aborted)
|
|
329
|
+
throw err;
|
|
330
|
+
if (s.buildsIndex)
|
|
331
|
+
await dropInvalidIndex(client, s.buildsIndex, log);
|
|
332
|
+
fail(u, `${errorText(err, t)}, at ${s.sql}`);
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (!tx) {
|
|
337
|
+
log("BEGIN");
|
|
338
|
+
await client.query("BEGIN");
|
|
339
|
+
tx = { number: outcome.transactions.length, heavy, ...(heavy ? { owner: u } : {}), members: new Set(), statements: [] };
|
|
340
|
+
}
|
|
341
|
+
tx.members.add(u);
|
|
342
|
+
const record = { object: u.ref.name, sql: s.sql, class: s.class, ...t, transaction: tx.number };
|
|
343
|
+
try {
|
|
344
|
+
for (const set of [`SET LOCAL lock_timeout = ${ms(t.lockTimeoutMs)}`, `SET LOCAL statement_timeout = ${ms(t.statementTimeoutMs)}`]) {
|
|
345
|
+
log(set);
|
|
346
|
+
await client.query(set);
|
|
347
|
+
}
|
|
348
|
+
log(s.sql);
|
|
349
|
+
outcome.statements.push(record);
|
|
350
|
+
await client.query(s.sql);
|
|
351
|
+
u.pending.push(s.sql);
|
|
352
|
+
u.done++;
|
|
353
|
+
}
|
|
354
|
+
catch (err) {
|
|
355
|
+
if (opts.signal?.aborted) {
|
|
356
|
+
await client.query("ROLLBACK").catch(() => undefined);
|
|
357
|
+
throw err;
|
|
358
|
+
}
|
|
359
|
+
const failed = tx;
|
|
360
|
+
tx = undefined;
|
|
361
|
+
await rollBack(failed, s.sql, errorText(err, t), u);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
await commit();
|
|
366
|
+
if (managed)
|
|
367
|
+
await applyAccess(client, reader, declared, onServer, exportNames, { ...opts, major: plan.major ?? opts.major ?? POSTGRES_LATEST_MAJOR, ...(plan.scope ? { schemas: plan.scope } : {}) }, timeouts, outcome, log);
|
|
368
|
+
if (opts.prune)
|
|
369
|
+
await prune(client, statements.drops, liveRawByKey, opts, timeouts, outcome, log);
|
|
370
|
+
if (outcome.failed.length > 0)
|
|
371
|
+
throw new PostgresApplyError(outcome);
|
|
372
|
+
return outcome;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Access, after every object (#3681): the privileges and default privileges
|
|
376
|
+
* the declarations add up to, against what the server holds now that the
|
|
377
|
+
* objects exist, made in one transaction. A grant or default-privileges
|
|
378
|
+
* declaration is `updated` when a statement ran for it and `unchanged`
|
|
379
|
+
* otherwise; a privilege nothing declares is revoked under the object's name.
|
|
380
|
+
*/
|
|
381
|
+
async function applyAccess(client, reader, declared, onServer, exportNames, opts, timeouts, outcome, log) {
|
|
382
|
+
const decls = declared.filter((o) => ACCESS_KINDS.has(o.canonical.kind));
|
|
383
|
+
const ref = (o) => ({ kind: o.type, name: o.canonical.name });
|
|
384
|
+
const ready = [];
|
|
385
|
+
for (const o of decls) {
|
|
386
|
+
const missing = missingDependencies(o.exportName, o.dependsOn, exportNames, onServer);
|
|
387
|
+
if (missing.length > 0)
|
|
388
|
+
outcome.notAttempted.push({ ...ref(o), reason: "dependency-failed", detail: dependencyFailedDetail(missing) });
|
|
389
|
+
else
|
|
390
|
+
ready.push(o);
|
|
391
|
+
}
|
|
392
|
+
const skipped = new Set(decls.filter((o) => !ready.includes(o)).map((o) => o.exportName));
|
|
393
|
+
const [me] = await reader.query("SELECT current_user AS self");
|
|
394
|
+
const want = declaredAccess(declared.filter((o) => !skipped.has(o.exportName)).map((o) => ({ key: o.exportName, canonical: o.canonical })), { major: opts.major, ...(me?.self ? { self: me.self } : {}) });
|
|
395
|
+
const have = await readLiveAccess(reader, want.targets, {
|
|
396
|
+
...(opts.schemas ? { schemas: opts.schemas } : {}),
|
|
397
|
+
forRoles: want.forRoles,
|
|
398
|
+
declaredDefaults: want.targets.filter((t) => t.kind === "default"),
|
|
399
|
+
});
|
|
400
|
+
// A target that is not on the server (its object failed) is left for the next apply.
|
|
401
|
+
const changes = diffAccess(have.state, want.state, want.exportsOf).filter((c) => c.target.kind === "default" || have.present.has(targetKey(c.target.kind === "column" ? { kind: "table", name: c.target.name } : c.target)));
|
|
402
|
+
const ranFor = new Map();
|
|
403
|
+
if (changes.length > 0) {
|
|
404
|
+
const t = { lockTimeoutMs: timeouts.lockTimeoutMs, statementTimeoutMs: timeouts.statementTimeoutMs };
|
|
405
|
+
const number = outcome.transactions.length;
|
|
406
|
+
const members = new Set();
|
|
407
|
+
log("BEGIN");
|
|
408
|
+
await client.query("BEGIN");
|
|
409
|
+
try {
|
|
410
|
+
for (const set of [`SET LOCAL lock_timeout = ${ms(t.lockTimeoutMs)}`, `SET LOCAL statement_timeout = ${ms(t.statementTimeoutMs)}`]) {
|
|
411
|
+
log(set);
|
|
412
|
+
await client.query(set);
|
|
413
|
+
}
|
|
414
|
+
for (const c of changes) {
|
|
415
|
+
const object = c.exports[0] !== undefined ? (declared.find((o) => o.exportName === c.exports[0])?.canonical.name ?? c.change.object) : c.change.object.slice(4);
|
|
416
|
+
members.add(object);
|
|
417
|
+
for (const sql of c.sql) {
|
|
418
|
+
opts.signal?.throwIfAborted();
|
|
419
|
+
log(sql);
|
|
420
|
+
outcome.statements.push({ object, sql, class: c.change.class, ...t, transaction: number });
|
|
421
|
+
await client.query(sql);
|
|
422
|
+
for (const e of c.exports)
|
|
423
|
+
ranFor.set(e, [...(ranFor.get(e) ?? []), sql]);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
log("COMMIT");
|
|
427
|
+
await client.query("COMMIT");
|
|
428
|
+
outcome.transactions.push({ objects: [...members], result: "committed" });
|
|
429
|
+
}
|
|
430
|
+
catch (err) {
|
|
431
|
+
if (opts.signal?.aborted) {
|
|
432
|
+
await client.query("ROLLBACK").catch(() => undefined);
|
|
433
|
+
throw err;
|
|
434
|
+
}
|
|
435
|
+
const error = errorText(err, t);
|
|
436
|
+
const failedAt = outcome.statements[outcome.statements.length - 1]?.sql ?? "BEGIN";
|
|
437
|
+
log("ROLLBACK");
|
|
438
|
+
await client.query("ROLLBACK").catch(() => undefined);
|
|
439
|
+
outcome.transactions.push({ objects: [...members], result: "rolled-back", failedAt: { sql: failedAt, error } });
|
|
440
|
+
for (const o of ready) {
|
|
441
|
+
if (changes.some((c) => c.exports.includes(o.exportName)))
|
|
442
|
+
outcome.failed.push({ ...ref(o), error: `${error}, in the access transaction at ${failedAt}`, statements: [] });
|
|
443
|
+
else
|
|
444
|
+
outcome.applied.push({ ...ref(o), action: "unchanged", statements: [] });
|
|
445
|
+
}
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
for (const o of ready) {
|
|
450
|
+
const ran = ranFor.get(o.exportName) ?? [];
|
|
451
|
+
outcome.applied.push({ ...ref(o), action: ran.length > 0 ? "updated" : "unchanged", statements: ran });
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/** The address a live object is keyed by in the diff. */
|
|
455
|
+
function liveCanonicalAddress(o) {
|
|
456
|
+
const kind = {
|
|
457
|
+
"Postgres::Schema": "schema",
|
|
458
|
+
"Postgres::Table": "table",
|
|
459
|
+
"Postgres::Index": "index",
|
|
460
|
+
"Postgres::View": "view",
|
|
461
|
+
"Postgres::MaterializedView": "materializedView",
|
|
462
|
+
"Postgres::Sequence": "sequence",
|
|
463
|
+
"Postgres::Enum": "enum",
|
|
464
|
+
"Postgres::CompositeType": "composite",
|
|
465
|
+
"Postgres::RangeType": "range",
|
|
466
|
+
"Postgres::Domain": "domain",
|
|
467
|
+
"Postgres::Aggregate": "aggregate",
|
|
468
|
+
"Postgres::Collation": "collation",
|
|
469
|
+
"Postgres::Cast": "cast",
|
|
470
|
+
"Postgres::EventTrigger": "eventTrigger",
|
|
471
|
+
"Postgres::Extension": "extension",
|
|
472
|
+
"Postgres::Function": "function",
|
|
473
|
+
"Postgres::Procedure": "procedure",
|
|
474
|
+
"Postgres::Trigger": "trigger",
|
|
475
|
+
"Postgres::Policy": "policy",
|
|
476
|
+
"Postgres::Role": "role",
|
|
477
|
+
"Postgres::Grant": "grant",
|
|
478
|
+
"Postgres::DefaultPrivileges": "defaultPrivileges",
|
|
479
|
+
}[o.type];
|
|
480
|
+
return {
|
|
481
|
+
kind,
|
|
482
|
+
...(o.schema !== undefined && kind !== "schema" && kind !== "extension" && kind !== "role" && kind !== "cast" && kind !== "eventTrigger" ? { schema: o.schema } : {}),
|
|
483
|
+
name: o.name,
|
|
484
|
+
...(o.signature !== undefined ? { signature: o.signature } : {}),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
/** The comments the extensions' control files set, by extension name. */
|
|
488
|
+
async function extensionDefaults(client, names) {
|
|
489
|
+
if (names.length === 0)
|
|
490
|
+
return new Map();
|
|
491
|
+
const rows = await client
|
|
492
|
+
.query("SELECT name, comment FROM pg_catalog.pg_available_extensions WHERE name = ANY($1::text[])", [[...names]])
|
|
493
|
+
.catch(() => []);
|
|
494
|
+
return new Map(rows.filter((r) => r.comment).map((r) => [r.name, r.comment]));
|
|
495
|
+
}
|
|
496
|
+
/** After a failed CREATE INDEX CONCURRENTLY: drop the INVALID index it left, and only an INVALID one. */
|
|
497
|
+
async function dropInvalidIndex(client, index, log) {
|
|
498
|
+
try {
|
|
499
|
+
const [row] = await client.query("SELECT NOT i.indisvalid AS invalid FROM pg_catalog.pg_index i WHERE i.indexrelid = pg_catalog.to_regclass($1)", [index]);
|
|
500
|
+
if (!row?.invalid)
|
|
501
|
+
return;
|
|
502
|
+
const sql = `DROP INDEX CONCURRENTLY IF EXISTS ${index}`;
|
|
503
|
+
log(sql);
|
|
504
|
+
await client.query(sql);
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
// Left for the next apply, which reads it and builds it again.
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async function prune(client, drops, liveRawByKey, opts, timeouts, outcome, log) {
|
|
511
|
+
for (const drop of drops) {
|
|
512
|
+
const raw = liveRawByKey.get(drop.key);
|
|
513
|
+
if (!raw || raw.foreign)
|
|
514
|
+
continue;
|
|
515
|
+
const address = liveCanonicalAddress(raw);
|
|
516
|
+
const ref = { kind: raw.type, name: refName(address) };
|
|
517
|
+
if (!isChantManaged(raw.comment))
|
|
518
|
+
continue; // not chant's: never touched, never reported
|
|
519
|
+
if (!opts.marker?.stack) {
|
|
520
|
+
outcome.notAttempted.push({
|
|
521
|
+
...ref,
|
|
522
|
+
reason: "not-prunable",
|
|
523
|
+
detail: "the project declares no ownership.stack, so this project's objects cannot be told from another chant project's",
|
|
524
|
+
});
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (!carriesMarker(raw.comment, opts.marker))
|
|
528
|
+
continue; // another stack's or env's
|
|
529
|
+
const s = drop.step;
|
|
530
|
+
const t = { lockTimeoutMs: timeouts.lockTimeoutMs, statementTimeoutMs: scansRows(s.class) ? timeouts.scanTimeoutMs : timeouts.statementTimeoutMs };
|
|
531
|
+
try {
|
|
532
|
+
opts.signal?.throwIfAborted();
|
|
533
|
+
for (const set of [`SET lock_timeout = ${ms(t.lockTimeoutMs)}`, `SET statement_timeout = ${ms(t.statementTimeoutMs)}`]) {
|
|
534
|
+
log(set);
|
|
535
|
+
await client.query(set);
|
|
536
|
+
}
|
|
537
|
+
log(s.sql);
|
|
538
|
+
outcome.statements.push({ object: ref.name, sql: s.sql, class: s.class, ...t });
|
|
539
|
+
await client.query(s.sql);
|
|
540
|
+
outcome.pruned.push({ ...ref, deleted: true, statement: s.sql });
|
|
541
|
+
}
|
|
542
|
+
catch (err) {
|
|
543
|
+
if (opts.signal?.aborted)
|
|
544
|
+
throw err;
|
|
545
|
+
const code = err instanceof PostgresQueryError ? err.code : undefined;
|
|
546
|
+
if (code === SQLSTATE_DEPENDENT_OBJECTS) {
|
|
547
|
+
outcome.notAttempted.push({ ...ref, reason: "not-prunable", detail: `${errorText(err, t)}; dropping it would take what depends on it, which this apply does not drop` });
|
|
548
|
+
}
|
|
549
|
+
else if (code && SQLSTATE_UNDEFINED.has(code)) {
|
|
550
|
+
// Gone with an object dropped before it (a table's own index or sequence).
|
|
551
|
+
outcome.pruned.push({ ...ref, deleted: false, statement: s.sql });
|
|
552
|
+
}
|
|
553
|
+
else
|
|
554
|
+
outcome.failed.push({ ...ref, error: `${errorText(err, t)}, at ${s.sql}`, statements: [] });
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|