@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,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A schema change's statements, rendered offline (#3644): for two `chant
|
|
3
|
+
* build` outputs, the ordered statements that take the first to the second,
|
|
4
|
+
* each with its change's class and rule, for a migration file. No server is
|
|
5
|
+
* read.
|
|
6
|
+
*
|
|
7
|
+
* The statements are the applier's: each dialect's `planStatements` /
|
|
8
|
+
* `planPgStatements` (`./clickhouse/apply/statements.ts`,
|
|
9
|
+
* `./postgres/apply/statements.ts`) turns classified changes into steps, and
|
|
10
|
+
* both the applier and this renderer call it. What differs is only where the
|
|
11
|
+
* current schema comes from: here the older build, keyed by export name as
|
|
12
|
+
* `chant sql diff` keys it; in the applier the server's catalog.
|
|
13
|
+
*
|
|
14
|
+
* A change the applier refuses in place is a step naming the Op that makes
|
|
15
|
+
* it, never DDL: a ClickHouse rebuild is `ClickHouseRebuildOp`, a Postgres
|
|
16
|
+
* column rename or type change across kinds is `PostgresMigrationOp`. One no
|
|
17
|
+
* Op makes is a manual step. Column and object drops are statements, marked
|
|
18
|
+
* destructive.
|
|
19
|
+
*
|
|
20
|
+
* Where the applier asks the server something, the renderer cannot:
|
|
21
|
+
*
|
|
22
|
+
* - ClickHouse: the applier also asks the server's formatter whether a
|
|
23
|
+
* difference the rules leave is only formatting; the offline diff is the
|
|
24
|
+
* rules' alone, as `chant sql diff` is. Before dropping a database it
|
|
25
|
+
* checks the database is empty.
|
|
26
|
+
* - Postgres: the applier asks the server to normalize expressions the rules
|
|
27
|
+
* leave different, and keeps an extension's own comment (from its control
|
|
28
|
+
* file) under the trailer when the declaration sets none.
|
|
29
|
+
* - Both: the applier restamps an object whose comment lost chant's marker;
|
|
30
|
+
* offline, the older build's objects carry it.
|
|
31
|
+
*/
|
|
32
|
+
import { statementChange } from "./core/statements.js";
|
|
33
|
+
import { declaredObjects as clickhouseDeclared } from "./clickhouse/apply/apply.js";
|
|
34
|
+
import { planStatements } from "./clickhouse/apply/statements.js";
|
|
35
|
+
import { topologyLabel } from "./clickhouse/topology.js";
|
|
36
|
+
import { diffSchemas } from "./clickhouse/plan/diff.js";
|
|
37
|
+
import { rebuildOpSuggestions } from "./clickhouse/plan/rebuild-handoff.js";
|
|
38
|
+
import { declaredObjects as postgresDeclared } from "./postgres/apply/apply.js";
|
|
39
|
+
import { planPgStatements } from "./postgres/apply/statements.js";
|
|
40
|
+
import { diffPgSchemas } from "./postgres/plan/diff.js";
|
|
41
|
+
import { pgBuildMajor } from "./postgres/plan/schema.js";
|
|
42
|
+
import { migrationOpSuggestions, migrationTarget } from "./postgres/migrate/handoff.js";
|
|
43
|
+
import { POSTGRES_ENTITY_TYPES } from "./postgres/entity-types.js";
|
|
44
|
+
import { POSTGRES_LATEST_MAJOR } from "./spec/postgres-pin.js";
|
|
45
|
+
export { renderStatements } from "./core/statements.js";
|
|
46
|
+
const text = (b) => (typeof b === "string" ? b : JSON.stringify(b));
|
|
47
|
+
/** The dialect a build output names, at its root or under `sql`. */
|
|
48
|
+
function dialectOf(json) {
|
|
49
|
+
const raw = JSON.parse(json);
|
|
50
|
+
const d = raw?.sql && typeof raw.sql === "object" ? raw.sql.dialect : raw?.dialect;
|
|
51
|
+
return typeof d === "string" ? d : undefined;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The statements that take the schema `before` builds to the one `after`
|
|
55
|
+
* builds. Each is a `chant build` output of the sql lexicon, as JSON text or
|
|
56
|
+
* parsed. Both must be the same dialect.
|
|
57
|
+
*/
|
|
58
|
+
export function diffStatements(before, after, options = {}) {
|
|
59
|
+
const b = text(before);
|
|
60
|
+
const a = text(after);
|
|
61
|
+
const dialect = dialectOf(a);
|
|
62
|
+
if (dialectOf(b) !== dialect)
|
|
63
|
+
throw new Error(`diffStatements: the builds are different dialects (${dialectOf(b) ?? "none"} and ${dialect ?? "none"})`);
|
|
64
|
+
if (dialect === "postgres")
|
|
65
|
+
return postgresStatements(b, a, options);
|
|
66
|
+
if (dialect === "clickhouse")
|
|
67
|
+
return clickhouseStatements(b, a, options);
|
|
68
|
+
throw new Error(`diffStatements: not a sql lexicon build output (dialect ${dialect ?? "missing"})`);
|
|
69
|
+
}
|
|
70
|
+
const at = (object, type, name) => ({ object, type, name });
|
|
71
|
+
// ── ClickHouse ─────────────────────────────────────────────────────────
|
|
72
|
+
function clickhouseStatements(beforeJson, afterJson, options) {
|
|
73
|
+
const defaultDatabase = options.defaultDatabase ?? "default";
|
|
74
|
+
const env = options.env ?? "<env>";
|
|
75
|
+
const topology = options.topology ?? { kind: "single" };
|
|
76
|
+
const before = clickhouseDeclared(beforeJson, defaultDatabase, topology);
|
|
77
|
+
const after = clickhouseDeclared(afterJson, defaultDatabase, topology);
|
|
78
|
+
const keyed = (objs) => objs.map((o) => ({ key: o.exportName, canonical: o.canonical }));
|
|
79
|
+
const diff = diffSchemas(keyed(before), keyed(after));
|
|
80
|
+
const plan = planStatements({
|
|
81
|
+
declared: after,
|
|
82
|
+
changes: diff.changes,
|
|
83
|
+
current: new Map(before.map((o) => [o.exportName, o.canonical])),
|
|
84
|
+
keyOf: (o) => o.exportName,
|
|
85
|
+
allowDestructive: true,
|
|
86
|
+
// A user whose password is the environment's exists by the time a migration runs: the GRANTs to it need it too (#3717).
|
|
87
|
+
environmentCreatesUsers: true,
|
|
88
|
+
topology,
|
|
89
|
+
...(options.marker ? { marker: options.marker } : {}),
|
|
90
|
+
});
|
|
91
|
+
const declaredByExport = new Map(after.map((o) => [o.exportName, o.canonical]));
|
|
92
|
+
const isDestructive = (changes, rule) => changes.some((c) => c.rule === rule && c.destructive);
|
|
93
|
+
const steps = [];
|
|
94
|
+
for (const entry of plan.objects) {
|
|
95
|
+
const where = at(entry.obj.exportName, entry.obj.type, entry.obj.key);
|
|
96
|
+
if (entry.verdict === "rebuild") {
|
|
97
|
+
const [op] = rebuildOpSuggestions({ ...diff, rebuilds: entry.refused }, declaredByExport, env);
|
|
98
|
+
if (op) {
|
|
99
|
+
steps.push({
|
|
100
|
+
kind: "op",
|
|
101
|
+
...where,
|
|
102
|
+
op: "ClickHouseRebuildOp",
|
|
103
|
+
importPath: "@intentius/chant-lexicon-sql/clickhouse",
|
|
104
|
+
options: { name: op.name, env: op.env, table: op.table, dualWrite: op.dualWrite },
|
|
105
|
+
declaration: op.declaration,
|
|
106
|
+
// The rebuild creates the new table from the declaration, so it makes every change to it.
|
|
107
|
+
changes: entry.changes.map(statementChange),
|
|
108
|
+
detail: entry.detail,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else
|
|
112
|
+
steps.push({ kind: "manual", ...where, changes: entry.refused.map(statementChange), detail: entry.detail });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (entry.verdict === "withheld")
|
|
116
|
+
continue; // not reached: destructive changes are planned
|
|
117
|
+
for (const s of entry.steps) {
|
|
118
|
+
steps.push({
|
|
119
|
+
kind: "statement",
|
|
120
|
+
...where,
|
|
121
|
+
sql: s.sql,
|
|
122
|
+
rule: s.rule,
|
|
123
|
+
class: s.class,
|
|
124
|
+
transactional: false,
|
|
125
|
+
...(s.rewrite ? { waitsForMutation: true } : {}),
|
|
126
|
+
...(s.rule === "SQLCH202" || isDestructive(entry.changes, s.rule) ? { destructive: true } : {}),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const beforeByExport = new Map(before.map((o) => [o.exportName, o]));
|
|
131
|
+
for (const d of plan.drops) {
|
|
132
|
+
const o = beforeByExport.get(d.key);
|
|
133
|
+
const change = diff.changes.find((c) => c.object === d.key && c.rule === "SQLCH250");
|
|
134
|
+
steps.push({
|
|
135
|
+
kind: "statement",
|
|
136
|
+
...at(d.key, d.type, o?.key ?? d.name),
|
|
137
|
+
sql: d.step.sql,
|
|
138
|
+
rule: d.step.rule,
|
|
139
|
+
class: d.step.class,
|
|
140
|
+
transactional: false,
|
|
141
|
+
...(change?.destructive ? { destructive: true } : {}),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return { dialect: "clickhouse", defaultDatabase, topology: topologyLabel(topology), steps, refused: steps.some((s) => s.kind !== "statement"), hints: diff.hints };
|
|
145
|
+
}
|
|
146
|
+
// ── Postgres ───────────────────────────────────────────────────────────
|
|
147
|
+
function postgresStatements(beforeJson, afterJson, options) {
|
|
148
|
+
const defaultSchema = options.defaultSchema ?? "public";
|
|
149
|
+
const env = options.env ?? "<env>";
|
|
150
|
+
const major = options.major ?? buildMajor(afterJson) ?? buildMajor(beforeJson) ?? POSTGRES_LATEST_MAJOR;
|
|
151
|
+
const before = postgresDeclared(beforeJson, defaultSchema);
|
|
152
|
+
const after = postgresDeclared(afterJson, defaultSchema);
|
|
153
|
+
const keyed = (objs) => objs.map((o) => ({ key: o.exportName, canonical: o.canonical }));
|
|
154
|
+
const current = keyed(before);
|
|
155
|
+
const diff = diffPgSchemas(current, keyed(after), { major });
|
|
156
|
+
const plan = planPgStatements({
|
|
157
|
+
declared: after,
|
|
158
|
+
changes: diff.changes,
|
|
159
|
+
current,
|
|
160
|
+
keyOf: (o) => o.exportName,
|
|
161
|
+
major,
|
|
162
|
+
allowDestructive: true,
|
|
163
|
+
...(options.marker ? { marker: options.marker } : {}),
|
|
164
|
+
});
|
|
165
|
+
const isDestructive = (changes, rule) => changes.some((c) => c.rule === rule && c.destructive);
|
|
166
|
+
const steps = [];
|
|
167
|
+
for (const entry of plan.objects) {
|
|
168
|
+
const where = at(entry.obj.exportName, entry.obj.type, entry.obj.name);
|
|
169
|
+
switch (entry.verdict) {
|
|
170
|
+
case "refused": {
|
|
171
|
+
const handled = new Set();
|
|
172
|
+
for (const op of migrationOpSuggestions(entry.refused, new Map([[entry.obj.exportName, entry.obj.canonical]]), env, defaultSchema)) {
|
|
173
|
+
const made = entry.refused.filter((c) => {
|
|
174
|
+
const t = migrationTarget(c, entry.obj.canonical, defaultSchema);
|
|
175
|
+
return t !== undefined && t.table === op.table && t.column === op.column;
|
|
176
|
+
});
|
|
177
|
+
for (const c of made)
|
|
178
|
+
handled.add(c);
|
|
179
|
+
steps.push({
|
|
180
|
+
kind: "op",
|
|
181
|
+
...where,
|
|
182
|
+
op: "PostgresMigrationOp",
|
|
183
|
+
importPath: "@intentius/chant-lexicon-sql/postgres",
|
|
184
|
+
options: { name: op.name, env: op.env, table: op.table, column: op.column },
|
|
185
|
+
declaration: op.declaration,
|
|
186
|
+
changes: made.map(statementChange),
|
|
187
|
+
detail: entry.detail,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const rest = entry.changes.filter((c) => !handled.has(c));
|
|
191
|
+
if (rest.length > 0)
|
|
192
|
+
steps.push(manual(where, rest, handled.size > 0 ? `the applier sends nothing for ${entry.obj.name} while a change to it is refused; these changes are not made by the Op: ${entry.detail}` : entry.detail));
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
case "unsupported":
|
|
196
|
+
steps.push(manual(where, entry.changes, entry.detail));
|
|
197
|
+
continue;
|
|
198
|
+
case "foreign":
|
|
199
|
+
case "withheld":
|
|
200
|
+
continue; // not reached offline: a build declares no foreign object, and destructive changes are planned
|
|
201
|
+
}
|
|
202
|
+
for (const s of entry.steps) {
|
|
203
|
+
steps.push({
|
|
204
|
+
kind: "statement",
|
|
205
|
+
...where,
|
|
206
|
+
sql: s.sql,
|
|
207
|
+
rule: s.rule ?? (s.class === "create" ? "SQLPG200" : (entry.changes[0]?.rule ?? "SQLPG200")),
|
|
208
|
+
class: s.class,
|
|
209
|
+
transactional: s.transactional,
|
|
210
|
+
...(s.rule === "SQLPG204" || isDestructive(entry.changes, s.rule) ? { destructive: true } : {}),
|
|
211
|
+
...(s.precheck ? { precheck: { sql: s.precheck.sql, detail: s.precheck.detail } } : {}),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Privileges (#3681), after the objects they are on: one statement each, under the declaration behind it.
|
|
216
|
+
const afterByExport = new Map(after.map((o) => [o.exportName, o]));
|
|
217
|
+
for (const c of diff.access ?? []) {
|
|
218
|
+
const decl = c.exports.map((e) => afterByExport.get(e)).find((o) => o !== undefined);
|
|
219
|
+
const where = decl ? at(decl.exportName, decl.type, decl.name) : at(c.change.object, c.target.kind === "default" ? POSTGRES_ENTITY_TYPES.defaultPrivileges : POSTGRES_ENTITY_TYPES.grant, c.change.object.slice(4));
|
|
220
|
+
for (const sql of c.sql)
|
|
221
|
+
steps.push({ kind: "statement", ...where, sql, rule: c.change.rule, class: c.change.class, transactional: true });
|
|
222
|
+
}
|
|
223
|
+
const beforeByExport = new Map(before.map((o) => [o.exportName, o]));
|
|
224
|
+
for (const d of plan.drops) {
|
|
225
|
+
const o = beforeByExport.get(d.key);
|
|
226
|
+
const change = diff.changes.find((c) => c.object === d.key && (c.rule === "SQLPG270" || c.rule === "SQLPG242"));
|
|
227
|
+
steps.push({
|
|
228
|
+
kind: "statement",
|
|
229
|
+
...at(d.key, o?.type ?? POSTGRES_ENTITY_TYPES[d.kind], o?.name ?? d.name),
|
|
230
|
+
sql: d.step.sql,
|
|
231
|
+
rule: d.step.rule ?? "SQLPG270",
|
|
232
|
+
class: d.step.class,
|
|
233
|
+
transactional: d.step.transactional,
|
|
234
|
+
...(change?.destructive ? { destructive: true } : {}),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return { dialect: "postgres", defaultSchema, major, steps, refused: steps.some((s) => s.kind !== "statement"), hints: diff.hints };
|
|
238
|
+
}
|
|
239
|
+
const manual = (where, changes, detail) => ({ kind: "manual", ...where, changes: changes.map(statementChange), detail });
|
|
240
|
+
function buildMajor(json) {
|
|
241
|
+
const raw = JSON.parse(json);
|
|
242
|
+
return pgBuildMajor(raw.sql && typeof raw.sql === "object" ? JSON.stringify(raw.sql) : json);
|
|
243
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `clickhouseApply`, the Op activity for the ClickHouse applier (#3208), and
|
|
3
|
+
* `toApplyResult`, its projection onto core's apply envelope (#1446).
|
|
4
|
+
*
|
|
5
|
+
* It reads a build's output (`chant build -o dist/schema.json`), binds the
|
|
6
|
+
* environment's server the way observation and import do
|
|
7
|
+
* (`sql.profiles.<env>`, else `CLICKHOUSE_URL`; ../../clickhouse/live/bind.ts),
|
|
8
|
+
* and hands the declarations to `applyClickHouse`
|
|
9
|
+
* (../../clickhouse/apply/apply.ts), which holds the decisions.
|
|
10
|
+
*
|
|
11
|
+
* The ownership marker is the project's: `ownership.stack` and
|
|
12
|
+
* `ownership.env` from `chant.config.ts`, or `stack` / `ownershipEnv` passed
|
|
13
|
+
* in. Without a stack the apply still runs, stamping only the managed-by
|
|
14
|
+
* marker, and a prune declines (as `not-prunable`) rather than drop another
|
|
15
|
+
* project's tables.
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { notAttemptedOutcome, resolveOwnershipMarker } from "../../core/apply.js";
|
|
19
|
+
import { bindClickHouse, ClickHouseBindingError, classifyClickHouseFailure } from "../../clickhouse/live/bind.js";
|
|
20
|
+
import { applyClickHouse, declaredObjects, planRefs } from "../../clickhouse/apply/apply.js";
|
|
21
|
+
export { toApplyResult } from "../../core/apply.js";
|
|
22
|
+
async function loadConfig(cwd) {
|
|
23
|
+
try {
|
|
24
|
+
const { loadChantConfig } = await import("@intentius/chant/config");
|
|
25
|
+
return (await loadChantConfig(cwd)).config;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** The marker to stamp and prune by: the arguments, else the project's `ownership` config. */
|
|
32
|
+
export function resolveMarker(args, config) {
|
|
33
|
+
return resolveOwnershipMarker(args, config, "clickhouse apply");
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Apply a sql build to the environment's ClickHouse server. A server that
|
|
37
|
+
* cannot be bound, or refuses the credentials on the first read, returns
|
|
38
|
+
* every declared object as not attempted, with the reason.
|
|
39
|
+
*/
|
|
40
|
+
export async function clickhouseApply(args, signal, deps = {}) {
|
|
41
|
+
const config = deps.config ?? (await loadConfig(args.cwd ?? process.cwd()));
|
|
42
|
+
const marker = resolveMarker(args, config);
|
|
43
|
+
const json = readFileSync(args.buildPath, "utf8");
|
|
44
|
+
const log = deps.log ?? ((line) => console.log(line));
|
|
45
|
+
let target;
|
|
46
|
+
try {
|
|
47
|
+
target = await bindClickHouse({ ...(args.environment !== undefined ? { environment: args.environment } : {}), config: config ?? {}, ...(deps.env ? { env: deps.env } : {}) });
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (!(err instanceof ClickHouseBindingError))
|
|
51
|
+
throw err;
|
|
52
|
+
return refused(declaredObjects(json), err.unresolved.reason, err.unresolved.detail);
|
|
53
|
+
}
|
|
54
|
+
const declared = declaredObjects(json, target.defaultDatabase, target.topology);
|
|
55
|
+
let outcome;
|
|
56
|
+
try {
|
|
57
|
+
outcome = await applyClickHouse(target, declared, {
|
|
58
|
+
...(marker ? { marker } : {}),
|
|
59
|
+
...(args.prune ? { prune: true } : {}),
|
|
60
|
+
...(args.mutationTimeoutMs !== undefined ? { mutationTimeoutMs: args.mutationTimeoutMs } : {}),
|
|
61
|
+
...(signal ? { signal } : {}),
|
|
62
|
+
log,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
// A server that never answered the first read was not written to at all.
|
|
67
|
+
const why = classifyClickHouseFailure(err);
|
|
68
|
+
if (why.reason === "no-credentials" && !(err instanceof Error && err.name === "ClickHouseApplyError")) {
|
|
69
|
+
return { ...refused(declared, "no-credentials", why.detail), target: target.endpoint.url, source: target.source };
|
|
70
|
+
}
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
for (const a of outcome.applied)
|
|
74
|
+
log(`${a.action}: ${a.kind}/${a.name}`);
|
|
75
|
+
for (const p of outcome.pruned)
|
|
76
|
+
log(`pruned: ${p.kind}/${p.name}`);
|
|
77
|
+
for (const n of outcome.notAttempted)
|
|
78
|
+
log(`not attempted: ${n.kind}/${n.name}: ${n.reason}${n.detail ? ` (${n.detail})` : ""}`);
|
|
79
|
+
return outcome;
|
|
80
|
+
function refused(objects, reason, detail) {
|
|
81
|
+
return notAttemptedOutcome(planRefs(objects), reason, detail);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sql lexicon's emulator capability (#3208): the pinned
|
|
3
|
+
* `clickhouse/clickhouse-server`, the same image and digest generation and
|
|
4
|
+
* the live tests run (../../spec/pin.ts), so `chant emulator up` boots a real
|
|
5
|
+
* server rather than a stand-in.
|
|
6
|
+
*
|
|
7
|
+
* `upstream` is left off on purpose. The freshness check compares the image
|
|
8
|
+
* tag against the repo's latest GitHub release, and ClickHouse's latest
|
|
9
|
+
* release is a monthly one, not the LTS line the pin follows; the lexicon's
|
|
10
|
+
* `upstreamPin` already tracks the LTS tags (`-lts`).
|
|
11
|
+
*/
|
|
12
|
+
import { emulatorLifecycle } from "@intentius/chant/op";
|
|
13
|
+
import { clickhouseImage } from "../../spec/pin.js";
|
|
14
|
+
/**
|
|
15
|
+
* The server's UUID (#3673), read in the container and through the endpoint
|
|
16
|
+
* `status` prints, so a server of another origin on the same port is told
|
|
17
|
+
* apart from the emulator.
|
|
18
|
+
*/
|
|
19
|
+
export const CLICKHOUSE_EMULATOR_IDENTITY = {
|
|
20
|
+
server: "ClickHouse",
|
|
21
|
+
command: ["clickhouse-client", "-q", "SELECT serverUUID()"],
|
|
22
|
+
async probe(endpoint) {
|
|
23
|
+
const url = new URL(endpoint);
|
|
24
|
+
url.searchParams.set("query", "SELECT serverUUID(), version() FORMAT TSV");
|
|
25
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
|
|
26
|
+
const body = (await res.text()).trim();
|
|
27
|
+
if (!res.ok)
|
|
28
|
+
throw new Error(`HTTP ${res.status}: ${body.split("\n")[0]}`);
|
|
29
|
+
const [id = "", version = "(unknown version)"] = body.split("\t");
|
|
30
|
+
return { id, label: `ClickHouse ${version}` };
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
export const CLICKHOUSE_EMULATOR_SPEC = {
|
|
34
|
+
name: "chant-clickhouse",
|
|
35
|
+
image: clickhouseImage(),
|
|
36
|
+
containerPort: 8123,
|
|
37
|
+
healthPath: "/ping",
|
|
38
|
+
// The image disables network access for the passwordless default user
|
|
39
|
+
// unless this is set; a local server is reached as `default` with no password.
|
|
40
|
+
runArgs: ["-e", "CLICKHOUSE_SKIP_USER_SETUP=1"],
|
|
41
|
+
identity: CLICKHOUSE_EMULATOR_IDENTITY,
|
|
42
|
+
credentials: "user default, no password",
|
|
43
|
+
};
|
|
44
|
+
/** `CLICKHOUSE_URL` is the variable binding reads when no `sql.profiles.<env>` names a server. */
|
|
45
|
+
export const CLICKHOUSE_EMULATOR = {
|
|
46
|
+
spec: CLICKHOUSE_EMULATOR_SPEC,
|
|
47
|
+
env: (endpoint) => ({ CLICKHOUSE_URL: endpoint }),
|
|
48
|
+
};
|
|
49
|
+
const lifecycle = emulatorLifecycle(CLICKHOUSE_EMULATOR_SPEC);
|
|
50
|
+
/** Boot the pinned ClickHouse server in Docker and return its endpoint. */
|
|
51
|
+
export const clickhouseUp = (args = {}, signal) => lifecycle.up(args, signal);
|
|
52
|
+
/** Stop and remove it (a no-op when it is gone). */
|
|
53
|
+
export const clickhouseDown = (args = {}, signal) => lifecycle.down(args, signal);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rebuild migration's activities (#3198), one per step of
|
|
3
|
+
* `ClickHouseRebuildOp` (`../../clickhouse/rebuild/op.ts`). Each takes the
|
|
4
|
+
* same arguments, reads the build's declaration of the table and binds the
|
|
5
|
+
* environment's server the way the applier does (`./clickhouse-apply.ts`),
|
|
6
|
+
* then runs its step (`../../clickhouse/rebuild/`), which re-reads the server.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
import { currentOpRun, parseDuration } from "@intentius/chant/op";
|
|
11
|
+
import { bindClickHouse } from "../../clickhouse/live/bind.js";
|
|
12
|
+
import { declaredObjects } from "../../clickhouse/apply/apply.js";
|
|
13
|
+
import { declaredTable, observeRebuild, rebuildPlanDigest, RebuildRefusal } from "../../clickhouse/rebuild/observe.js";
|
|
14
|
+
import { backfill } from "../../clickhouse/rebuild/backfill.js";
|
|
15
|
+
import { RebuildVerificationError, verifyRebuild } from "../../clickhouse/rebuild/verify.js";
|
|
16
|
+
import { compensate, createNewTable, dropOldTable, retainPlan, startDualWrite, swapTables, } from "../../clickhouse/rebuild/steps.js";
|
|
17
|
+
import { CLASSIFIER_RULES } from "../../clickhouse/plan/rules.js";
|
|
18
|
+
import { clusterSharding } from "../../clickhouse/rebuild/shards.js";
|
|
19
|
+
import { resolveMarker } from "./clickhouse-apply.js";
|
|
20
|
+
import { statementChange } from "../../core/statements.js";
|
|
21
|
+
import { clickhouseRebuildPart } from "../../change-set.js";
|
|
22
|
+
async function loadConfig(cwd) {
|
|
23
|
+
try {
|
|
24
|
+
const { loadChantConfig } = await import("@intentius/chant/config");
|
|
25
|
+
return (await loadChantConfig(cwd)).config;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Bind the server and read the declaration: what every step starts from. */
|
|
32
|
+
export async function rebuildRun(args, signal, deps) {
|
|
33
|
+
const cwd = args.cwd ?? process.cwd();
|
|
34
|
+
const config = deps.config ?? (await loadConfig(cwd));
|
|
35
|
+
const marker = resolveMarker(args, config);
|
|
36
|
+
const target = await bindClickHouse({ ...(args.environment !== undefined ? { environment: args.environment } : {}), config: config ?? {}, ...(deps.env ? { env: deps.env } : {}) });
|
|
37
|
+
const declared = declaredTable(declaredObjects(readFileSync(resolve(cwd, args.buildPath), "utf8"), target.defaultDatabase, target.topology), args.table);
|
|
38
|
+
const runId = currentOpRun()?.runId;
|
|
39
|
+
const sharding = await clusterSharding(target);
|
|
40
|
+
return {
|
|
41
|
+
target,
|
|
42
|
+
declared,
|
|
43
|
+
...(marker ? { marker } : {}),
|
|
44
|
+
dualWrite: args.dualWrite,
|
|
45
|
+
retainMs: parseDuration(args.retain ?? "7d"),
|
|
46
|
+
...(args.mutationTimeout ? { mutationTimeoutMs: parseDuration(args.mutationTimeout) } : {}),
|
|
47
|
+
...(args.replicaTimeout ? { replicaTimeoutMs: parseDuration(args.replicaTimeout) } : {}),
|
|
48
|
+
...(args.cutoverTimeout ? { cutoverTimeoutMs: parseDuration(args.cutoverTimeout) } : {}),
|
|
49
|
+
log: deps.log ?? ((line) => console.log(line)),
|
|
50
|
+
...(signal ? { signal } : {}),
|
|
51
|
+
...(runId ? { runId } : {}),
|
|
52
|
+
...(sharding ? { sharding } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* With `onFailure: "keep"` (#3658) no onFailure drops the new table, so a
|
|
57
|
+
* step that fails says what a person does instead: a refusal or a failed
|
|
58
|
+
* verification would otherwise fail every run after it the same way.
|
|
59
|
+
*/
|
|
60
|
+
async function noting(args, fn) {
|
|
61
|
+
try {
|
|
62
|
+
return await fn();
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (args.keepOnFailure && (err instanceof RebuildRefusal || err instanceof RebuildVerificationError)) {
|
|
66
|
+
err.message +=
|
|
67
|
+
` This rebuild keeps its new table on failure (onFailure: "keep"), so nothing drops it: ` +
|
|
68
|
+
`drop ${args.table}__chant_new and ${args.table}__chant_dual by hand to start again from a new table.`;
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Plan: classify the table's change against the server and say how far the rebuild has got. */
|
|
74
|
+
export async function clickhouseRebuildPlan(args, signal, deps = {}) {
|
|
75
|
+
return noting(args, () => plan(args, signal, deps));
|
|
76
|
+
}
|
|
77
|
+
async function plan(args, signal, deps) {
|
|
78
|
+
const run = await rebuildRun(args, signal, deps);
|
|
79
|
+
const o = await observeRebuild(run.target, run.declared, run.marker);
|
|
80
|
+
const changes = o.changes.map((c) => `${c.rule} ${c.field}${c.before !== undefined || c.after !== undefined ? `: ${c.before ?? "none"} -> ${c.after ?? "none"}` : ""}`);
|
|
81
|
+
const rebuilds = o.changes.filter((c) => c.class === "rebuild").map((c) => `${c.rule} ${CLASSIFIER_RULES[c.rule].title}`);
|
|
82
|
+
const summary = o.state === "rebuild"
|
|
83
|
+
? `${o.names.key} needs a rebuild (${rebuilds.join("; ")}); ${o.copied.length} column(s) copied${o.newTable ? ", new table already made" : ""}`
|
|
84
|
+
: o.state === "swapped"
|
|
85
|
+
? `${o.names.key} has been swapped; the old table is kept as ${o.names.database}.${o.names.oldName}`
|
|
86
|
+
: `${o.names.key} holds its declaration; nothing to rebuild`;
|
|
87
|
+
run.log(`-- ${summary}`);
|
|
88
|
+
for (const c of changes)
|
|
89
|
+
run.log(`-- ${c}`);
|
|
90
|
+
const classified = o.changes.map(statementChange);
|
|
91
|
+
const result = { state: o.state, table: o.names.key, ...(o.state === "rebuild" ? { planDigest: rebuildPlanDigest(o, run.dualWrite) } : {}), changes, classified };
|
|
92
|
+
const part = clickhouseRebuildPart({ member: o.names.key, plan: result, ...(args.environment !== undefined ? { environment: args.environment } : {}) });
|
|
93
|
+
return { ...result, part, summary };
|
|
94
|
+
}
|
|
95
|
+
export async function clickhouseRebuildCreate(args, signal, deps = {}) {
|
|
96
|
+
return noting(args, async () => createNewTable(await rebuildRun(args, signal, deps)));
|
|
97
|
+
}
|
|
98
|
+
export async function clickhouseRebuildDualWrite(args, signal, deps = {}) {
|
|
99
|
+
return noting(args, async () => startDualWrite(await rebuildRun(args, signal, deps)));
|
|
100
|
+
}
|
|
101
|
+
export async function clickhouseRebuildBackfill(args, signal, deps = {}) {
|
|
102
|
+
return noting(args, async () => backfill(await rebuildRun(args, signal, deps), deps.backfill));
|
|
103
|
+
}
|
|
104
|
+
export async function clickhouseRebuildVerify(args, signal, deps = {}) {
|
|
105
|
+
return noting(args, async () => verifyRebuild(await rebuildRun(args, signal, deps)));
|
|
106
|
+
}
|
|
107
|
+
export async function clickhouseRebuildSwap(args, signal, deps = {}) {
|
|
108
|
+
return noting(args, async () => swapTables(await rebuildRun(args, signal, deps)));
|
|
109
|
+
}
|
|
110
|
+
export async function clickhouseRebuildRetain(args, signal, deps = {}) {
|
|
111
|
+
return retainPlan(await rebuildRun(args, signal, deps));
|
|
112
|
+
}
|
|
113
|
+
export async function clickhouseRebuildDrop(args, signal, deps = {}) {
|
|
114
|
+
return dropOldTable(await rebuildRun(args, signal, deps));
|
|
115
|
+
}
|
|
116
|
+
/** onFailure: drop the new table and the dual-write view. */
|
|
117
|
+
export async function clickhouseRebuildCompensate(args, signal, deps = {}) {
|
|
118
|
+
return compensate(await rebuildRun(args, signal, deps));
|
|
119
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { sqlReceiptActivities } from "../../receipts.js";
|
|
2
|
+
export { clickhouseApply, toApplyResult, resolveMarker } from "./clickhouse-apply.js";
|
|
3
|
+
export { clickhouseUp, clickhouseDown, CLICKHOUSE_EMULATOR, CLICKHOUSE_EMULATOR_SPEC } from "./clickhouse-emulator.js";
|
|
4
|
+
export { postgresApply } from "./postgres-apply.js";
|
|
5
|
+
export { postgresUp, postgresDown, POSTGRES_EMULATOR, POSTGRES_EMULATOR_SPEC } from "./postgres-emulator.js";
|
|
6
|
+
// effect() steps (#3657): their receipts are kept on the environment's
|
|
7
|
+
// database server (../../receipts.ts, `chant_receipts.receipts`), and
|
|
8
|
+
// `sqlExec` runs a batch's SQL there. The receipt activities are fallbacks
|
|
9
|
+
// (core's ACTIVITY_FALLBACKS): those names are global to a run, and a project
|
|
10
|
+
// that also lists aws or k8s keeps its receipts in that lexicon's row.
|
|
11
|
+
/** The activities this module provides only when no other configured lexicon does. */
|
|
12
|
+
export const ACTIVITY_FALLBACKS = ["receiptRead", "receiptWrite", "receiptStaleness"];
|
|
13
|
+
// Bound per call: the environment (`chant run --env`) and the project are read when a step runs.
|
|
14
|
+
export const receiptRead = (args, signal) => sqlReceiptActivities().receiptRead(args, signal);
|
|
15
|
+
export const receiptWrite = (args, signal) => sqlReceiptActivities().receiptWrite(args, signal);
|
|
16
|
+
export const receiptStaleness = (args, signal) => sqlReceiptActivities().receiptStaleness(args, signal);
|
|
17
|
+
export { sqlExec } from "./sql-exec.js";
|
|
18
|
+
// The rebuild migration (#3198): one activity per step of ClickHouseRebuildOp
|
|
19
|
+
// (../../clickhouse/rebuild/op.ts). Its receipts are kept in ClickHouse and
|
|
20
|
+
// reached by the backfill directly, with the copy's own identity.
|
|
21
|
+
export { clickhouseRebuildPlan, clickhouseRebuildCreate, clickhouseRebuildDualWrite, clickhouseRebuildBackfill, clickhouseRebuildVerify, clickhouseRebuildSwap, clickhouseRebuildRetain, clickhouseRebuildDrop, clickhouseRebuildCompensate, } from "./clickhouse-rebuild.js";
|
|
22
|
+
// The Postgres expand-and-contract migration (#3281): one activity per step of
|
|
23
|
+
// PostgresMigrationOp (../../postgres/migrate/op.ts). Its receipts are kept in
|
|
24
|
+
// the migrated table's schema and written by the backfill directly, in each
|
|
25
|
+
// batch's own transaction.
|
|
26
|
+
export { postgresMigrationPlan, postgresMigrationExpand, postgresMigrationDualWrite, postgresMigrationBackfill, postgresMigrationCarry, postgresMigrationVerify, postgresMigrationSwitch, postgresMigrationRetain, postgresMigrationContract, postgresMigrationCompensate, } from "./postgres-migration.js";
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `postgresApply`, the Op activity for the Postgres applier (#3280), and
|
|
3
|
+
* `toApplyResult`, its projection onto core's apply envelope (#1446), the
|
|
4
|
+
* same projection the ClickHouse applier exports.
|
|
5
|
+
*
|
|
6
|
+
* It reads a build's output (`chant build -o dist/schema.json`), binds the
|
|
7
|
+
* environment's server the way observation, import and `chant sql plan` do
|
|
8
|
+
* (`sql.profiles.<env>`, else `POSTGRES_URL`; ../../postgres/live/bind.ts),
|
|
9
|
+
* connects with node-postgres (loaded only now), and hands the declarations
|
|
10
|
+
* to `applyPostgres` (../../postgres/apply/apply.ts), which holds the
|
|
11
|
+
* decisions.
|
|
12
|
+
*
|
|
13
|
+
* The server's major is the build output's `postgresMajor` (#3315), else
|
|
14
|
+
* `sql.postgresMajor`, else the newest pinned one.
|
|
15
|
+
*
|
|
16
|
+
* The ownership marker is the project's: `ownership.stack` and
|
|
17
|
+
* `ownership.env` from `chant.config.ts`, or `stack` / `ownershipEnv` passed
|
|
18
|
+
* in. Without a stack the apply still runs, stamping only the managed-by
|
|
19
|
+
* marker, and a prune declines (as `not-prunable`) rather than drop another
|
|
20
|
+
* project's objects. The timeouts are the profile's, else the applier's
|
|
21
|
+
* defaults, and an argument overrides either.
|
|
22
|
+
*/
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
24
|
+
import { notAttemptedOutcome, resolveOwnershipMarker } from "../../core/apply.js";
|
|
25
|
+
import { classifyPostgresFailure, PostgresBindingError, resolveBoundTarget } from "../../postgres/live/bind.js";
|
|
26
|
+
import { connectPostgres } from "../../postgres/live/client.js";
|
|
27
|
+
import { DEFAULT_POSTGRES_APPLY_TIMEOUTS, applyPostgres, declaredObjects, planRefs, } from "../../postgres/apply/apply.js";
|
|
28
|
+
/** The build output's `postgresMajor`, when it carries one. */
|
|
29
|
+
export function buildMajor(json) {
|
|
30
|
+
try {
|
|
31
|
+
const raw = JSON.parse(json);
|
|
32
|
+
const m = raw.sql && typeof raw.sql === "object" ? raw.sql.postgresMajor : raw.postgresMajor;
|
|
33
|
+
return typeof m === "number" ? m : undefined;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function loadConfig(cwd) {
|
|
40
|
+
try {
|
|
41
|
+
const { loadChantConfig } = await import("@intentius/chant/config");
|
|
42
|
+
return (await loadChantConfig(cwd)).config;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Apply a sql build to the environment's Postgres server. A server that
|
|
50
|
+
* cannot be bound, or refuses the credentials when connecting, returns every
|
|
51
|
+
* declared object as not attempted, with the reason.
|
|
52
|
+
*/
|
|
53
|
+
export async function postgresApply(args, signal, deps = {}) {
|
|
54
|
+
const config = deps.config ?? (await loadConfig(args.cwd ?? process.cwd()));
|
|
55
|
+
const marker = resolveOwnershipMarker(args, config, "postgres apply");
|
|
56
|
+
const json = readFileSync(args.buildPath, "utf8");
|
|
57
|
+
const log = deps.log ?? ((line) => console.log(line));
|
|
58
|
+
const timeouts = {
|
|
59
|
+
...(args.lockTimeoutMs !== undefined ? { lockTimeoutMs: args.lockTimeoutMs } : {}),
|
|
60
|
+
...(args.statementTimeoutMs !== undefined ? { statementTimeoutMs: args.statementTimeoutMs } : {}),
|
|
61
|
+
...(args.scanTimeoutMs !== undefined ? { scanTimeoutMs: args.scanTimeoutMs } : {}),
|
|
62
|
+
};
|
|
63
|
+
// The major the build targets (#3315): what an apply does can differ by major (SET EXPRESSION from 17, SET STORAGE DEFAULT from 16).
|
|
64
|
+
const major = buildMajor(json) ?? config?.sql?.postgresMajor;
|
|
65
|
+
let target;
|
|
66
|
+
try {
|
|
67
|
+
target = await resolveBoundTarget({ ...(args.environment !== undefined ? { environment: args.environment } : {}), config: config ?? {}, ...(deps.env ? { env: deps.env } : {}) });
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
if (!(err instanceof PostgresBindingError))
|
|
71
|
+
throw err;
|
|
72
|
+
return refused(declaredObjects(json), undefined, err.unresolved.reason, err.unresolved.detail);
|
|
73
|
+
}
|
|
74
|
+
const declared = declaredObjects(json, target.defaultSchema);
|
|
75
|
+
let client;
|
|
76
|
+
try {
|
|
77
|
+
client = await (deps.connect ?? ((e) => connectPostgres(e, { applicationName: "chant apply" })))(target.endpoint);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
// A server that refused the credentials was not written to at all; one that cannot be reached is an error.
|
|
81
|
+
const why = classifyPostgresFailure(err);
|
|
82
|
+
if (why.reason === "no-credentials")
|
|
83
|
+
return refused(declared, target, "no-credentials", why.detail);
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
let outcome;
|
|
87
|
+
try {
|
|
88
|
+
outcome = await applyPostgres(client, target, declared, {
|
|
89
|
+
...(marker ? { marker } : {}),
|
|
90
|
+
...(args.prune ? { prune: true } : {}),
|
|
91
|
+
timeouts,
|
|
92
|
+
...(major !== undefined ? { major } : {}),
|
|
93
|
+
...(signal ? { signal } : {}),
|
|
94
|
+
...(deps.readLive ? { readLive: deps.readLive } : {}),
|
|
95
|
+
...(deps.serverNormalize ? { serverNormalize: deps.serverNormalize } : {}),
|
|
96
|
+
log,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// The first read is the catalog read: a refusal there means nothing was written.
|
|
101
|
+
const why = classifyPostgresFailure(err);
|
|
102
|
+
if (why.reason === "no-credentials" && !(err instanceof Error && err.name === "PostgresApplyError")) {
|
|
103
|
+
return refused(declared, target, "no-credentials", why.detail);
|
|
104
|
+
}
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await client.end();
|
|
109
|
+
}
|
|
110
|
+
for (const a of outcome.applied)
|
|
111
|
+
log(`${a.action}: ${a.kind}/${a.name}`);
|
|
112
|
+
for (const p of outcome.pruned)
|
|
113
|
+
log(`pruned: ${p.kind}/${p.name}`);
|
|
114
|
+
for (const n of outcome.notAttempted)
|
|
115
|
+
log(`not attempted: ${n.kind}/${n.name}: ${n.reason}${n.detail ? ` (${n.detail})` : ""}`);
|
|
116
|
+
return outcome;
|
|
117
|
+
function refused(objects, t, reason, detail) {
|
|
118
|
+
return {
|
|
119
|
+
...notAttemptedOutcome(planRefs(objects), reason, detail),
|
|
120
|
+
...(t ? { target: t.endpoint.url, source: t.source } : {}),
|
|
121
|
+
timeouts: { ...DEFAULT_POSTGRES_APPLY_TIMEOUTS, ...t?.timeouts, ...timeouts },
|
|
122
|
+
statements: [],
|
|
123
|
+
transactions: [],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|