@intentius/chant-lexicon-sql 0.124.30 → 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,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The diff between two Postgres schemas, each change classified by the lock
|
|
3
|
+
* it takes and whether it reads or rewrites the table (`./rules.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Identity, as for ClickHouse (#3237): an object is its export name between
|
|
6
|
+
* two builds, so a changed SQL name under the same export is a rename;
|
|
7
|
+
* against a server it is its qualified name, and `-- previously: <old name>`
|
|
8
|
+
* before the CREATE declares a rename. A column is its name; a column renamed
|
|
9
|
+
* in place says `-- previously: <old name>` on its line, and without the hint
|
|
10
|
+
* a drop and an add of the same type in the same place get a hint.
|
|
11
|
+
*
|
|
12
|
+
* A constraint the declaration leaves unnamed is matched by what it says, so
|
|
13
|
+
* the name Postgres gave it is not a change.
|
|
14
|
+
*/
|
|
15
|
+
import { classifiedChange } from "../../core/classifier.js";
|
|
16
|
+
import { matchByIdentity } from "../../core/diff.js";
|
|
17
|
+
import { PG_CLASSIFIER_RULES } from "./rules.js";
|
|
18
|
+
import { ACCESS_KINDS, sameConstraint, serialBase } from "./normalize.js";
|
|
19
|
+
import { builtinEntries, declaredAccess, diffAccess, entryKey, targetKey } from "../access/acl.js";
|
|
20
|
+
import { pgNamespace } from "./schema.js";
|
|
21
|
+
import { POSTGRES_LATEST_MAJOR } from "../../spec/postgres-pin.js";
|
|
22
|
+
const change = (object, field, rule, before, after, extra = {}) => classifiedChange(PG_CLASSIFIER_RULES, object, field, rule, before, after, extra);
|
|
23
|
+
const qualified = (o) => `${o.schema ? `${o.schema}.` : ""}${o.name}${o.kind === "trigger" || o.kind === "policy" ? "" : (o.signature ?? "")}`;
|
|
24
|
+
// ── Columns ────────────────────────────────────────────────────────────
|
|
25
|
+
/**
|
|
26
|
+
* Volatile functions a default may call, from `pg_proc.provolatile = 'v'` at
|
|
27
|
+
* 18.6: a default that calls one has to be computed per row, so ADD COLUMN
|
|
28
|
+
* with it rewrites the table. `now()` and `current_timestamp` are stable and
|
|
29
|
+
* do not.
|
|
30
|
+
*/
|
|
31
|
+
const VOLATILE = /\b(clock_timestamp|timeofday|random|random_normal|setseed|gen_random_uuid|uuidv4|uuidv7|uuid_generate_v1|uuid_generate_v1mc|uuid_generate_v4|nextval|txid_current|pg_current_xact_id|statement_timestamp)\s*\(/;
|
|
32
|
+
/** Whether a type change needs no rewrite: binary-coercible, per ALTER TABLE's Notes. */
|
|
33
|
+
function coercible(before, after) {
|
|
34
|
+
if (before === after)
|
|
35
|
+
return true;
|
|
36
|
+
const vc = (t) => /^character varying(\((\d+)\))?$/.exec(t);
|
|
37
|
+
const b = vc(before);
|
|
38
|
+
const a = vc(after);
|
|
39
|
+
if (b && (after === "text" || (a && (!a[2] || (b[2] && Number(a[2]) >= Number(b[2]))))))
|
|
40
|
+
return true;
|
|
41
|
+
if (before === "text" && after === "character varying")
|
|
42
|
+
return true;
|
|
43
|
+
const num = (t) => /^numeric(\((\d+),(\d+)\))?$/.exec(t);
|
|
44
|
+
const nb = num(before);
|
|
45
|
+
const na = num(after);
|
|
46
|
+
if (nb && na && (!na[1] || (nb[1] && na[3] === nb[3] && Number(na[2]) >= Number(nb[2]))))
|
|
47
|
+
return true;
|
|
48
|
+
if (before === "cidr" && after === "inet")
|
|
49
|
+
return true;
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
/** The kind of value a type holds, for telling a widening from a change of kind. */
|
|
53
|
+
function family(t) {
|
|
54
|
+
const base = t.replace(/\(.*\)/, "").replace(/\[\]$/, "");
|
|
55
|
+
if (/^(smallint|integer|bigint|numeric|real|double precision|serial|bigserial|smallserial)$/.test(base))
|
|
56
|
+
return "number";
|
|
57
|
+
if (/^(text|character varying|character|"char"|name|citext)$/.test(base) || base.endsWith(".citext"))
|
|
58
|
+
return "string";
|
|
59
|
+
if (/^(timestamp|time|date|interval)/.test(base))
|
|
60
|
+
return "time";
|
|
61
|
+
return base;
|
|
62
|
+
}
|
|
63
|
+
function diffColumns(key, before, after, out, hints, tableIsNew, major) {
|
|
64
|
+
const byName = new Map(before.columns.map((c) => [c.name, c]));
|
|
65
|
+
const afterNames = new Set(after.columns.map((c) => c.name));
|
|
66
|
+
const matched = new Set();
|
|
67
|
+
const added = [];
|
|
68
|
+
const checksProvingNotNull = new Set(before.constraints.filter((c) => c.kind === "CHECK" && !c.notValid).map((c) => /^check (\S+) is not null$/.exec(c.body)?.[1]).filter((x) => x !== undefined));
|
|
69
|
+
for (const a of after.columns) {
|
|
70
|
+
let b = byName.get(a.name);
|
|
71
|
+
const prev = after.columnPreviously[a.name];
|
|
72
|
+
if (!b && prev && byName.has(prev) && !afterNames.has(prev)) {
|
|
73
|
+
b = byName.get(prev);
|
|
74
|
+
out.push(change(key, `columns.${a.name}`, "SQLPG205", b.name, a.name));
|
|
75
|
+
}
|
|
76
|
+
if (!b) {
|
|
77
|
+
added.push(a);
|
|
78
|
+
if (tableIsNew)
|
|
79
|
+
continue;
|
|
80
|
+
const identity = a.identity !== undefined;
|
|
81
|
+
const storedGenerated = a.generated?.startsWith("stored ");
|
|
82
|
+
const volatile = a.default !== undefined && VOLATILE.test(a.default);
|
|
83
|
+
const text = `${a.name} ${a.type ?? ""}`.trim();
|
|
84
|
+
if (identity || storedGenerated || volatile) {
|
|
85
|
+
out.push(change(key, `columns.${a.name}`, "SQLPG202", undefined, text, { note: identity ? "an identity column" : storedGenerated ? "a STORED generated column" : `the default ${a.default} is volatile` }));
|
|
86
|
+
}
|
|
87
|
+
else if (a.notNull && a.default === undefined && !a.generated)
|
|
88
|
+
out.push(change(key, `columns.${a.name}`, "SQLPG203", undefined, text));
|
|
89
|
+
else
|
|
90
|
+
out.push(change(key, `columns.${a.name}`, "SQLPG201", undefined, text));
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
matched.add(b.name);
|
|
94
|
+
const at = `columns.${a.name}`;
|
|
95
|
+
if (b.type !== a.type) {
|
|
96
|
+
// A serial spelling is an integer column with an owned sequence and a nextval default:
|
|
97
|
+
// the change is classified by the integer types, and a change of the shorthand alone is a default.
|
|
98
|
+
const bt = serialBase(b.type) ?? b.type ?? "";
|
|
99
|
+
const at2 = serialBase(a.type) ?? a.type ?? "";
|
|
100
|
+
const serial = serialBase(b.type) !== undefined || serialBase(a.type) !== undefined;
|
|
101
|
+
if (bt === at2)
|
|
102
|
+
out.push(change(key, `${at}.default`, "SQLPG209", b.type, a.type, { note: "the column gains or loses its owned sequence and nextval default" }));
|
|
103
|
+
else {
|
|
104
|
+
const rule = coercible(bt, at2) ? "SQLPG206" : family(bt) === family(at2) ? "SQLPG207" : "SQLPG208";
|
|
105
|
+
const note = serial && serialBase(b.type) !== undefined && serialBase(a.type) !== undefined ? { note: `${bt} to ${at2}; the owned sequence is widened to ${at2} (ALTER SEQUENCE ... AS ${at2}, SQLPG265)` } : {};
|
|
106
|
+
out.push(change(key, `${at}.type`, rule, b.type, a.type, note));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (b.default !== a.default)
|
|
110
|
+
out.push(change(key, `${at}.default`, "SQLPG209", b.default, a.default));
|
|
111
|
+
if (b.notNull !== a.notNull) {
|
|
112
|
+
if (a.notNull) {
|
|
113
|
+
const proven = checksProvingNotNull.has(a.name);
|
|
114
|
+
out.push(change(key, `${at}.notNull`, "SQLPG210", "NULL", "NOT NULL", proven ? { class: "metadata", note: "a valid CHECK (column IS NOT NULL) proves it, so no scan (12 and later)" } : {}));
|
|
115
|
+
}
|
|
116
|
+
else
|
|
117
|
+
out.push(change(key, `${at}.notNull`, "SQLPG211", "NOT NULL", "NULL"));
|
|
118
|
+
}
|
|
119
|
+
if (b.generated !== a.generated) {
|
|
120
|
+
const virtual = a.generated?.startsWith("virtual ") && b.generated?.startsWith("virtual ");
|
|
121
|
+
const extra = virtual
|
|
122
|
+
? { class: "metadata", note: "a VIRTUAL column (18) changes only the catalog" }
|
|
123
|
+
: major < 17
|
|
124
|
+
? { class: "expand", note: `Postgres ${major} has no SET EXPRESSION: the column is dropped and added` }
|
|
125
|
+
: {};
|
|
126
|
+
out.push(change(key, `${at}.generated`, "SQLPG212", b.generated, a.generated, extra));
|
|
127
|
+
}
|
|
128
|
+
if (b.identity !== a.identity)
|
|
129
|
+
out.push(change(key, `${at}.identity`, "SQLPG213", b.identity, a.identity));
|
|
130
|
+
if (b.collate !== a.collate)
|
|
131
|
+
out.push(change(key, `${at}.collate`, "SQLPG214", b.collate, a.collate));
|
|
132
|
+
if (b.compression !== a.compression || b.storage !== a.storage)
|
|
133
|
+
out.push(change(key, `${at}.storage`, "SQLPG215", [b.storage, b.compression].filter(Boolean).join(" ") || undefined, [a.storage, a.compression].filter(Boolean).join(" ") || undefined));
|
|
134
|
+
if (b.comment !== a.comment)
|
|
135
|
+
out.push(change(key, `${at}.comment`, "SQLPG216", b.comment, a.comment));
|
|
136
|
+
}
|
|
137
|
+
const dropped = before.columns.filter((c) => !matched.has(c.name));
|
|
138
|
+
for (const b of dropped) {
|
|
139
|
+
out.push(change(key, `columns.${b.name}`, "SQLPG204", `${b.name} ${b.type ?? ""}`.trim(), undefined, { destructive: true }));
|
|
140
|
+
const twin = added.find((a) => a.type === b.type && a.position === b.position);
|
|
141
|
+
if (twin)
|
|
142
|
+
hints.push(`${key}: ${b.name} is dropped and ${twin.name} added with the same type in the same place; if it is a rename, write -- previously: ${b.name} on its line`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// ── Constraints ────────────────────────────────────────────────────────
|
|
146
|
+
/** The rule adding a constraint falls under. */
|
|
147
|
+
export function addConstraintRule(c) {
|
|
148
|
+
if (c.notValid)
|
|
149
|
+
return "SQLPG217";
|
|
150
|
+
switch (c.kind) {
|
|
151
|
+
case "CHECK":
|
|
152
|
+
return "SQLPG218";
|
|
153
|
+
case "FOREIGN KEY":
|
|
154
|
+
return "SQLPG219";
|
|
155
|
+
case "PRIMARY KEY":
|
|
156
|
+
case "UNIQUE":
|
|
157
|
+
return "SQLPG221";
|
|
158
|
+
case "EXCLUDE":
|
|
159
|
+
return "SQLPG222";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function diffConstraints(key, before, after, out, domain) {
|
|
163
|
+
const remaining = [...before.constraints];
|
|
164
|
+
const label = (c) => (c.name ? `${c.name}: ${c.body}` : c.body);
|
|
165
|
+
for (const a of after.constraints) {
|
|
166
|
+
const i = remaining.findIndex((b) => sameConstraint(a, b));
|
|
167
|
+
if (i < 0) {
|
|
168
|
+
out.push(change(key, `constraints.${a.name ?? a.kind.toLowerCase()}`, domain ? (a.notValid ? "SQLPG217" : "SQLPG262") : addConstraintRule(a), undefined, label(a)));
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const b = remaining.splice(i, 1)[0];
|
|
172
|
+
if (b.notValid && !a.notValid)
|
|
173
|
+
out.push(change(key, `constraints.${a.name ?? b.name ?? a.kind.toLowerCase()}`, "SQLPG220", "NOT VALID", "VALID"));
|
|
174
|
+
if (!b.notValid && a.notValid)
|
|
175
|
+
out.push(change(key, `constraints.${a.name ?? b.name ?? a.kind.toLowerCase()}`, "SQLPG216", "VALID", "NOT VALID", { note: "a validated constraint stays validated; NOT VALID only matters when it is added" }));
|
|
176
|
+
if (b.comment !== a.comment)
|
|
177
|
+
out.push(change(key, `constraints.${a.name ?? b.name}.comment`, "SQLPG216", b.comment, a.comment));
|
|
178
|
+
}
|
|
179
|
+
for (const b of remaining)
|
|
180
|
+
out.push(change(key, `constraints.${b.name ?? b.kind.toLowerCase()}`, domain ? "SQLPG263" : "SQLPG223", label(b), undefined));
|
|
181
|
+
}
|
|
182
|
+
// ── Objects ────────────────────────────────────────────────────────────
|
|
183
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
184
|
+
const show = (v) => (v === undefined ? undefined : typeof v === "string" ? v : JSON.stringify(v));
|
|
185
|
+
/**
|
|
186
|
+
* The clauses chant does not read (#3872), compared as text: one the
|
|
187
|
+
* declaration has and the server does not, or has differently, is a change
|
|
188
|
+
* to review (SQLPG299); one only the server prints is a hint, since a
|
|
189
|
+
* default a newer server prints cannot be told from a clause removed.
|
|
190
|
+
*/
|
|
191
|
+
function diffUnparsed(key, before, after, out, hints) {
|
|
192
|
+
const b = before.fields.unparsed ?? {};
|
|
193
|
+
const a = after.fields.unparsed ?? {};
|
|
194
|
+
for (const clause of Object.keys(a)) {
|
|
195
|
+
if (b[clause] !== a[clause])
|
|
196
|
+
out.push(change(key, `unparsed.${clause}`, "SQLPG299", b[clause], a[clause], { note: "chant does not read this clause; review the change" }));
|
|
197
|
+
}
|
|
198
|
+
for (const clause of Object.keys(b)) {
|
|
199
|
+
if (!(clause in a))
|
|
200
|
+
hints.push(`${key}: the current definition has ${clause} (${b[clause]}), a clause chant does not read and the declaration does not have; it is not compared`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** The kinds (#3911) whose definition has no ALTER: a change is a drop and a create, or (an aggregate's) CREATE OR REPLACE. */
|
|
204
|
+
const REPLACED_KINDS = new Set(["composite", "range", "aggregate", "collation", "cast", "eventTrigger"]);
|
|
205
|
+
/** What each of {@link REPLACED_KINDS} compares besides its name and comment. */
|
|
206
|
+
const DEFINITION = {
|
|
207
|
+
composite: ["attributeDefs"],
|
|
208
|
+
range: ["subtype", "subtypeOpclass", "collation", "canonical", "subtypeDiff", "multirange"],
|
|
209
|
+
aggregate: ["definition"],
|
|
210
|
+
collation: ["provider", "lcCollate", "lcCtype", "locale", "nondeterministic", "rules"],
|
|
211
|
+
cast: ["castMethod", "function", "context"],
|
|
212
|
+
eventTrigger: ["event", "tags", "function"],
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* A type's, aggregate's, collation's, cast's or event trigger's change: its
|
|
216
|
+
* comment in place; its definition by CREATE OR REPLACE AGGREGATE where that
|
|
217
|
+
* keeps the aggregate's result, else dropped and created (SQLPG300), or
|
|
218
|
+
* refused while something depends on it (SQLPG301).
|
|
219
|
+
*/
|
|
220
|
+
function diffReplaced(key, before, after, out) {
|
|
221
|
+
if (!same(before.fields.comment, after.fields.comment))
|
|
222
|
+
out.push(change(key, "comment", "SQLPG216", show(before.fields.comment), show(after.fields.comment)));
|
|
223
|
+
const changed = DEFINITION[after.kind].filter((k) => !same(before.fields[k], after.fields[k]));
|
|
224
|
+
if (before.schema !== after.schema || before.name !== after.name || before.signature !== after.signature)
|
|
225
|
+
changed.unshift("name");
|
|
226
|
+
if (changed.length === 0)
|
|
227
|
+
return;
|
|
228
|
+
const was = changed.map((k) => `${k} ${k === "name" ? qualified(before) : (show(before.fields[k]) ?? "-")}`).join("; ");
|
|
229
|
+
const now = changed.map((k) => `${k} ${k === "name" ? qualified(after) : (show(after.fields[k]) ?? "-")}`).join("; ");
|
|
230
|
+
const bo = (before.fields.definition ?? {});
|
|
231
|
+
const ao = (after.fields.definition ?? {});
|
|
232
|
+
const replaceable = after.kind === "aggregate" && !changed.includes("name") && same(bo.stype, ao.stype) && same(bo.finalfunc, ao.finalfunc) && same(bo.hypothetical, ao.hypothetical);
|
|
233
|
+
const what = changed.join(", ");
|
|
234
|
+
if (replaceable)
|
|
235
|
+
out.push(change(key, what, "SQLPG302", was, now, { note: "CREATE OR REPLACE AGGREGATE" }));
|
|
236
|
+
else if (before.dependents && before.dependents.length > 0) {
|
|
237
|
+
out.push(change(key, what, "SQLPG301", was, now, { note: `${before.dependents.join(", ")} depend${before.dependents.length === 1 ? "s" : ""} on it` }));
|
|
238
|
+
}
|
|
239
|
+
else
|
|
240
|
+
out.push(change(key, what, "SQLPG300", was, now));
|
|
241
|
+
}
|
|
242
|
+
function diffObject(key, before, after, out, hints, major) {
|
|
243
|
+
diffUnparsed(key, before, after, out, hints);
|
|
244
|
+
if (before.kind !== after.kind) {
|
|
245
|
+
out.push(change(key, "kind", "SQLPG268", before.kind, after.kind));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const f = (name) => [before.fields[name], after.fields[name]];
|
|
249
|
+
const field = (name, rule, extra = {}) => {
|
|
250
|
+
const [b, a] = f(name);
|
|
251
|
+
if (!same(b, a))
|
|
252
|
+
out.push(change(key, name, rule, show(b), show(a), extra));
|
|
253
|
+
};
|
|
254
|
+
if (REPLACED_KINDS.has(after.kind)) {
|
|
255
|
+
// A cast is named by its types, and an aggregate's argument types are its identity: either changed, between two builds, is another object.
|
|
256
|
+
if (after.kind === "cast" && before.name !== after.name) {
|
|
257
|
+
out.push(change(key, "name", before.dependents?.length ? "SQLPG301" : "SQLPG300", before.name, after.name));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
diffReplaced(key, before, after, out);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (before.schema !== after.schema || before.name !== after.name) {
|
|
264
|
+
out.push(change(key, "name", after.kind === "index" ? "SQLPG229" : "SQLPG228", qualified(before), qualified(after)));
|
|
265
|
+
}
|
|
266
|
+
field("comment", "SQLPG216");
|
|
267
|
+
switch (after.kind) {
|
|
268
|
+
case "schema":
|
|
269
|
+
field("authorization", "SQLPG267");
|
|
270
|
+
break;
|
|
271
|
+
case "extension":
|
|
272
|
+
field("version", "SQLPG266");
|
|
273
|
+
field("schema", "SQLPG266");
|
|
274
|
+
break;
|
|
275
|
+
case "sequence":
|
|
276
|
+
field("options", "SQLPG265");
|
|
277
|
+
field("ownedBy", "SQLPG265");
|
|
278
|
+
field("unlogged", "SQLPG225", { class: "metadata", note: "a sequence's own row only" });
|
|
279
|
+
break;
|
|
280
|
+
case "enum": {
|
|
281
|
+
const b = before.fields.labels ?? [];
|
|
282
|
+
const a = after.fields.labels ?? [];
|
|
283
|
+
if (!same(b, a)) {
|
|
284
|
+
// Labels only added, wherever they go, keep every old label in order.
|
|
285
|
+
const kept = a.filter((l) => b.includes(l));
|
|
286
|
+
out.push(change(key, "labels", same(kept, b) ? "SQLPG260" : "SQLPG261", b.join(", "), a.join(", ")));
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case "domain":
|
|
291
|
+
field("dataType", "SQLPG264");
|
|
292
|
+
field("collate", "SQLPG264");
|
|
293
|
+
field("default", "SQLPG263");
|
|
294
|
+
if (!same(f("notNull")[0], f("notNull")[1]))
|
|
295
|
+
out.push(change(key, "notNull", after.fields.notNull ? "SQLPG262" : "SQLPG263", show(before.fields.notNull), show(after.fields.notNull)));
|
|
296
|
+
diffConstraints(key, before, after, out, true);
|
|
297
|
+
break;
|
|
298
|
+
case "index": {
|
|
299
|
+
const definition = ["table", "unique", "method", "elements", "include", "nullsNotDistinct", "where"];
|
|
300
|
+
const changed = definition.filter((k) => !same(before.fields[k], after.fields[k]));
|
|
301
|
+
if (changed.length > 0) {
|
|
302
|
+
out.push(change(key, changed.join(", "), "SQLPG243", changed.map((k) => `${k} ${show(before.fields[k]) ?? "-"}`).join("; "), changed.map((k) => `${k} ${show(after.fields[k]) ?? "-"}`).join("; ")));
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
field("with", "SQLPG224");
|
|
306
|
+
field("tablespace", "SQLPG226");
|
|
307
|
+
}
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
case "view":
|
|
311
|
+
case "materializedView": {
|
|
312
|
+
const b = before.outputs ?? [];
|
|
313
|
+
const a = after.outputs ?? [];
|
|
314
|
+
const appends = a.length >= b.length && b.every((o, i) => a[i] === o);
|
|
315
|
+
if (!same(before.fields.query, after.fields.query) || !same(b, a)) {
|
|
316
|
+
if (after.kind === "materializedView")
|
|
317
|
+
out.push(change(key, "query", "SQLPG252", show(before.fields.query), show(after.fields.query)));
|
|
318
|
+
else
|
|
319
|
+
out.push(change(key, "query", appends ? "SQLPG250" : "SQLPG251", show(before.fields.query), show(after.fields.query), appends ? {} : { note: `columns ${b.join(", ")} -> ${a.join(", ")}` }));
|
|
320
|
+
}
|
|
321
|
+
field("with", "SQLPG253");
|
|
322
|
+
field("checkOption", "SQLPG253");
|
|
323
|
+
field("withData", "SQLPG252");
|
|
324
|
+
field("columnComments", "SQLPG216");
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
case "function":
|
|
328
|
+
case "procedure": {
|
|
329
|
+
// CREATE OR REPLACE cannot change the result, the OUT parameters, an input parameter's name, a removed default or a window function's kind.
|
|
330
|
+
const ba = before.fields.args ?? [];
|
|
331
|
+
const aa = after.fields.args ?? [];
|
|
332
|
+
const inputName = (a) => (/^(in|inout|variadic) /.test(a) ? a.split(" ")[1] : undefined);
|
|
333
|
+
const reasons = [];
|
|
334
|
+
// Between two builds, the same export with other parameter types is another function on the server.
|
|
335
|
+
if (before.signature !== after.signature)
|
|
336
|
+
reasons.push(`the parameter types ${before.signature} -> ${after.signature}`);
|
|
337
|
+
if (!same(before.fields.returns, after.fields.returns))
|
|
338
|
+
reasons.push(`the result ${show(before.fields.returns) ?? "-"} -> ${show(after.fields.returns) ?? "-"}`);
|
|
339
|
+
const outs = (l) => l.filter((a) => /^(out|inout) /.test(a));
|
|
340
|
+
if (!same(outs(ba), outs(aa)))
|
|
341
|
+
reasons.push("the output parameters");
|
|
342
|
+
if (ba.some((a, i) => aa[i] !== undefined && inputName(a) !== inputName(aa[i]) && /^(in|inout|variadic) /.test(aa[i])))
|
|
343
|
+
reasons.push("a parameter's name");
|
|
344
|
+
if (ba.some((a, i) => / default /.test(a) && aa[i] !== undefined && !/ default /.test(aa[i])))
|
|
345
|
+
reasons.push("a parameter's default is removed");
|
|
346
|
+
if (!same(before.fields.window, after.fields.window))
|
|
347
|
+
reasons.push("WINDOW");
|
|
348
|
+
const definition = ["args", "returns", "language", "body", "link", "securityDefiner", "set", "transform", "volatility", "strict", "leakproof", "parallel", "cost", "rows", "support", "window"];
|
|
349
|
+
const changed = definition.filter((k) => !same(before.fields[k], after.fields[k]));
|
|
350
|
+
if (changed.length === 0 && reasons.length === 0)
|
|
351
|
+
break;
|
|
352
|
+
if (changed.length === 0)
|
|
353
|
+
changed.push("args");
|
|
354
|
+
const what = changed.join(", ");
|
|
355
|
+
if (reasons.length === 0)
|
|
356
|
+
out.push(change(key, what, "SQLPG280", undefined, undefined, { note: `CREATE OR REPLACE ${after.kind.toUpperCase()}` }));
|
|
357
|
+
else if (before.dependents && before.dependents.length > 0) {
|
|
358
|
+
out.push(change(key, what, "SQLPG282", undefined, undefined, { note: `${reasons.join(", ")} changes, and ${before.dependents.join(", ")} depend${before.dependents.length === 1 ? "s" : ""} on it` }));
|
|
359
|
+
}
|
|
360
|
+
else
|
|
361
|
+
out.push(change(key, what, "SQLPG281", undefined, undefined, { note: `${reasons.join(", ")} changes, which CREATE OR REPLACE refuses` }));
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
case "trigger": {
|
|
365
|
+
const definition = ["table", "constraint", "timing", "events", "forEach", "when", "function", "args", "from", "deferrable", "initiallyDeferred", "referencing"];
|
|
366
|
+
const changed = definition.filter((k) => !same(before.fields[k], after.fields[k]));
|
|
367
|
+
if (changed.length > 0) {
|
|
368
|
+
out.push(change(key, changed.join(", "), "SQLPG284", changed.map((k) => `${k} ${show(before.fields[k]) ?? "-"}`).join("; "), changed.map((k) => `${k} ${show(after.fields[k]) ?? "-"}`).join("; "), after.fields.constraint || before.fields.constraint || !same(before.fields.table, after.fields.table) ? { note: "dropped and created in one transaction: a constraint trigger has no OR REPLACE, and OR REPLACE keeps a trigger on its table" } : {}));
|
|
369
|
+
}
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
case "policy": {
|
|
373
|
+
// ALTER POLICY sets the roles, USING and WITH CHECK; the command and AS RESTRICTIVE are fixed, and neither expression can be taken away.
|
|
374
|
+
const definition = ["table", "restrictive", "command", "roles", "using", "check"];
|
|
375
|
+
const changed = definition.filter((k) => !same(before.fields[k], after.fields[k]));
|
|
376
|
+
if (changed.length === 0)
|
|
377
|
+
break;
|
|
378
|
+
const recreate = !same(before.fields.table, after.fields.table) ||
|
|
379
|
+
!same(before.fields.restrictive, after.fields.restrictive) ||
|
|
380
|
+
!same(before.fields.command, after.fields.command) ||
|
|
381
|
+
(before.fields.using !== undefined && after.fields.using === undefined) ||
|
|
382
|
+
(before.fields.check !== undefined && after.fields.check === undefined);
|
|
383
|
+
out.push(change(key, changed.join(", "), "SQLPG291", changed.map((k) => `${k} ${show(before.fields[k]) ?? "-"}`).join("; "), changed.map((k) => `${k} ${show(after.fields[k]) ?? "-"}`).join("; "), recreate ? { note: "dropped and created in one transaction: ALTER POLICY cannot change its command, AS RESTRICTIVE or table, or take an expression away" } : {}));
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
case "role":
|
|
387
|
+
field("attributes", "SQLPG294");
|
|
388
|
+
field("connectionLimit", "SQLPG294");
|
|
389
|
+
break;
|
|
390
|
+
case "table":
|
|
391
|
+
if (!same(before.fields.rowSecurity, after.fields.rowSecurity) || !same(before.fields.forceRowSecurity, after.fields.forceRowSecurity)) {
|
|
392
|
+
const state = (o) => (o.fields.rowSecurity ? (o.fields.forceRowSecurity ? "enabled, forced" : "enabled") : o.fields.forceRowSecurity ? "disabled, forced" : "disabled");
|
|
393
|
+
out.push(change(key, "rowSecurity", "SQLPG293", state(before), state(after), after.fields.rowSecurity && !before.fields.rowSecurity ? { note: "with row-level security on, a role the table's policies do not let in sees no rows" } : {}));
|
|
394
|
+
}
|
|
395
|
+
diffColumns(key, before, after, out, hints, false, major);
|
|
396
|
+
diffConstraints(key, before, after, out, false);
|
|
397
|
+
field("with", "SQLPG224");
|
|
398
|
+
field("unlogged", "SQLPG225");
|
|
399
|
+
field("using", "SQLPG226", major < 15 ? { class: "expand", note: `Postgres ${major} has no SET ACCESS METHOD: the table is created again` } : {});
|
|
400
|
+
field("tablespace", "SQLPG226");
|
|
401
|
+
for (const k of ["partitionBy", "partitionOf", "partitionBound", "inherits"])
|
|
402
|
+
field(k, "SQLPG227");
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/** Whether an object is created by this plan in the same run (its index then needs no CONCURRENTLY). */
|
|
407
|
+
function createdTables(matches) {
|
|
408
|
+
const out = new Set();
|
|
409
|
+
for (const m of matches)
|
|
410
|
+
if (m.kind === "created" && m.after.canonical.kind === "table")
|
|
411
|
+
out.add(qualified(m.after.canonical));
|
|
412
|
+
return out;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Classify every change from `before` to `after`. Objects another tool owns
|
|
416
|
+
* (an ORM's revision table) are never proposed for a drop.
|
|
417
|
+
*/
|
|
418
|
+
/**
|
|
419
|
+
* Which object of `before` each object of `after` is: by key, else by a
|
|
420
|
+
* `-- previously:` hint naming the old one. The diff and the applier
|
|
421
|
+
* (`../apply/`) match the same way.
|
|
422
|
+
*/
|
|
423
|
+
export function matchPgObjects(before, after) {
|
|
424
|
+
return matchByIdentity(before, after, {
|
|
425
|
+
qualified: (o) => `${pgNamespace(o.kind)} ${o.schema ? `${o.schema}.` : ""}${o.name}${o.signature ?? ""}`,
|
|
426
|
+
previously: (o) => o.previously,
|
|
427
|
+
previousNames: (o, prev) => {
|
|
428
|
+
const space = pgNamespace(o.kind);
|
|
429
|
+
const sig = o.signature ?? "";
|
|
430
|
+
return [`${space} ${prev}${sig}`, ...(o.schema && !prev.includes(".") ? [`${space} ${o.schema}.${prev}${sig}`] : [])];
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
export function diffPgSchemas(beforeAll, afterAll, options = {}) {
|
|
435
|
+
const major = options.major ?? POSTGRES_LATEST_MAJOR;
|
|
436
|
+
// Grants and default privileges are compared as the access they add up to, below, not one declaration against another.
|
|
437
|
+
const before = beforeAll.filter((o) => !ACCESS_KINDS.has(o.canonical.kind));
|
|
438
|
+
const after = afterAll.filter((o) => !ACCESS_KINDS.has(o.canonical.kind));
|
|
439
|
+
const matches = matchPgObjects(before, after);
|
|
440
|
+
const newTables = createdTables(matches);
|
|
441
|
+
const changes = [];
|
|
442
|
+
const hints = [];
|
|
443
|
+
for (const m of matches) {
|
|
444
|
+
if (m.kind === "created") {
|
|
445
|
+
const o = m.after.canonical;
|
|
446
|
+
if (o.kind === "index" && !newTables.has(String(o.fields.table))) {
|
|
447
|
+
changes.push(change(m.after.key, "index", o.concurrently ? "SQLPG240" : "SQLPG241", undefined, qualified(o)));
|
|
448
|
+
}
|
|
449
|
+
else if (o.kind === "trigger" && !newTables.has(String(o.fields.table))) {
|
|
450
|
+
changes.push(change(m.after.key, "trigger", "SQLPG283", undefined, `${o.name}${o.signature ?? ""}`));
|
|
451
|
+
}
|
|
452
|
+
else if (o.kind === "policy" && !newTables.has(String(o.fields.table))) {
|
|
453
|
+
changes.push(change(m.after.key, "policy", "SQLPG290", undefined, `${o.name}${o.signature ?? ""}`));
|
|
454
|
+
}
|
|
455
|
+
else
|
|
456
|
+
changes.push(change(m.after.key, o.kind, "SQLPG200", undefined, qualified(o)));
|
|
457
|
+
}
|
|
458
|
+
else if (m.kind === "dropped") {
|
|
459
|
+
const o = m.before.canonical;
|
|
460
|
+
if (o.foreign) {
|
|
461
|
+
hints.push(`${m.before.key} is kept by ${o.foreign}; it is not chant's to drop and is left alone`);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (o.kind === "role") {
|
|
465
|
+
hints.push(`role ${o.name} is no longer declared; a role is the cluster's and is never dropped`);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const destructive = o.kind === "table" || o.kind === "materializedView" || o.kind === "sequence";
|
|
469
|
+
const rule = o.kind === "index" ? "SQLPG242" : o.kind === "trigger" ? "SQLPG285" : o.kind === "policy" ? "SQLPG292" : "SQLPG270";
|
|
470
|
+
changes.push(change(m.before.key, o.kind, rule, o.kind === "trigger" || o.kind === "policy" ? `${o.name}${o.signature ?? ""}` : qualified(o), undefined, destructive ? { destructive: true } : {}));
|
|
471
|
+
}
|
|
472
|
+
else
|
|
473
|
+
diffObject(m.after.key, m.before.canonical, m.after.canonical, changes, hints, major);
|
|
474
|
+
}
|
|
475
|
+
const access = options.access === false ? [] : buildAccessChanges(beforeAll, afterAll, major);
|
|
476
|
+
changes.push(...access.map((a) => a.change));
|
|
477
|
+
return { changes, hints, refused: changes.filter((c) => c.class === "expand"), ...(access.length > 0 ? { access } : {}) };
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* The access change between two builds: on what the newer build decides. An
|
|
481
|
+
* object only it declares starts from what Postgres gives a new one; an
|
|
482
|
+
* object only the older one declared is dropped with its privileges.
|
|
483
|
+
*/
|
|
484
|
+
function buildAccessChanges(before, after, major) {
|
|
485
|
+
const b = declaredAccess(before, { major });
|
|
486
|
+
const a = declaredAccess(after, { major });
|
|
487
|
+
const decided = new Set(a.targets.map(targetKey));
|
|
488
|
+
const known = new Set(b.targets.map(targetKey));
|
|
489
|
+
const from = new Map([...b.state].filter(([, e]) => decided.has(targetKey(e.target))));
|
|
490
|
+
for (const t of a.targets) {
|
|
491
|
+
if (known.has(targetKey(t)) || t.kind === "default" || t.kind === "column")
|
|
492
|
+
continue;
|
|
493
|
+
for (const e of builtinEntries(t.kind)) {
|
|
494
|
+
const key = entryKey(t, e.grantee);
|
|
495
|
+
from.set(key, { target: t, grantee: e.grantee, privileges: new Map([...(from.get(key)?.privileges ?? []), [e.privilege, false]]) });
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return diffAccess(from, a.state, a.exportsOf);
|
|
499
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `classifyDisruption()` for Postgres: what applying a pending update costs,
|
|
3
|
+
* from the paths `chant lifecycle plan` reports changed, mapped onto the
|
|
4
|
+
* classifier's rules (`./rules.ts`). A path alone cannot always say which rule
|
|
5
|
+
* applies (a column's type change is metadata, a rewrite or expand and
|
|
6
|
+
* contract depending on both types); those answer `unknown`, and `chant sql
|
|
7
|
+
* plan` classifies them with both definitions in hand.
|
|
8
|
+
*/
|
|
9
|
+
import { classifyDisruptionWith } from "../../core/classifier.js";
|
|
10
|
+
import { PG_CHANGE_CLASSES, PG_CLASSIFIER_RULES } from "./rules.js";
|
|
11
|
+
function ruleFor(path) {
|
|
12
|
+
const root = path.split(/[.[]/)[0];
|
|
13
|
+
if (root === "name" || root === "schema")
|
|
14
|
+
return "SQLPG228";
|
|
15
|
+
if (root === "comment" || root === "columnComments")
|
|
16
|
+
return "SQLPG216";
|
|
17
|
+
if (root === "with")
|
|
18
|
+
return "SQLPG224";
|
|
19
|
+
if (root === "persistence")
|
|
20
|
+
return "SQLPG225";
|
|
21
|
+
if (root === "using" || root === "tablespace")
|
|
22
|
+
return "SQLPG226";
|
|
23
|
+
if (["partitionBy", "partitionOf", "partitionBound", "inherits", "ofType"].includes(root))
|
|
24
|
+
return "SQLPG227";
|
|
25
|
+
if (root === "query")
|
|
26
|
+
return "ambiguous";
|
|
27
|
+
if (root === "labels")
|
|
28
|
+
return "ambiguous";
|
|
29
|
+
if (root === "dataType")
|
|
30
|
+
return "SQLPG264";
|
|
31
|
+
if (root === "default")
|
|
32
|
+
return "SQLPG263";
|
|
33
|
+
if (["increment", "minValue", "maxValue", "start", "cache", "cycle", "ownedBy"].includes(root))
|
|
34
|
+
return "SQLPG265";
|
|
35
|
+
if (root === "version")
|
|
36
|
+
return "SQLPG266";
|
|
37
|
+
if (root === "elements" || root === "where" || root === "include" || root === "unique" || root === "method")
|
|
38
|
+
return "SQLPG243";
|
|
39
|
+
if (["primaryKey", "uniques", "checks", "foreignKeys", "exclusions"].includes(root))
|
|
40
|
+
return "ambiguous";
|
|
41
|
+
if (root === "columns") {
|
|
42
|
+
const field = /^columns\[\d+\]\.(\w+)/.exec(path)?.[1];
|
|
43
|
+
if (field === undefined || field === "type" || field === "name" || field === "notNull" || field === "generated")
|
|
44
|
+
return "ambiguous";
|
|
45
|
+
if (field === "default")
|
|
46
|
+
return "SQLPG209";
|
|
47
|
+
if (field === "comment")
|
|
48
|
+
return "SQLPG216";
|
|
49
|
+
if (field === "collate")
|
|
50
|
+
return "SQLPG214";
|
|
51
|
+
if (field === "storage" || field === "compression")
|
|
52
|
+
return "SQLPG215";
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
export function classifyPgDisruption(options) {
|
|
57
|
+
return classifyDisruptionWith({
|
|
58
|
+
typePrefix: "Postgres::",
|
|
59
|
+
rules: PG_CLASSIFIER_RULES,
|
|
60
|
+
classes: PG_CHANGE_CLASSES,
|
|
61
|
+
ruleFor,
|
|
62
|
+
ambiguousDetail: "a column, constraint, view or enum change takes a different lock depending on both definitions; `chant sql plan` classifies it",
|
|
63
|
+
}, options);
|
|
64
|
+
}
|