@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
package/dist/receipts.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sql lexicon's `effect()` receipt store (#3657): where the receipts of a
|
|
3
|
+
* project that uses only the sql lexicon are kept, on the database server the
|
|
4
|
+
* environment names.
|
|
5
|
+
*
|
|
6
|
+
* ClickHouse chant_receipts.receipts
|
|
7
|
+
* Postgres chant_receipts.receipts (or <receiptsSchema>.__chant_receipts)
|
|
8
|
+
*
|
|
9
|
+
* The op activities module binds it as `receiptRead`, `receiptWrite` and
|
|
10
|
+
* `receiptStaleness` (`./op/activities/index.ts`), listed as fallbacks: a
|
|
11
|
+
* project that also configures aws or k8s keeps its receipts in that
|
|
12
|
+
* lexicon's receipt row, in whichever order `lexicons` lists them.
|
|
13
|
+
*
|
|
14
|
+
* Which server: the environment is the one `chant run --env` sets
|
|
15
|
+
* (`CHANT_ENV`), else a literal `ownership.env`. Its `sql.profiles.<env>`
|
|
16
|
+
* binds the server, a `postgres://` URL making it Postgres; with no profile,
|
|
17
|
+
* `CLICKHOUSE_URL` or `POSTGRES_URL`, picked by `sql.dialect`. A receipt's
|
|
18
|
+
* address is `<stack>/<env>/<effect>`, from `ownership.stack` and that
|
|
19
|
+
* environment, the same identity the rebuild's and the column migration's
|
|
20
|
+
* receipts carry (`./core/receipts.ts`).
|
|
21
|
+
*
|
|
22
|
+
* Both tables carry chant's ownership trailer with the `receipts` key in their
|
|
23
|
+
* comment (the Postgres schema too), so plan, import and prune leave them out.
|
|
24
|
+
*
|
|
25
|
+
* A caller that runs its own Op over a server it has already bound (a
|
|
26
|
+
* migration runner, say) passes that server instead: a ClickHouse endpoint,
|
|
27
|
+
* or a Postgres connection, whose receipt writes then commit or roll back
|
|
28
|
+
* with whatever transaction the caller has open on it.
|
|
29
|
+
*/
|
|
30
|
+
import { receiptActivities } from "@intentius/chant/op/receipt-store";
|
|
31
|
+
import { OWNERSHIP_MANAGED_BY_VALUE } from "@intentius/chant/ownership";
|
|
32
|
+
import { COMMENT_OWNERSHIP_KEYS, RECEIPTS_TRAILER_KEY } from "./core/ownership.js";
|
|
33
|
+
/** The database (ClickHouse) or schema (Postgres) the receipts are kept in. */
|
|
34
|
+
export const SQL_RECEIPTS_SCHEMA = "chant_receipts";
|
|
35
|
+
/** The table in it. */
|
|
36
|
+
export const SQL_RECEIPTS_TABLE = "receipts";
|
|
37
|
+
/** The table's name in a schema of the user's own (`receiptsSchema`), as the column migration names it. */
|
|
38
|
+
export const SQL_RECEIPTS_TABLE_BESIDE = "__chant_receipts";
|
|
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
|
+
const isPostgresUrl = (url) => /^postgres(ql)?:\/\//i.test(url);
|
|
49
|
+
/** The dialect an environment's receipts are kept in: its profile's URL, else `sql.dialect`, else whichever server variable is set. */
|
|
50
|
+
export function receiptDialect(config, environment, env) {
|
|
51
|
+
const profile = environment !== undefined ? config?.sql?.profiles?.[environment] : undefined;
|
|
52
|
+
if (profile)
|
|
53
|
+
return isPostgresUrl(profile.url) ? "postgres" : "clickhouse";
|
|
54
|
+
const dialects = config?.sql?.dialect === undefined ? [] : Array.isArray(config.sql.dialect) ? config.sql.dialect : [config.sql.dialect];
|
|
55
|
+
if (dialects.length === 1)
|
|
56
|
+
return dialects[0] === "postgres" ? "postgres" : "clickhouse";
|
|
57
|
+
if (!env.CLICKHOUSE_URL && env.POSTGRES_URL)
|
|
58
|
+
return "postgres";
|
|
59
|
+
return "clickhouse";
|
|
60
|
+
}
|
|
61
|
+
/** The run's environment: the option, `CHANT_ENV`, or a literal `ownership.env`. */
|
|
62
|
+
export function environmentOf(options, config, env) {
|
|
63
|
+
if (options.environment)
|
|
64
|
+
return options.environment;
|
|
65
|
+
if (env.CHANT_ENV)
|
|
66
|
+
return env.CHANT_ENV;
|
|
67
|
+
const literal = config?.ownership?.env;
|
|
68
|
+
return typeof literal === "string" ? literal : undefined;
|
|
69
|
+
}
|
|
70
|
+
async function currentRunId() {
|
|
71
|
+
try {
|
|
72
|
+
const { currentOpRun } = await import("@intentius/chant/op");
|
|
73
|
+
return currentOpRun()?.runId;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const receiptsComment = `chant effect receipts [chant ${COMMENT_OWNERSHIP_KEYS.managedBy}=${OWNERSHIP_MANAGED_BY_VALUE} ${RECEIPTS_TRAILER_KEY}=effects]`;
|
|
80
|
+
async function resolve(options) {
|
|
81
|
+
const env = options.env ?? process.env;
|
|
82
|
+
const needsConfig = options.config === undefined && (!options.stack || (!options.clickhouse && !options.postgres) || !(options.environment || env.CHANT_ENV));
|
|
83
|
+
const config = options.config ?? (needsConfig ? await loadConfig(options.cwd ?? process.cwd()) : undefined);
|
|
84
|
+
const environment = environmentOf(options, config, env);
|
|
85
|
+
const stack = options.stack ?? (config?.ownership && config.ownership.enabled !== false ? config.ownership.stack : undefined);
|
|
86
|
+
const identity = { ...(stack ? { stack } : {}), ...(environment ? { env: environment } : {}) };
|
|
87
|
+
const runId = options.runId ?? (await currentRunId());
|
|
88
|
+
const dialect = options.postgres ? "postgres" : options.clickhouse ? "clickhouse" : receiptDialect(config, environment, env);
|
|
89
|
+
if (dialect === "clickhouse") {
|
|
90
|
+
const { clickhouseReceiptStore } = await import("./clickhouse/rebuild/receipts.js");
|
|
91
|
+
let endpoint;
|
|
92
|
+
let topology;
|
|
93
|
+
let source;
|
|
94
|
+
if (options.clickhouse) {
|
|
95
|
+
endpoint = options.clickhouse.endpoint;
|
|
96
|
+
topology = options.clickhouse.topology;
|
|
97
|
+
source = endpoint.url;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const { bindClickHouse } = await import("./clickhouse/live/bind.js");
|
|
101
|
+
const target = await bindClickHouse({ ...(environment !== undefined ? { environment } : {}), config: config ?? {}, env });
|
|
102
|
+
endpoint = target.endpoint;
|
|
103
|
+
topology = target.topology;
|
|
104
|
+
source = target.source;
|
|
105
|
+
}
|
|
106
|
+
// Created for the environment's topology: ON CLUSTER on a cluster, a Replicated database in the replicated one.
|
|
107
|
+
const store = clickhouseReceiptStore(endpoint, identity, { ...(runId ? { runId } : {}), ...(topology ? { topology } : {}) });
|
|
108
|
+
return {
|
|
109
|
+
identity,
|
|
110
|
+
dialect,
|
|
111
|
+
table: `${SQL_RECEIPTS_SCHEMA}.${SQL_RECEIPTS_TABLE}`,
|
|
112
|
+
source,
|
|
113
|
+
open: async () => ({ store, close: async () => undefined }),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const { postgresReceiptStore } = await import("./postgres/migrate/receipts.js");
|
|
117
|
+
const profile = environment !== undefined ? config?.sql?.profiles?.[environment] : undefined;
|
|
118
|
+
const schema = options.schema ?? profile?.receiptsSchema ?? SQL_RECEIPTS_SCHEMA;
|
|
119
|
+
const own = schema === SQL_RECEIPTS_SCHEMA;
|
|
120
|
+
const table = own ? SQL_RECEIPTS_TABLE : SQL_RECEIPTS_TABLE_BESIDE;
|
|
121
|
+
const storeOver = (client) => {
|
|
122
|
+
const store = postgresReceiptStore(client, schema, identity, { ...(runId ? { runId } : {}), table });
|
|
123
|
+
let ensured = false;
|
|
124
|
+
return {
|
|
125
|
+
...store,
|
|
126
|
+
async ensure() {
|
|
127
|
+
if (ensured)
|
|
128
|
+
return;
|
|
129
|
+
// chant's own schema is made here, marked so the catalog leaves it out; a schema of the user's must exist.
|
|
130
|
+
if (own) {
|
|
131
|
+
const [present] = await client.query("SELECT count(*)::int AS n FROM pg_catalog.pg_namespace WHERE nspname = $1", [SQL_RECEIPTS_SCHEMA]);
|
|
132
|
+
if (!present?.n) {
|
|
133
|
+
await client.query(`CREATE SCHEMA IF NOT EXISTS ${SQL_RECEIPTS_SCHEMA}`);
|
|
134
|
+
await client.query(`COMMENT ON SCHEMA ${SQL_RECEIPTS_SCHEMA} IS '${receiptsComment.replace(/'/g, "''")}'`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
await store.ensure();
|
|
138
|
+
ensured = true;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
if (options.postgres) {
|
|
143
|
+
const client = options.postgres;
|
|
144
|
+
const store = storeOver(client);
|
|
145
|
+
return { identity, dialect, table: `${schema}.${table}`, source: "the caller's connection", open: async () => ({ store, close: async () => undefined }) };
|
|
146
|
+
}
|
|
147
|
+
const { resolveBoundTarget } = await import("./postgres/live/bind.js");
|
|
148
|
+
const { connectPostgres } = await import("./postgres/live/client.js");
|
|
149
|
+
const target = await resolveBoundTarget({ ...(environment !== undefined ? { environment } : {}), config: config ?? {}, env });
|
|
150
|
+
return {
|
|
151
|
+
identity,
|
|
152
|
+
dialect,
|
|
153
|
+
table: `${schema}.${table}`,
|
|
154
|
+
source: target.source,
|
|
155
|
+
// One connection per read or write: a run's process holds no connection open between steps, and exits when the run ends.
|
|
156
|
+
open: async () => {
|
|
157
|
+
const client = await connectPostgres(target.endpoint, { applicationName: "chant receipts" });
|
|
158
|
+
return { store: storeOver(client), close: () => client.end() };
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The receipt store on the environment's database server. Nothing is read or
|
|
164
|
+
* connected until the first read or write; the environment, the server and
|
|
165
|
+
* the identity are resolved then, once.
|
|
166
|
+
*/
|
|
167
|
+
export function sqlReceiptStore(options = {}) {
|
|
168
|
+
let resolved;
|
|
169
|
+
const resolvedOnce = () => (resolved ?? (resolved = resolve(options)));
|
|
170
|
+
const using = async (fn) => {
|
|
171
|
+
const r = await resolvedOnce();
|
|
172
|
+
const { store, close } = await r.open();
|
|
173
|
+
try {
|
|
174
|
+
return await fn(store, r);
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
await close();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
read: (receipt) => using((s) => s.read(receipt)),
|
|
182
|
+
readAll: (prefix) => using((s) => s.readAll(prefix)),
|
|
183
|
+
async write(receipt, expectation) {
|
|
184
|
+
await using(async (s) => {
|
|
185
|
+
// Made on the first write when it is not there. A caller writing
|
|
186
|
+
// inside its own transaction calls ensure() before opening it, so a
|
|
187
|
+
// rolled-back batch does not take the table with it.
|
|
188
|
+
await s.ensure?.();
|
|
189
|
+
await s.write(receipt, expectation);
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
ensure: () => using(async (s) => s.ensure?.()),
|
|
193
|
+
async location() {
|
|
194
|
+
const r = await resolvedOnce();
|
|
195
|
+
const { receiptAddress } = await import("./core/receipts.js");
|
|
196
|
+
return { dialect: r.dialect, table: r.table, source: r.source, address: receiptAddress(r.identity, "<effect>") };
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The `receiptRead`, `receiptWrite` and `receiptStaleness` activities over
|
|
202
|
+
* {@link sqlReceiptStore}: what the op activities module exports, and what a
|
|
203
|
+
* caller running an Op of `effect()` steps with its own activity map binds.
|
|
204
|
+
*/
|
|
205
|
+
export function sqlReceiptActivities(options = {}, activityOptions) {
|
|
206
|
+
return receiptActivities(sqlReceiptStore(options), activityOptions);
|
|
207
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sql serializer.
|
|
3
|
+
*
|
|
4
|
+
* A build writes two things:
|
|
5
|
+
*
|
|
6
|
+
* - The primary output, one JSON document: the dialect, the order objects
|
|
7
|
+
* must be created in, and each object filed under its export name (its
|
|
8
|
+
* identity in chant) with its type, its parsed definition, the references it
|
|
9
|
+
* makes and its DDL. Post-synth checks, planning and the applier read this.
|
|
10
|
+
* - `clickhouse.sql` (or `postgres.sql`) beside it: every statement, in that
|
|
11
|
+
* order, as it will be sent. Written verbatim.
|
|
12
|
+
*
|
|
13
|
+
* The order is the dependency order of the references: a view after the
|
|
14
|
+
* tables it reads, a materialized view after its target, a table after its
|
|
15
|
+
* database. An object read from a `.sql` file (`./sql-files.ts`) that a
|
|
16
|
+
* template names in its text counts as referenced too. Ties break by export name, so the output is the same however the
|
|
17
|
+
* files were discovered. A reference cycle is an error naming the cycle.
|
|
18
|
+
*
|
|
19
|
+
* One build holds one dialect: the dialect is the document's, and a project
|
|
20
|
+
* whose declarations are ClickHouse and Postgres both is refused, naming one
|
|
21
|
+
* of each. Two databases of different dialects are two projects (two
|
|
22
|
+
* workspace members, #3047 question 1).
|
|
23
|
+
*
|
|
24
|
+
* A Postgres document also carries `postgresMajor`, the major the project
|
|
25
|
+
* targets (`sql.postgresMajor`, else the newest pinned major). Post-synth
|
|
26
|
+
* checks and the lock classifier read it; ClickHouse output has no such field.
|
|
27
|
+
*
|
|
28
|
+
* A clause chant's parser does not know (#3872) is kept in the statement as
|
|
29
|
+
* written and named in a build warning ({@link unparsedWarnings}).
|
|
30
|
+
*
|
|
31
|
+
* Rule ids: `SQL` for rules that hold in every dialect, `SQLCH` for the
|
|
32
|
+
* ClickHouse dialect's (#3199), `SQLPG` for Postgres's (#3289).
|
|
33
|
+
*/
|
|
34
|
+
import { isAttrRefLike } from "@intentius/chant/utils";
|
|
35
|
+
import { isClickHouseObject } from "./clickhouse/entities.js";
|
|
36
|
+
import { isPostgresObject } from "./postgres/entities.js";
|
|
37
|
+
import { POSTGRES_LATEST_MAJOR } from "./spec/postgres-pin.js";
|
|
38
|
+
import { applyOrder, lineageJson, referenceName } from "./core/references.js";
|
|
39
|
+
import { sqlFileLineOf, sqlFileOf, sqlFileReferences } from "./sql-files.js";
|
|
40
|
+
import { relative } from "node:path";
|
|
41
|
+
import { getProvenance } from "@intentius/chant/provenance";
|
|
42
|
+
export { applyOrder } from "./core/references.js";
|
|
43
|
+
/**
|
|
44
|
+
* A warning for each clause chant's parser does not know (#3872): where it
|
|
45
|
+
* is (the `.sql` file and line, or the template's file and the line in its
|
|
46
|
+
* statement), which object, and what follows from it. The statement is sent
|
|
47
|
+
* as written; the server decides.
|
|
48
|
+
*/
|
|
49
|
+
export function unparsedWarnings(objects) {
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const [name, entity] of objects) {
|
|
52
|
+
const unparsed = entity.props?.unparsed;
|
|
53
|
+
if (!unparsed || unparsed.length === 0)
|
|
54
|
+
continue;
|
|
55
|
+
const file = sqlFileOf(entity);
|
|
56
|
+
const start = sqlFileLineOf(entity);
|
|
57
|
+
const source = getProvenance(entity)?.sourceFile;
|
|
58
|
+
for (const u of unparsed) {
|
|
59
|
+
const where = file !== undefined ? `${file}:${start !== undefined ? start + u.line - 1 : u.line}` : `${source ? relative(process.cwd(), source) : "?"}: ${name}, line ${u.line} of its statement`;
|
|
60
|
+
const on = u.column !== undefined ? ` on column ${u.column}` : "";
|
|
61
|
+
out.push(`${where}: ${entity.sqlName ?? name} has a clause chant does not read, ${u.clause}${on}. ` +
|
|
62
|
+
"It is sent as written and the server checks it; a plan reads the rest of the object, and classes any change to the clause as one to review");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/** The file the statements are written to, beside the primary output. */
|
|
68
|
+
export const CLICKHOUSE_DDL_FILE = "clickhouse.sql";
|
|
69
|
+
/** The Postgres statements' file, beside the primary output. */
|
|
70
|
+
export const POSTGRES_DDL_FILE = "postgres.sql";
|
|
71
|
+
function objectJson(name, obj, names, extra = []) {
|
|
72
|
+
const props = { ...obj.props };
|
|
73
|
+
delete props.source;
|
|
74
|
+
const ddl = props.ddl;
|
|
75
|
+
delete props.ddl;
|
|
76
|
+
if (props.reads)
|
|
77
|
+
props.reads = props.reads.map((r) => referenceName(r, names) ?? null);
|
|
78
|
+
if (props.to !== undefined && typeof props.to !== "string")
|
|
79
|
+
props.to = referenceName(props.to, names) ?? null;
|
|
80
|
+
if (props.lineage) {
|
|
81
|
+
props.lineage = lineageJson(props.lineage, names);
|
|
82
|
+
}
|
|
83
|
+
const dependsOn = [...new Set([...obj.dependsOn.map((r) => referenceName(r, names)).filter((n) => n !== undefined), ...extra])];
|
|
84
|
+
return JSON.parse(JSON.stringify({ export: name, type: obj.entityType, sqlName: obj.sqlName, ...props, dependsOn, ddl }));
|
|
85
|
+
}
|
|
86
|
+
/** A props value with every entity and column reference in it written as its export name (`users`, `users.id`). */
|
|
87
|
+
function postgresJsonValue(v, names) {
|
|
88
|
+
if (isPostgresObject(v) || isAttrRefLike(v))
|
|
89
|
+
return referenceName(v, names) ?? null;
|
|
90
|
+
if (Array.isArray(v))
|
|
91
|
+
return v.map((x) => postgresJsonValue(x, names));
|
|
92
|
+
if (v !== null && typeof v === "object")
|
|
93
|
+
return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, postgresJsonValue(x, names)]));
|
|
94
|
+
return v;
|
|
95
|
+
}
|
|
96
|
+
function postgresObjectJson(name, obj, names, extra = []) {
|
|
97
|
+
const props = { ...obj.props };
|
|
98
|
+
delete props.source;
|
|
99
|
+
const ddl = props.ddl;
|
|
100
|
+
delete props.ddl;
|
|
101
|
+
const dependsOn = [...new Set([...obj.dependsOn.map((r) => referenceName(r, names)).filter((n) => n !== undefined), ...extra])];
|
|
102
|
+
return JSON.parse(JSON.stringify({ export: name, type: obj.entityType, sqlName: obj.sqlName, ...postgresJsonValue(props, names), dependsOn, ddl }));
|
|
103
|
+
}
|
|
104
|
+
/** The Postgres major the build targets: `sql.postgresMajor`, else the newest pinned major. */
|
|
105
|
+
function targetMajor(config) {
|
|
106
|
+
const m = config?.sql?.postgresMajor;
|
|
107
|
+
return typeof m === "number" ? m : POSTGRES_LATEST_MAJOR;
|
|
108
|
+
}
|
|
109
|
+
function serializePostgres(objects, entities, config) {
|
|
110
|
+
const names = new Map();
|
|
111
|
+
for (const [name, entity] of entities)
|
|
112
|
+
names.set(entity, name);
|
|
113
|
+
// A template names an object read from a `.sql` file as text: that is an edge too.
|
|
114
|
+
const extra = sqlFileReferences("postgres", objects);
|
|
115
|
+
const order = applyOrder(objects, names, extra);
|
|
116
|
+
const doc = {
|
|
117
|
+
dialect: "postgres",
|
|
118
|
+
postgresMajor: targetMajor(config),
|
|
119
|
+
applyOrder: order,
|
|
120
|
+
objects: order.map((n) => postgresObjectJson(n, objects.get(n), names, extra.get(n))),
|
|
121
|
+
};
|
|
122
|
+
const ddl = order.map((n) => `${objects.get(n).props.ddl};`).join("\n\n");
|
|
123
|
+
const warnings = unparsedWarnings(objects);
|
|
124
|
+
return {
|
|
125
|
+
primary: `${JSON.stringify(doc, null, 2)}\n`,
|
|
126
|
+
files: { [POSTGRES_DDL_FILE]: `${ddl}\n` },
|
|
127
|
+
verbatimFiles: [POSTGRES_DDL_FILE],
|
|
128
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export const sqlSerializer = {
|
|
132
|
+
name: "sql",
|
|
133
|
+
rulePrefix: "SQL",
|
|
134
|
+
serialize(entities, _outputs, context) {
|
|
135
|
+
const objects = new Map();
|
|
136
|
+
const postgres = new Map();
|
|
137
|
+
for (const [name, entity] of entities) {
|
|
138
|
+
if (isClickHouseObject(entity))
|
|
139
|
+
objects.set(name, entity);
|
|
140
|
+
else if (isPostgresObject(entity))
|
|
141
|
+
postgres.set(name, entity);
|
|
142
|
+
}
|
|
143
|
+
if (objects.size > 0 && postgres.size > 0) {
|
|
144
|
+
throw new Error(`sql: one build holds one dialect, and this one declares ClickHouse (${[...objects.keys()][0]}) and Postgres (${[...postgres.keys()][0]}) objects; ` +
|
|
145
|
+
"declare each database in its own project");
|
|
146
|
+
}
|
|
147
|
+
if (postgres.size > 0)
|
|
148
|
+
return serializePostgres(postgres, entities, context?.config);
|
|
149
|
+
if (objects.size === 0)
|
|
150
|
+
return "";
|
|
151
|
+
const names = new Map();
|
|
152
|
+
for (const [name, entity] of entities)
|
|
153
|
+
names.set(entity, name);
|
|
154
|
+
const extra = sqlFileReferences("clickhouse", objects);
|
|
155
|
+
const order = applyOrder(objects, names, extra);
|
|
156
|
+
const doc = {
|
|
157
|
+
dialect: "clickhouse",
|
|
158
|
+
applyOrder: order,
|
|
159
|
+
objects: order.map((n) => objectJson(n, objects.get(n), names, extra.get(n))),
|
|
160
|
+
};
|
|
161
|
+
const ddl = order
|
|
162
|
+
.map((n) => `${objects.get(n).props.ddl};`)
|
|
163
|
+
.join("\n\n");
|
|
164
|
+
const warnings = unparsedWarnings(objects);
|
|
165
|
+
return {
|
|
166
|
+
primary: `${JSON.stringify(doc, null, 2)}\n`,
|
|
167
|
+
files: { [CLICKHOUSE_DDL_FILE]: `${ddl}\n` },
|
|
168
|
+
verbatimFiles: [CLICKHOUSE_DDL_FILE],
|
|
169
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createSkillsLoader } from "@intentius/chant/lexicon-plugin-helpers";
|
|
2
|
+
/** The sql lexicon's AI skills, read from src/skills/. */
|
|
3
|
+
export const sqlSkills = createSkillsLoader(import.meta.url, [
|
|
4
|
+
{
|
|
5
|
+
file: "chant-sql.md",
|
|
6
|
+
name: "chant-sql",
|
|
7
|
+
description: "Declare ClickHouse databases, tables, views and materialized views as SQL-shaped tagged templates, with references, lineage and the checks that run on them",
|
|
8
|
+
triggers: [
|
|
9
|
+
{ type: "context", value: "clickhouse" },
|
|
10
|
+
{ type: "context", value: "clickhouse schema" },
|
|
11
|
+
{ type: "context", value: "materialized view" },
|
|
12
|
+
],
|
|
13
|
+
examples: [
|
|
14
|
+
{
|
|
15
|
+
title: "A table and a view that reads it",
|
|
16
|
+
output: 'export const analytics = database`CREATE DATABASE analytics ENGINE = Atomic`;\n' +
|
|
17
|
+
"export const events = table`CREATE TABLE ${analytics}.events (user_id UUID, ts DateTime) ENGINE = MergeTree ORDER BY (user_id, ts)`;\n" +
|
|
18
|
+
"export const recent = view`CREATE VIEW ${analytics}.recent AS SELECT ${events.columns.user_id} AS user_id FROM ${events}`;",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
file: "chant-sql-plan.md",
|
|
24
|
+
name: "chant-sql-plan",
|
|
25
|
+
description: "Plan ClickHouse schema changes with chant sql diff and chant sql plan, and read the three change classes (metadata only, background rewrite, rebuild) with the ALTER restriction behind each",
|
|
26
|
+
triggers: [
|
|
27
|
+
{ type: "context", value: "clickhouse schema change" },
|
|
28
|
+
{ type: "context", value: "sql plan" },
|
|
29
|
+
{ type: "context", value: "alter table" },
|
|
30
|
+
],
|
|
31
|
+
examples: [
|
|
32
|
+
{
|
|
33
|
+
title: "Classify a pull request's schema change offline",
|
|
34
|
+
output: "chant sql diff base.json head.json",
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
file: "chant-sql-rebuild.md",
|
|
40
|
+
name: "chant-sql-rebuild",
|
|
41
|
+
description: "Run a ClickHouse table rebuild (a sorting key, primary key, partition key, engine or key column type change) with ClickHouseRebuildOp, a gated Op with backfill receipts, verification, an exchange swap and onFailure cleanup",
|
|
42
|
+
triggers: [
|
|
43
|
+
{ type: "context", value: "clickhouse rebuild" },
|
|
44
|
+
{ type: "context", value: "change sorting key" },
|
|
45
|
+
{ type: "context", value: "clickhouse migration" },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
file: "chant-sql-postgres.md",
|
|
50
|
+
name: "chant-sql-postgres",
|
|
51
|
+
description: "Declare Postgres schemas, tables, constraints, indexes, views, sequences, enum, composite, range and domain types, collations, casts, aggregates, event triggers, extensions, functions, procedures, triggers, policies, roles and grants as SQL-shaped tagged templates, with references, lineage and the SQLPG lint rules",
|
|
52
|
+
triggers: [
|
|
53
|
+
{ type: "context", value: "postgres" },
|
|
54
|
+
{ type: "context", value: "postgres schema" },
|
|
55
|
+
{ type: "context", value: "postgresql" },
|
|
56
|
+
],
|
|
57
|
+
examples: [
|
|
58
|
+
{
|
|
59
|
+
title: "A table with a foreign key and an index",
|
|
60
|
+
output: 'export const app = schema`CREATE SCHEMA app`;\n' +
|
|
61
|
+
"export const users = table`CREATE TABLE ${app}.users (id bigint PRIMARY KEY)`;\n" +
|
|
62
|
+
"export const orders = table`CREATE TABLE ${app}.orders (id bigint PRIMARY KEY, user_id bigint NOT NULL REFERENCES ${users} (${users.columns.id}))`;\n" +
|
|
63
|
+
"export const ordersUser = index`CREATE INDEX orders_user_id_idx ON ${orders} (${orders.columns.user_id})`;",
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
file: "chant-sql-postgres-plan.md",
|
|
69
|
+
name: "chant-sql-postgres-plan",
|
|
70
|
+
description: "Plan Postgres schema changes with chant sql diff and chant sql plan, and read the change classes (metadata only, validates, needs CONCURRENTLY, ACCESS EXCLUSIVE rewrite, expand and contract) as the lock each takes in production",
|
|
71
|
+
triggers: [
|
|
72
|
+
{ type: "context", value: "postgres schema change" },
|
|
73
|
+
{ type: "context", value: "postgres lock" },
|
|
74
|
+
{ type: "context", value: "postgres migration" },
|
|
75
|
+
],
|
|
76
|
+
examples: [
|
|
77
|
+
{
|
|
78
|
+
title: "Classify a pull request's Postgres schema change offline",
|
|
79
|
+
output: "chant sql diff base.json head.json",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
file: "chant-sql-postgres-migration.md",
|
|
85
|
+
name: "chant-sql-postgres-migration",
|
|
86
|
+
description: "Run a Postgres column rename or type change with PostgresMigrationOp, a gated expand-and-contract Op with a dual-write trigger, a batched backfill with receipts, verification, a switch, replication lag handling and onFailure cleanup",
|
|
87
|
+
triggers: [
|
|
88
|
+
{ type: "context", value: "postgres column rename" },
|
|
89
|
+
{ type: "context", value: "postgres expand and contract" },
|
|
90
|
+
{ type: "context", value: "postgres backfill" },
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ClickHouse catalog: what generation reads from a pinned server's
|
|
3
|
+
* `system.*` tables, in the shape the committed snapshot stores.
|
|
4
|
+
*
|
|
5
|
+
* Every section is sorted by name, and the snapshot is written one entry per
|
|
6
|
+
* line, so a pin move is a line diff a reviewer can read. Nothing here depends
|
|
7
|
+
* on the host: the columns that reflect server config (`value`, `changed`) are
|
|
8
|
+
* never read, every query orders its rows, and `auto(<cores>)` defaults are
|
|
9
|
+
* written as `auto`.
|
|
10
|
+
*/
|
|
11
|
+
import { clickhouseQuery } from "../clickhouse/http.js";
|
|
12
|
+
/** The section names, in the order the snapshot writes them. */
|
|
13
|
+
export const CATALOG_SECTIONS = [
|
|
14
|
+
"tableEngines",
|
|
15
|
+
"databaseEngines",
|
|
16
|
+
"typeFamilies",
|
|
17
|
+
"codecs",
|
|
18
|
+
"skipIndexTypes",
|
|
19
|
+
"aggregateCombinators",
|
|
20
|
+
"tableFunctions",
|
|
21
|
+
"formats",
|
|
22
|
+
"functions",
|
|
23
|
+
"keywords",
|
|
24
|
+
"mergeTreeSettings",
|
|
25
|
+
"querySettings",
|
|
26
|
+
];
|
|
27
|
+
// ── Normalization ────────────────────────────────────────────────────
|
|
28
|
+
/** `max_threads` and a few others default to `auto(<cores>)`; the core count is the host's, not the pin's. */
|
|
29
|
+
export function normalizeDefault(value) {
|
|
30
|
+
return String(value ?? "").replace(/auto\(\d+\)/g, "auto");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The first prose line of a markdown description, at most 200 characters.
|
|
34
|
+
* Descriptions are multi-line markdown with headings and fenced examples; a
|
|
35
|
+
* summary is what a hover or a JSDoc line can carry.
|
|
36
|
+
*/
|
|
37
|
+
export function summaryOf(description) {
|
|
38
|
+
let fenced = false;
|
|
39
|
+
let line;
|
|
40
|
+
for (const raw of String(description ?? "").split("\n")) {
|
|
41
|
+
const l = raw.trim();
|
|
42
|
+
if (l.startsWith("```")) {
|
|
43
|
+
fenced = !fenced;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (fenced || l.length === 0 || l.startsWith("#") || l.startsWith("|"))
|
|
47
|
+
continue;
|
|
48
|
+
line = l;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
if (!line)
|
|
52
|
+
return "";
|
|
53
|
+
return line.length > 200 ? `${line.slice(0, 197)}...` : line;
|
|
54
|
+
}
|
|
55
|
+
const optional = (value) => {
|
|
56
|
+
if (value === null || value === undefined)
|
|
57
|
+
return undefined;
|
|
58
|
+
const s = String(value);
|
|
59
|
+
return s === "" ? undefined : s;
|
|
60
|
+
};
|
|
61
|
+
const flag = (value) => value === 1 || value === true || value === "1";
|
|
62
|
+
// ── Reading a server ─────────────────────────────────────────────────
|
|
63
|
+
/**
|
|
64
|
+
* Read the catalog from a running server. Every query has an explicit
|
|
65
|
+
* `ORDER BY`: system tables have no stable row order.
|
|
66
|
+
*/
|
|
67
|
+
export async function readCatalog(endpoint, image) {
|
|
68
|
+
const q = (sql) => clickhouseQuery(endpoint, sql);
|
|
69
|
+
const [{ v: version }] = (await q("SELECT version() AS v"));
|
|
70
|
+
const engines = await q("SELECT name, supports_settings, supports_skipping_indices, supports_projections, supports_sort_order, " +
|
|
71
|
+
"supports_ttl, supports_replication, supports_deduplication, supports_parallel_insert, syntax, description " +
|
|
72
|
+
"FROM system.table_engines ORDER BY name");
|
|
73
|
+
const dbEngines = await q("SELECT name, syntax, description FROM system.database_engines ORDER BY name");
|
|
74
|
+
const types = await q("SELECT name, case_insensitive, alias_to FROM system.data_type_families ORDER BY name");
|
|
75
|
+
const codecs = await q("SELECT name, is_compression, is_generic_compression, is_encryption, is_timeseries_codec, is_experimental, description " +
|
|
76
|
+
"FROM system.codecs ORDER BY name");
|
|
77
|
+
const indexTypes = await q("SELECT name, syntax, description FROM system.data_skipping_index_types ORDER BY name");
|
|
78
|
+
const combinators = await q("SELECT name, is_internal FROM system.aggregate_function_combinators ORDER BY name");
|
|
79
|
+
const tableFunctions = await q("SELECT name FROM system.table_functions ORDER BY name");
|
|
80
|
+
const formats = await q("SELECT name, is_input, is_output FROM system.formats ORDER BY name");
|
|
81
|
+
const functions = await q("SELECT name, is_aggregate, case_insensitive, alias_to FROM system.functions WHERE origin = 'System' ORDER BY name");
|
|
82
|
+
const keywords = await q("SELECT keyword FROM system.keywords ORDER BY keyword");
|
|
83
|
+
const mtSettings = await q("SELECT name, type, default, min, max, readonly, is_obsolete, tier, description FROM system.merge_tree_settings ORDER BY name");
|
|
84
|
+
const settings = await q("SELECT name, type, default, min, max, readonly, is_obsolete, tier, alias_for, description FROM system.settings ORDER BY name");
|
|
85
|
+
const setting = (r) => ({
|
|
86
|
+
name: String(r.name),
|
|
87
|
+
type: String(r.type),
|
|
88
|
+
default: normalizeDefault(r.default),
|
|
89
|
+
...(optional(r.min) !== undefined ? { min: optional(r.min) } : {}),
|
|
90
|
+
...(optional(r.max) !== undefined ? { max: optional(r.max) } : {}),
|
|
91
|
+
tier: String(r.tier),
|
|
92
|
+
obsolete: flag(r.is_obsolete),
|
|
93
|
+
readonly: flag(r.readonly),
|
|
94
|
+
...(optional(r.alias_for) !== undefined ? { aliasFor: optional(r.alias_for) } : {}),
|
|
95
|
+
summary: summaryOf(r.description),
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
dialect: "clickhouse",
|
|
99
|
+
version,
|
|
100
|
+
image,
|
|
101
|
+
tableEngines: engines.map((e) => ({
|
|
102
|
+
name: String(e.name),
|
|
103
|
+
syntax: String(e.syntax ?? ""),
|
|
104
|
+
summary: summaryOf(e.description),
|
|
105
|
+
capabilities: {
|
|
106
|
+
settings: flag(e.supports_settings),
|
|
107
|
+
skippingIndices: flag(e.supports_skipping_indices),
|
|
108
|
+
projections: flag(e.supports_projections),
|
|
109
|
+
sortOrder: flag(e.supports_sort_order),
|
|
110
|
+
ttl: flag(e.supports_ttl),
|
|
111
|
+
replication: flag(e.supports_replication),
|
|
112
|
+
deduplication: flag(e.supports_deduplication),
|
|
113
|
+
parallelInsert: flag(e.supports_parallel_insert),
|
|
114
|
+
},
|
|
115
|
+
})),
|
|
116
|
+
databaseEngines: dbEngines.map((e) => ({
|
|
117
|
+
name: String(e.name),
|
|
118
|
+
syntax: String(e.syntax ?? ""),
|
|
119
|
+
summary: summaryOf(e.description),
|
|
120
|
+
})),
|
|
121
|
+
typeFamilies: types.map((t) => ({
|
|
122
|
+
name: String(t.name),
|
|
123
|
+
caseInsensitive: flag(t.case_insensitive),
|
|
124
|
+
...(optional(t.alias_to) !== undefined ? { aliasOf: optional(t.alias_to) } : {}),
|
|
125
|
+
})),
|
|
126
|
+
codecs: codecs.map((c) => ({
|
|
127
|
+
name: String(c.name),
|
|
128
|
+
compression: flag(c.is_compression),
|
|
129
|
+
generic: flag(c.is_generic_compression),
|
|
130
|
+
encryption: flag(c.is_encryption),
|
|
131
|
+
timeseries: flag(c.is_timeseries_codec),
|
|
132
|
+
experimental: flag(c.is_experimental),
|
|
133
|
+
summary: summaryOf(c.description),
|
|
134
|
+
})),
|
|
135
|
+
skipIndexTypes: indexTypes.map((c) => ({
|
|
136
|
+
name: String(c.name),
|
|
137
|
+
syntax: String(c.syntax ?? ""),
|
|
138
|
+
summary: summaryOf(c.description),
|
|
139
|
+
})),
|
|
140
|
+
aggregateCombinators: combinators.map((c) => ({ name: String(c.name), internal: flag(c.is_internal) })),
|
|
141
|
+
tableFunctions: tableFunctions.map((f) => String(f.name)),
|
|
142
|
+
formats: formats.map((f) => ({ name: String(f.name), input: flag(f.is_input), output: flag(f.is_output) })),
|
|
143
|
+
functions: functions.map((f) => ({
|
|
144
|
+
name: String(f.name),
|
|
145
|
+
aggregate: flag(f.is_aggregate),
|
|
146
|
+
caseInsensitive: flag(f.case_insensitive),
|
|
147
|
+
...(optional(f.alias_to) !== undefined ? { aliasOf: optional(f.alias_to) } : {}),
|
|
148
|
+
})),
|
|
149
|
+
keywords: keywords.map((k) => String(k.keyword)),
|
|
150
|
+
mergeTreeSettings: mtSettings.map(setting),
|
|
151
|
+
querySettings: settings.map(setting),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// ── The snapshot's text form ─────────────────────────────────────────
|
|
155
|
+
/**
|
|
156
|
+
* Write the catalog with one array entry per line. A pretty-printed tree would
|
|
157
|
+
* spread one engine over a dozen lines; this keeps a pin move to one changed
|
|
158
|
+
* line per changed entry.
|
|
159
|
+
*/
|
|
160
|
+
export function stringifyCatalog(catalog) {
|
|
161
|
+
const lines = ["{"];
|
|
162
|
+
lines.push(` "dialect": ${JSON.stringify(catalog.dialect)},`);
|
|
163
|
+
lines.push(` "version": ${JSON.stringify(catalog.version)},`);
|
|
164
|
+
lines.push(` "image": ${JSON.stringify(catalog.image)},`);
|
|
165
|
+
CATALOG_SECTIONS.forEach((section, i) => {
|
|
166
|
+
const rows = catalog[section];
|
|
167
|
+
const last = i === CATALOG_SECTIONS.length - 1;
|
|
168
|
+
if (rows.length === 0) {
|
|
169
|
+
lines.push(` ${JSON.stringify(section)}: []${last ? "" : ","}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
lines.push(` ${JSON.stringify(section)}: [`);
|
|
173
|
+
rows.forEach((row, j) => lines.push(` ${JSON.stringify(row)}${j === rows.length - 1 ? "" : ","}`));
|
|
174
|
+
lines.push(` ]${last ? "" : ","}`);
|
|
175
|
+
});
|
|
176
|
+
lines.push("}");
|
|
177
|
+
return `${lines.join("\n")}\n`;
|
|
178
|
+
}
|
|
179
|
+
/** Parse and shape-check a snapshot. Throws naming the first section that is missing or not a list. */
|
|
180
|
+
export function parseCatalog(text) {
|
|
181
|
+
const value = JSON.parse(text);
|
|
182
|
+
if (value.dialect !== "clickhouse")
|
|
183
|
+
throw new Error(`not a ClickHouse catalog (dialect ${String(value.dialect)})`);
|
|
184
|
+
if (typeof value.version !== "string")
|
|
185
|
+
throw new Error("catalog has no version");
|
|
186
|
+
for (const section of CATALOG_SECTIONS) {
|
|
187
|
+
if (!Array.isArray(value[section]))
|
|
188
|
+
throw new Error(`catalog section ${section} is missing or not a list`);
|
|
189
|
+
}
|
|
190
|
+
return value;
|
|
191
|
+
}
|
package/dist/spec/fetch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/spec/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,OAAO,EAA+C,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEhG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/spec/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,OAAO,EAA+C,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEhG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,eAAO,MAAM,aAAa,QAA6E,CAAC;AAExG,uFAAuF;AACvF,eAAO,MAAM,WAAW,wBAAwB,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,6GAA6G;IAC7G,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAUD,0EAA0E;AAC1E,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAU/G;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAqChG;AAED,2FAA2F;AAC3F,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAGlG"}
|