@intentius/sql-yodeler 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +112 -2
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +29 -0
- package/dist/bin.js.map +1 -0
- package/dist/chant/apply.d.ts +59 -0
- package/dist/chant/apply.js +116 -0
- package/dist/chant/apply.js.map +1 -0
- package/dist/chant/build.d.ts +34 -0
- package/dist/chant/build.js +48 -0
- package/dist/chant/build.js.map +1 -0
- package/dist/chant/drift.d.ts +112 -0
- package/dist/chant/drift.js +238 -0
- package/dist/chant/drift.js.map +1 -0
- package/dist/chant/plan.d.ts +45 -0
- package/dist/chant/plan.js +61 -0
- package/dist/chant/plan.js.map +1 -0
- package/dist/chant/process.d.ts +41 -0
- package/dist/chant/process.js +87 -0
- package/dist/chant/process.js.map +1 -0
- package/dist/chant/runnable.d.ts +19 -0
- package/dist/chant/runnable.js +47 -0
- package/dist/chant/runnable.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +99 -0
- package/dist/cli.js.map +1 -0
- package/dist/command.d.ts +23 -0
- package/dist/command.js +13 -0
- package/dist/command.js.map +1 -0
- package/dist/commands/apply.d.ts +3 -0
- package/dist/commands/apply.js +426 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/approve.d.ts +5 -0
- package/dist/commands/approve.js +111 -0
- package/dist/commands/approve.js.map +1 -0
- package/dist/commands/drift.d.ts +14 -0
- package/dist/commands/drift.js +267 -0
- package/dist/commands/drift.js.map +1 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +28 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +139 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +3 -0
- package/dist/commands/lint.js +202 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.js +50 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/new.d.ts +5 -0
- package/dist/commands/new.js +292 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/override.d.ts +3 -0
- package/dist/commands/override.js +118 -0
- package/dist/commands/override.js.map +1 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.js +183 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/rebase.d.ts +3 -0
- package/dist/commands/rebase.js +167 -0
- package/dist/commands/rebase.js.map +1 -0
- package/dist/commands/repair.d.ts +14 -0
- package/dist/commands/repair.js +125 -0
- package/dist/commands/repair.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +54 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config/define.d.ts +148 -0
- package/dist/config/define.js +20 -0
- package/dist/config/define.js.map +1 -0
- package/dist/config/index.d.ts +11 -0
- package/dist/config/index.js +12 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/load.d.ts +17 -0
- package/dist/config/load.js +162 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/settings.d.ts +53 -0
- package/dist/config/settings.js +116 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/env-args.d.ts +24 -0
- package/dist/env-args.js +92 -0
- package/dist/env-args.js.map +1 -0
- package/dist/exit.d.ts +20 -0
- package/dist/exit.js +28 -0
- package/dist/exit.js.map +1 -0
- package/dist/history/client.d.ts +25 -0
- package/dist/history/client.js +42 -0
- package/dist/history/client.js.map +1 -0
- package/dist/history/ddl.d.ts +58 -0
- package/dist/history/ddl.js +100 -0
- package/dist/history/ddl.js.map +1 -0
- package/dist/history/index.d.ts +25 -0
- package/dist/history/index.js +26 -0
- package/dist/history/index.js.map +1 -0
- package/dist/history/lock.d.ts +104 -0
- package/dist/history/lock.js +230 -0
- package/dist/history/lock.js.map +1 -0
- package/dist/history/postgres/ddl.d.ts +22 -0
- package/dist/history/postgres/ddl.js +47 -0
- package/dist/history/postgres/ddl.js.map +1 -0
- package/dist/history/postgres/index.d.ts +12 -0
- package/dist/history/postgres/index.js +13 -0
- package/dist/history/postgres/index.js.map +1 -0
- package/dist/history/postgres/lock.d.ts +48 -0
- package/dist/history/postgres/lock.js +95 -0
- package/dist/history/postgres/lock.js.map +1 -0
- package/dist/history/postgres/store.d.ts +39 -0
- package/dist/history/postgres/store.js +68 -0
- package/dist/history/postgres/store.js.map +1 -0
- package/dist/history/rows.d.ts +96 -0
- package/dist/history/rows.js +73 -0
- package/dist/history/rows.js.map +1 -0
- package/dist/history/store.d.ts +72 -0
- package/dist/history/store.js +134 -0
- package/dist/history/store.js.map +1 -0
- package/dist/history/topology.d.ts +41 -0
- package/dist/history/topology.js +54 -0
- package/dist/history/topology.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/init/adopt.d.ts +70 -0
- package/dist/init/adopt.js +143 -0
- package/dist/init/adopt.js.map +1 -0
- package/dist/init/baseline.d.ts +61 -0
- package/dist/init/baseline.js +88 -0
- package/dist/init/baseline.js.map +1 -0
- package/dist/init/files.d.ts +23 -0
- package/dist/init/files.js +77 -0
- package/dist/init/files.js.map +1 -0
- package/dist/init/index.d.ts +16 -0
- package/dist/init/index.js +17 -0
- package/dist/init/index.js.map +1 -0
- package/dist/init/live.d.ts +39 -0
- package/dist/init/live.js +68 -0
- package/dist/init/live.js.map +1 -0
- package/dist/lint/check.d.ts +88 -0
- package/dist/lint/check.js +227 -0
- package/dist/lint/check.js.map +1 -0
- package/dist/lint/config.d.ts +21 -0
- package/dist/lint/config.js +43 -0
- package/dist/lint/config.js.map +1 -0
- package/dist/lint/index.d.ts +31 -0
- package/dist/lint/index.js +43 -0
- package/dist/lint/index.js.map +1 -0
- package/dist/lint/project-rules.d.ts +19 -0
- package/dist/lint/project-rules.js +32 -0
- package/dist/lint/project-rules.js.map +1 -0
- package/dist/lint/report.d.ts +7 -0
- package/dist/lint/report.js +48 -0
- package/dist/lint/report.js.map +1 -0
- package/dist/lint/rules.d.ts +53 -0
- package/dist/lint/rules.js +42 -0
- package/dist/lint/rules.js.map +1 -0
- package/dist/lint/silences.d.ts +49 -0
- package/dist/lint/silences.js +91 -0
- package/dist/lint/silences.js.map +1 -0
- package/dist/lint/statements.d.ts +38 -0
- package/dist/lint/statements.js +120 -0
- package/dist/lint/statements.js.map +1 -0
- package/dist/mcp/server.d.ts +40 -0
- package/dist/mcp/server.js +106 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +72 -0
- package/dist/mcp/tools.js +178 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/migrations/checks.d.ts +28 -0
- package/dist/migrations/checks.js +97 -0
- package/dist/migrations/checks.js.map +1 -0
- package/dist/migrations/checksum.d.ts +56 -0
- package/dist/migrations/checksum.js +72 -0
- package/dist/migrations/checksum.js.map +1 -0
- package/dist/migrations/create.d.ts +75 -0
- package/dist/migrations/create.js +120 -0
- package/dist/migrations/create.js.map +1 -0
- package/dist/migrations/directory.d.ts +119 -0
- package/dist/migrations/directory.js +229 -0
- package/dist/migrations/directory.js.map +1 -0
- package/dist/migrations/document.d.ts +106 -0
- package/dist/migrations/document.js +83 -0
- package/dist/migrations/document.js.map +1 -0
- package/dist/migrations/index.d.ts +21 -0
- package/dist/migrations/index.js +22 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/pr/comment.d.ts +32 -0
- package/dist/pr/comment.js +99 -0
- package/dist/pr/comment.js.map +1 -0
- package/dist/pr/forge.d.ts +54 -0
- package/dist/pr/forge.js +119 -0
- package/dist/pr/forge.js.map +1 -0
- package/dist/pr/issue.d.ts +68 -0
- package/dist/pr/issue.js +107 -0
- package/dist/pr/issue.js.map +1 -0
- package/dist/pr/model.d.ts +99 -0
- package/dist/pr/model.js +137 -0
- package/dist/pr/model.js.map +1 -0
- package/dist/pr/render.d.ts +29 -0
- package/dist/pr/render.js +279 -0
- package/dist/pr/render.js.map +1 -0
- package/dist/project.d.ts +62 -0
- package/dist/project.js +92 -0
- package/dist/project.js.map +1 -0
- package/dist/rebase/index.d.ts +121 -0
- package/dist/rebase/index.js +390 -0
- package/dist/rebase/index.js.map +1 -0
- package/dist/rebase/repair.d.ts +41 -0
- package/dist/rebase/repair.js +68 -0
- package/dist/rebase/repair.js.map +1 -0
- package/dist/replay/clickhouse.d.ts +33 -0
- package/dist/replay/clickhouse.js +106 -0
- package/dist/replay/clickhouse.js.map +1 -0
- package/dist/replay/index.d.ts +39 -0
- package/dist/replay/index.js +57 -0
- package/dist/replay/index.js.map +1 -0
- package/dist/replay/postgres.d.ts +31 -0
- package/dist/replay/postgres.js +140 -0
- package/dist/replay/postgres.js.map +1 -0
- package/dist/replay/replay.d.ts +114 -0
- package/dist/replay/replay.js +98 -0
- package/dist/replay/replay.js.map +1 -0
- package/dist/replay/report.d.ts +18 -0
- package/dist/replay/report.js +72 -0
- package/dist/replay/report.js.map +1 -0
- package/dist/replay/shared.d.ts +28 -0
- package/dist/replay/shared.js +77 -0
- package/dist/replay/shared.js.map +1 -0
- package/dist/schemas.d.ts +34 -0
- package/dist/schemas.js +38 -0
- package/dist/schemas.js.map +1 -0
- package/dist/server.d.ts +40 -0
- package/dist/server.js +87 -0
- package/dist/server.js.map +1 -0
- package/dist/steps/backfill.d.ts +132 -0
- package/dist/steps/backfill.js +307 -0
- package/dist/steps/backfill.js.map +1 -0
- package/dist/steps/index.d.ts +52 -0
- package/dist/steps/index.js +52 -0
- package/dist/steps/index.js.map +1 -0
- package/dist/steps/kinds.d.ts +101 -0
- package/dist/steps/kinds.js +143 -0
- package/dist/steps/kinds.js.map +1 -0
- package/dist/steps/postgres-migration.d.ts +95 -0
- package/dist/steps/postgres-migration.js +149 -0
- package/dist/steps/postgres-migration.js.map +1 -0
- package/dist/steps/rebuild.d.ts +97 -0
- package/dist/steps/rebuild.js +155 -0
- package/dist/steps/rebuild.js.map +1 -0
- package/dist/steps/run.d.ts +45 -0
- package/dist/steps/run.js +72 -0
- package/dist/steps/run.js.map +1 -0
- package/dist/topology/render.d.ts +17 -0
- package/dist/topology/render.js +17 -0
- package/dist/topology/render.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/dist/versioned/approval.d.ts +35 -0
- package/dist/versioned/approval.js +53 -0
- package/dist/versioned/approval.js.map +1 -0
- package/dist/versioned/checks.d.ts +29 -0
- package/dist/versioned/checks.js +54 -0
- package/dist/versioned/checks.js.map +1 -0
- package/dist/versioned/clickhouse.d.ts +24 -0
- package/dist/versioned/clickhouse.js +42 -0
- package/dist/versioned/clickhouse.js.map +1 -0
- package/dist/versioned/digest.d.ts +77 -0
- package/dist/versioned/digest.js +69 -0
- package/dist/versioned/digest.js.map +1 -0
- package/dist/versioned/driver.d.ts +51 -0
- package/dist/versioned/driver.js +13 -0
- package/dist/versioned/driver.js.map +1 -0
- package/dist/versioned/engine.d.ts +206 -0
- package/dist/versioned/engine.js +370 -0
- package/dist/versioned/engine.js.map +1 -0
- package/dist/versioned/explain.d.ts +123 -0
- package/dist/versioned/explain.js +294 -0
- package/dist/versioned/explain.js.map +1 -0
- package/dist/versioned/index.d.ts +46 -0
- package/dist/versioned/index.js +77 -0
- package/dist/versioned/index.js.map +1 -0
- package/dist/versioned/op.d.ts +72 -0
- package/dist/versioned/op.js +185 -0
- package/dist/versioned/op.js.map +1 -0
- package/dist/versioned/policy.d.ts +67 -0
- package/dist/versioned/policy.js +161 -0
- package/dist/versioned/policy.js.map +1 -0
- package/dist/versioned/postgres/catalog.d.ts +33 -0
- package/dist/versioned/postgres/catalog.js +34 -0
- package/dist/versioned/postgres/catalog.js.map +1 -0
- package/dist/versioned/postgres/driver.d.ts +71 -0
- package/dist/versioned/postgres/driver.js +140 -0
- package/dist/versioned/postgres/driver.js.map +1 -0
- package/dist/versioned/postgres/executor.d.ts +104 -0
- package/dist/versioned/postgres/executor.js +172 -0
- package/dist/versioned/postgres/executor.js.map +1 -0
- package/dist/versioned/postgres/index.d.ts +11 -0
- package/dist/versioned/postgres/index.js +12 -0
- package/dist/versioned/postgres/index.js.map +1 -0
- package/dist/versioned/postgres/waits.d.ts +46 -0
- package/dist/versioned/postgres/waits.js +78 -0
- package/dist/versioned/postgres/waits.js.map +1 -0
- package/dist/versioned/report.d.ts +25 -0
- package/dist/versioned/report.js +84 -0
- package/dist/versioned/report.js.map +1 -0
- package/dist/versioned/stand-down.d.ts +30 -0
- package/dist/versioned/stand-down.js +56 -0
- package/dist/versioned/stand-down.js.map +1 -0
- package/dist/versioned/state.d.ts +108 -0
- package/dist/versioned/state.js +153 -0
- package/dist/versioned/state.js.map +1 -0
- package/package.json +61 -4
- package/schemas/apply.schema.json +269 -0
- package/schemas/common.schema.json +213 -0
- package/schemas/drift.schema.json +36 -0
- package/schemas/init.schema.json +31 -0
- package/schemas/lint-rules.schema.json +54 -0
- package/schemas/lint.schema.json +290 -0
- package/schemas/mcp.schema.json +70 -0
- package/schemas/new.schema.json +71 -0
- package/schemas/plan.schema.json +371 -0
- package/schemas/rebase.schema.json +54 -0
- package/schemas/repair.schema.json +20 -0
- package/schemas/status.schema.json +310 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `ClickHouseRebuildOp` step: the change ClickHouse cannot make with
|
|
3
|
+
* `ALTER` (a sort key, an engine, a key column's type or name), run inside
|
|
4
|
+
* the migration as chant's rebuild Op.
|
|
5
|
+
*
|
|
6
|
+
* The Op is chant's own (`ClickHouseRebuildOp` from the sql lexicon), built
|
|
7
|
+
* from the step's options with the apply's environment, and run by chant's
|
|
8
|
+
* local executor with the lexicon's rebuild activities:
|
|
9
|
+
*
|
|
10
|
+
* Plan, Create, Dual write (materialized-view mode), Backfill, Verify, Swap, Retain
|
|
11
|
+
*
|
|
12
|
+
* Three things differ from running the Op with `chant run`:
|
|
13
|
+
*
|
|
14
|
+
* - The declaration is the migration's recorded schema (`migration.json`'s
|
|
15
|
+
* `schema`), written to a temporary build file, not a fresh `chant build`:
|
|
16
|
+
* the table is rebuilt as this migration declares it, however far the
|
|
17
|
+
* declared schema has moved on since.
|
|
18
|
+
* - It is built with `gates: "outer"`: the swap gate's approval is the
|
|
19
|
+
* migration's (the step's options are in the migration's checksum, which
|
|
20
|
+
* is in the plan digest the migrations Op's gate approved), so the Op has
|
|
21
|
+
* no gate and no Drop phase. The verification still runs before the swap,
|
|
22
|
+
* and a difference fails the step with nothing swapped. The old table is
|
|
23
|
+
* kept, `<table>__chant_old`, until its retention date (7 days).
|
|
24
|
+
* - It is built with `onFailure: "keep"`, so a step that failed or was
|
|
25
|
+
* killed part way keeps `<table>__chant_new` and `<table>__chant_dual` and
|
|
26
|
+
* resumes from the backfill's receipts on the next `yodel apply`: every
|
|
27
|
+
* partition with a receipt is skipped. To start the rebuild over instead,
|
|
28
|
+
* drop those two tables (a refusal names them).
|
|
29
|
+
*
|
|
30
|
+
* The activities render their DDL for the topology chant resolves from
|
|
31
|
+
* `sql.profiles.<env>.topology`. When the environment's topology comes from
|
|
32
|
+
* elsewhere (`yodel.config.ts`, or `YODEL_TOPOLOGY`, #9), the step hands
|
|
33
|
+
* every rebuild activity chant's config with that topology set on the
|
|
34
|
+
* profile ({@link rebuildChantConfig}), so the rebuild renders for the same
|
|
35
|
+
* topology as the migration's statements. On a cluster of more than one
|
|
36
|
+
* shard the Op copies and verifies every shard; it needs the `{shard}` macro
|
|
37
|
+
* on each server, distinct per shard, and refuses the cluster without it.
|
|
38
|
+
*/
|
|
39
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
40
|
+
import { tmpdir } from "node:os";
|
|
41
|
+
import { join } from "node:path";
|
|
42
|
+
import { environmentSettings } from "../config/settings.js";
|
|
43
|
+
import { loadProject } from "../project.js";
|
|
44
|
+
import { runOp, StepOpFailed } from "./run.js";
|
|
45
|
+
/** The rebuild activities whose results the history's summary keeps. */
|
|
46
|
+
const SUMMARIZED = ["clickhouseRebuildPlan", "clickhouseRebuildBackfill", "clickhouseRebuildVerify", "clickhouseRebuildSwap", "clickhouseRebuildRetain"];
|
|
47
|
+
/** `config` with `sql.profiles.<environment>.topology` set to `topology`. Pure; `config` is not changed. */
|
|
48
|
+
export function withProfileTopology(config, environment, topology) {
|
|
49
|
+
const profiles = config.sql?.profiles ?? {};
|
|
50
|
+
const profile = profiles[environment];
|
|
51
|
+
if (!profile)
|
|
52
|
+
return config;
|
|
53
|
+
return { ...config, sql: { ...config.sql, profiles: { ...profiles, [environment]: { ...profile, topology } } } };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The chant config a rebuild step's activities bind with: the project's,
|
|
57
|
+
* with the environment's resolved topology on its profile when chant would
|
|
58
|
+
* not find it there itself. `undefined` when chant's own resolution already
|
|
59
|
+
* gives the same topology (set in the profile, or no topology anywhere).
|
|
60
|
+
*/
|
|
61
|
+
export async function rebuildChantConfig(projectDir, environment, env = process.env) {
|
|
62
|
+
const project = await loadProject(projectDir);
|
|
63
|
+
const settings = environmentSettings(project, environment, env);
|
|
64
|
+
if (settings.topologySource === "default" || settings.topologySource === `sql.profiles.${environment}.topology`)
|
|
65
|
+
return undefined;
|
|
66
|
+
return withProfileTopology(project.config, environment, settings.topology);
|
|
67
|
+
}
|
|
68
|
+
/** The Op a rebuild step runs. `buildPath` is the recorded schema's file; `projectDir` holds chant.config.ts. */
|
|
69
|
+
export async function rebuildOpConfig(options, ctx) {
|
|
70
|
+
const { ClickHouseRebuildOp } = await import("@intentius/chant-lexicon-sql/clickhouse/rebuild/op");
|
|
71
|
+
const { op } = ClickHouseRebuildOp({
|
|
72
|
+
name: options.name,
|
|
73
|
+
env: ctx.environment,
|
|
74
|
+
table: options.table,
|
|
75
|
+
dualWrite: options.dualWrite,
|
|
76
|
+
build: false,
|
|
77
|
+
output: ctx.buildPath,
|
|
78
|
+
path: ctx.projectDir,
|
|
79
|
+
gates: "outer",
|
|
80
|
+
onFailure: "keep",
|
|
81
|
+
});
|
|
82
|
+
return op.props;
|
|
83
|
+
}
|
|
84
|
+
const num = (v) => (typeof v === "number" ? v : undefined);
|
|
85
|
+
const str = (v) => (typeof v === "string" ? v : undefined);
|
|
86
|
+
/** Folds one rebuild activity's result into the summary. */
|
|
87
|
+
export function summarize(summary, fn, result) {
|
|
88
|
+
const r = (typeof result === "object" && result !== null ? result : {});
|
|
89
|
+
const put = (k, v) => {
|
|
90
|
+
if (v !== undefined)
|
|
91
|
+
summary[k] = v;
|
|
92
|
+
};
|
|
93
|
+
if (fn === "clickhouseRebuildPlan")
|
|
94
|
+
put("state", str(r.state));
|
|
95
|
+
if (fn === "clickhouseRebuildBackfill") {
|
|
96
|
+
put("partitions", num(r.partitions));
|
|
97
|
+
put("copied", num(r.copied));
|
|
98
|
+
put("skipped", num(r.skipped));
|
|
99
|
+
put("cleared", num(r.cleared));
|
|
100
|
+
put("repaired", num(r.repaired));
|
|
101
|
+
}
|
|
102
|
+
if (fn === "clickhouseRebuildVerify")
|
|
103
|
+
put("verifiedRows", num(r.rows));
|
|
104
|
+
if (fn === "clickhouseRebuildSwap") {
|
|
105
|
+
put("swapped", typeof r.swapped === "boolean" ? r.swapped : undefined);
|
|
106
|
+
put("oldTable", str(r.oldTable));
|
|
107
|
+
put("retainUntil", str(r.retainUntil));
|
|
108
|
+
}
|
|
109
|
+
if (fn === "clickhouseRebuildRetain") {
|
|
110
|
+
put("oldTable", str(r.oldTable));
|
|
111
|
+
put("retainUntil", str(r.retainUntil));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Runs a rebuild step to the end. The summary is filled in as the activities return, so a failure still says how far it got. */
|
|
115
|
+
export async function runRebuild(options, run, summary = { op: "ClickHouseRebuildOp", table: options.table }) {
|
|
116
|
+
const dir = mkdtempSync(join(tmpdir(), "yodel-rebuild-"));
|
|
117
|
+
try {
|
|
118
|
+
const buildPath = join(dir, "schema.json");
|
|
119
|
+
writeFileSync(buildPath, JSON.stringify(run.schema));
|
|
120
|
+
const config = await rebuildOpConfig(options, { environment: run.environment, projectDir: run.projectDir, buildPath });
|
|
121
|
+
const lexicon = run.activities ?? (await import("@intentius/chant-lexicon-sql/op/activities"));
|
|
122
|
+
const wrapped = {};
|
|
123
|
+
// A rebuild activity takes its config as a third argument (chant's ClickHouseRebuildDeps).
|
|
124
|
+
const deps = run.config ? { config: run.config } : undefined;
|
|
125
|
+
const summarized = SUMMARIZED;
|
|
126
|
+
for (const fn of Object.keys(lexicon).filter((f) => f.startsWith("clickhouseRebuild") && (deps !== undefined || summarized.includes(f)))) {
|
|
127
|
+
const inner = lexicon[fn];
|
|
128
|
+
if (!inner)
|
|
129
|
+
continue;
|
|
130
|
+
wrapped[fn] = async (args, signal) => {
|
|
131
|
+
const result = await (deps ? inner(args, signal, deps) : inner(args, signal));
|
|
132
|
+
if (summarized.includes(fn))
|
|
133
|
+
summarize(summary, fn, result);
|
|
134
|
+
return result;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const records = await runOp(config, {
|
|
138
|
+
cwd: run.projectDir,
|
|
139
|
+
environment: run.environment,
|
|
140
|
+
activities: { ...(run.activities ?? {}), ...wrapped },
|
|
141
|
+
...(run.base ? { base: run.base } : {}),
|
|
142
|
+
...(run.signal ? { signal: run.signal } : {}),
|
|
143
|
+
...(run.onRecord ? { onRecord: run.onRecord } : {}),
|
|
144
|
+
}).catch((e) => {
|
|
145
|
+
if (e instanceof StepOpFailed)
|
|
146
|
+
e.summary = summary;
|
|
147
|
+
throw e;
|
|
148
|
+
});
|
|
149
|
+
return { summary, records };
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
rmSync(dir, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=rebuild.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rebuild.js","sourceRoot":"","sources":["../../src/steps/rebuild.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Running an Op step's Op in this process, with chant's local executor
|
|
3
|
+
* (`runOpLocally`, the executor `chant run` uses), inside the versioned
|
|
4
|
+
* apply that holds the lock.
|
|
5
|
+
*
|
|
6
|
+
* The activities are the ones `chant run` loads for a sql project (core's,
|
|
7
|
+
* and the sql lexicon's, the rebuild's among them), plus what the step adds
|
|
8
|
+
* (a backfill's receipt store and its `yodelSql`). Nothing is written to the
|
|
9
|
+
* run ledger: the history table is the record of what ran.
|
|
10
|
+
*
|
|
11
|
+
* A step's Op has no gate of its own here. The migration it is part of was
|
|
12
|
+
* approved under the plan digest, which covers the step through the
|
|
13
|
+
* migration's checksum; a gate left in the Op is refused before it runs.
|
|
14
|
+
*/
|
|
15
|
+
import type { OpConfig } from "@intentius/chant/op/types";
|
|
16
|
+
import type { ActivityFn } from "@intentius/chant/op/activity-registry";
|
|
17
|
+
import type { StepRecord } from "@intentius/chant/op/local-executor";
|
|
18
|
+
import { YodelError } from "../project.js";
|
|
19
|
+
/** A step's Op stopped part way. The history records it failed; the next run resumes it. */
|
|
20
|
+
export declare class StepOpFailed extends YodelError {
|
|
21
|
+
readonly op: string;
|
|
22
|
+
readonly detail: string;
|
|
23
|
+
readonly records: StepRecord[];
|
|
24
|
+
/** How far it got, for the history's note: set by the step that ran it. */
|
|
25
|
+
summary: unknown;
|
|
26
|
+
constructor(op: string, detail: string, records: StepRecord[]);
|
|
27
|
+
}
|
|
28
|
+
/** The gates in an Op, by name. */
|
|
29
|
+
export declare const gatesOf: (config: Pick<OpConfig, "phases" | "onFailure">) => string[];
|
|
30
|
+
/** The first failing record's error, for the message. */
|
|
31
|
+
export declare function failureOf(records: readonly StepRecord[]): string;
|
|
32
|
+
export interface RunOpOptions {
|
|
33
|
+
/** The project directory: chant's config is read from it. */
|
|
34
|
+
cwd: string;
|
|
35
|
+
/** The environment, handed to the activities through the run context. */
|
|
36
|
+
environment: string;
|
|
37
|
+
/** Activities added to (or replacing) the ones chant loads. */
|
|
38
|
+
activities?: Record<string, ActivityFn>;
|
|
39
|
+
/** The activities to use instead of loading chant's, for a test. */
|
|
40
|
+
base?: Map<string, ActivityFn>;
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
onRecord?: (record: StepRecord) => void;
|
|
43
|
+
}
|
|
44
|
+
/** Runs `config` to the end. Resolves with its records; throws {@link StepOpFailed} when it fails, or stops at a gate or a decision point. */
|
|
45
|
+
export declare function runOp(config: OpConfig, options: RunOpOptions): Promise<StepRecord[]>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Running an Op step's Op in this process, with chant's local executor
|
|
3
|
+
* (`runOpLocally`, the executor `chant run` uses), inside the versioned
|
|
4
|
+
* apply that holds the lock.
|
|
5
|
+
*
|
|
6
|
+
* The activities are the ones `chant run` loads for a sql project (core's,
|
|
7
|
+
* and the sql lexicon's, the rebuild's among them), plus what the step adds
|
|
8
|
+
* (a backfill's receipt store and its `yodelSql`). Nothing is written to the
|
|
9
|
+
* run ledger: the history table is the record of what ran.
|
|
10
|
+
*
|
|
11
|
+
* A step's Op has no gate of its own here. The migration it is part of was
|
|
12
|
+
* approved under the plan digest, which covers the step through the
|
|
13
|
+
* migration's checksum; a gate left in the Op is refused before it runs.
|
|
14
|
+
*/
|
|
15
|
+
import { YodelError } from "../project.js";
|
|
16
|
+
/** A step's Op stopped part way. The history records it failed; the next run resumes it. */
|
|
17
|
+
export class StepOpFailed extends YodelError {
|
|
18
|
+
op;
|
|
19
|
+
detail;
|
|
20
|
+
records;
|
|
21
|
+
/** How far it got, for the history's note: set by the step that ran it. */
|
|
22
|
+
summary;
|
|
23
|
+
constructor(op, detail, records) {
|
|
24
|
+
super(`${op}: ${detail}`, 1);
|
|
25
|
+
this.op = op;
|
|
26
|
+
this.detail = detail;
|
|
27
|
+
this.records = records;
|
|
28
|
+
this.name = "StepOpFailed";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const allSteps = (config) => [...config.phases, ...(config.onFailure ?? [])].flatMap((p) => p.steps.flatMap((s) => (s.kind === "effect" ? [s, ...s.steps] : [s])));
|
|
32
|
+
/** The gates in an Op, by name. */
|
|
33
|
+
export const gatesOf = (config) => allSteps(config).flatMap((s) => (s.kind === "gate" ? [s.gate ?? "unnamed"] : []));
|
|
34
|
+
/** The first failing record's error, for the message. */
|
|
35
|
+
export function failureOf(records) {
|
|
36
|
+
const failed = records.find((r) => r.status === "fail");
|
|
37
|
+
return failed ? `${failed.phase} / ${failed.fn} failed: ${failed.error ?? "no error given"}` : "the Op failed";
|
|
38
|
+
}
|
|
39
|
+
/** Runs `config` to the end. Resolves with its records; throws {@link StepOpFailed} when it fails, or stops at a gate or a decision point. */
|
|
40
|
+
export async function runOp(config, options) {
|
|
41
|
+
const gates = gatesOf(config);
|
|
42
|
+
if (gates.length > 0) {
|
|
43
|
+
throw new StepOpFailed(config.name, `it has a gate (${gates.join(", ")}); a step's Op runs behind the migration's own gate and may not have one of its own`, []);
|
|
44
|
+
}
|
|
45
|
+
const { loadActivities, loadProfiles } = await import("@intentius/chant/op/activity-registry");
|
|
46
|
+
const { runOpLocally, OpRunFailure } = await import("@intentius/chant/op/local-executor");
|
|
47
|
+
const activities = options.base ? new Map(options.base) : await loadActivities(["sql"]);
|
|
48
|
+
for (const [name, fn] of Object.entries(options.activities ?? {}))
|
|
49
|
+
activities.set(name, fn);
|
|
50
|
+
const profiles = await loadProfiles();
|
|
51
|
+
try {
|
|
52
|
+
const result = await runOpLocally(config, activities, profiles, options.signal, {
|
|
53
|
+
cwd: options.cwd,
|
|
54
|
+
env: options.environment,
|
|
55
|
+
...(options.onRecord ? { onRecord: options.onRecord } : {}),
|
|
56
|
+
});
|
|
57
|
+
if (result.status !== "ok")
|
|
58
|
+
throw new StepOpFailed(config.name, `it stopped (${result.status}) instead of running to the end`, result.records);
|
|
59
|
+
return result.records;
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
if (e instanceof StepOpFailed)
|
|
63
|
+
throw e;
|
|
64
|
+
if (e instanceof OpRunFailure) {
|
|
65
|
+
const cause = e.cause instanceof Error ? e.cause.message : typeof e.cause === "string" ? e.cause : undefined;
|
|
66
|
+
const failedStep = e.result.records.some((r) => r.status === "fail");
|
|
67
|
+
throw new StepOpFailed(config.name, failedStep ? failureOf(e.result.records) : (cause ?? "the Op failed"), e.result.records);
|
|
68
|
+
}
|
|
69
|
+
throw new StepOpFailed(config.name, e instanceof Error ? e.message : String(e), []);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/steps/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A migration's statements as one environment runs them. The source is
|
|
3
|
+
* topology-neutral: `yodel new` writes each statement for a single node,
|
|
4
|
+
* and at apply time each is rendered for the environment's topology with
|
|
5
|
+
* chant's renderer (`ON CLUSTER` added or removed, the replicated engine
|
|
6
|
+
* written). Pure.
|
|
7
|
+
*
|
|
8
|
+
* The history records a statement by the neutral statement's sha, so a
|
|
9
|
+
* migration resumes the same way on every topology; the topology it was
|
|
10
|
+
* sent for goes in the row's note ({@link topologyNote}).
|
|
11
|
+
*/
|
|
12
|
+
import { type Topology } from "@intentius/chant-lexicon-sql/clickhouse/topology";
|
|
13
|
+
export type { Topology };
|
|
14
|
+
/** One neutral statement, as `topology` runs it. */
|
|
15
|
+
export declare const renderForEnvironment: (sql: string, topology: Topology) => string;
|
|
16
|
+
/** The `note` a statement row carries: the topology the statement was rendered for. */
|
|
17
|
+
export declare const topologyNote: (topology: Topology) => string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A migration's statements as one environment runs them. The source is
|
|
3
|
+
* topology-neutral: `yodel new` writes each statement for a single node,
|
|
4
|
+
* and at apply time each is rendered for the environment's topology with
|
|
5
|
+
* chant's renderer (`ON CLUSTER` added or removed, the replicated engine
|
|
6
|
+
* written). Pure.
|
|
7
|
+
*
|
|
8
|
+
* The history records a statement by the neutral statement's sha, so a
|
|
9
|
+
* migration resumes the same way on every topology; the topology it was
|
|
10
|
+
* sent for goes in the row's note ({@link topologyNote}).
|
|
11
|
+
*/
|
|
12
|
+
import { renderStatement, topologyLabel } from "@intentius/chant-lexicon-sql/clickhouse/topology";
|
|
13
|
+
/** One neutral statement, as `topology` runs it. */
|
|
14
|
+
export const renderForEnvironment = (sql, topology) => renderStatement(sql, topology);
|
|
15
|
+
/** The `note` a statement row carries: the topology the statement was rendered for. */
|
|
16
|
+
export const topologyNote = (topology) => `topology=${topologyLabel(topology)}`;
|
|
17
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/topology/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,kDAAkD,CAAC;AAIjH,oDAAoD;AACpD,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,GAAW,EAAE,QAAkB,EAAU,EAAE,CAAC,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAEhH,uFAAuF;AACvF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAkB,EAAU,EAAE,CAAC,YAAY,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
/** The package version, read from package.json (one level above both src/ and dist/). */
|
|
3
|
+
export function version() {
|
|
4
|
+
const url = new URL("../package.json", import.meta.url);
|
|
5
|
+
const pkg = JSON.parse(readFileSync(url, "utf8"));
|
|
6
|
+
if (typeof pkg.version !== "string") {
|
|
7
|
+
throw new Error(`no version in ${url.pathname}`);
|
|
8
|
+
}
|
|
9
|
+
return pkg.version;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,yFAAyF;AACzF,MAAM,UAAU,OAAO;IACrB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAA0B,CAAC;IAC3E,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approval check `yodel apply <env> --execute` makes itself, against
|
|
3
|
+
* chant's gate ledger, before it sends anything.
|
|
4
|
+
*
|
|
5
|
+
* `YODEL_APPROVED_PLAN` is how the Op hands the digest to the Apply step, but
|
|
6
|
+
* it is never enough on its own: anyone can set an environment variable.
|
|
7
|
+
* `--execute` reads the ledger the gate step reads (the `chant/lifecycle`
|
|
8
|
+
* branch, through chant's `gitGateLedgerPort`) for the environment's
|
|
9
|
+
* migrations Op, and requires an approval of its gate for exactly the digest
|
|
10
|
+
* it computes under the lock, by the rule chant's gate applies
|
|
11
|
+
* (`evaluateGate`):
|
|
12
|
+
*
|
|
13
|
+
* - the resolution is for this gate and for this plan digest
|
|
14
|
+
* (`latestResolutionForPlan`; one recorded before plan-bound gates never
|
|
15
|
+
* counts);
|
|
16
|
+
* - it is newer than the newest pending fact for the gate, so an approval
|
|
17
|
+
* from before the gate last asked does not answer it;
|
|
18
|
+
* - the workspace's approval rule for the gate (`identity.gates`), when one
|
|
19
|
+
* applies, accepts it.
|
|
20
|
+
*
|
|
21
|
+
* A gate with a quorum or policy (`approval`) is refused here: counting a
|
|
22
|
+
* quorum is the gate step's, and `--execute` fails closed rather than
|
|
23
|
+
* approximate it.
|
|
24
|
+
*/
|
|
25
|
+
import type { GateLedgerPort } from "@intentius/chant/op/gate";
|
|
26
|
+
import type { MigrateOpRef } from "./op.js";
|
|
27
|
+
/** Exit code of a refusal for want of an approval: the same as a run stopped at the gate. */
|
|
28
|
+
export declare const NOT_APPROVED = 3;
|
|
29
|
+
/** Throws unless the ledger holds an approval of `op`'s gate for `digest`. Returns who approved it. */
|
|
30
|
+
export declare function verifyApproval(port: GateLedgerPort, op: MigrateOpRef, digest: string): Promise<{
|
|
31
|
+
resolvedBy: string;
|
|
32
|
+
timestamp: string;
|
|
33
|
+
}>;
|
|
34
|
+
/** The ledger as the gate step reads it, in the project's checkout. */
|
|
35
|
+
export declare function gitLedger(cwd: string): Promise<GateLedgerPort>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approval check `yodel apply <env> --execute` makes itself, against
|
|
3
|
+
* chant's gate ledger, before it sends anything.
|
|
4
|
+
*
|
|
5
|
+
* `YODEL_APPROVED_PLAN` is how the Op hands the digest to the Apply step, but
|
|
6
|
+
* it is never enough on its own: anyone can set an environment variable.
|
|
7
|
+
* `--execute` reads the ledger the gate step reads (the `chant/lifecycle`
|
|
8
|
+
* branch, through chant's `gitGateLedgerPort`) for the environment's
|
|
9
|
+
* migrations Op, and requires an approval of its gate for exactly the digest
|
|
10
|
+
* it computes under the lock, by the rule chant's gate applies
|
|
11
|
+
* (`evaluateGate`):
|
|
12
|
+
*
|
|
13
|
+
* - the resolution is for this gate and for this plan digest
|
|
14
|
+
* (`latestResolutionForPlan`; one recorded before plan-bound gates never
|
|
15
|
+
* counts);
|
|
16
|
+
* - it is newer than the newest pending fact for the gate, so an approval
|
|
17
|
+
* from before the gate last asked does not answer it;
|
|
18
|
+
* - the workspace's approval rule for the gate (`identity.gates`), when one
|
|
19
|
+
* applies, accepts it.
|
|
20
|
+
*
|
|
21
|
+
* A gate with a quorum or policy (`approval`) is refused here: counting a
|
|
22
|
+
* quorum is the gate step's, and `--execute` fails closed rather than
|
|
23
|
+
* approximate it.
|
|
24
|
+
*/
|
|
25
|
+
import { YodelError } from "../project.js";
|
|
26
|
+
const EPOCH = new Date(0).toISOString();
|
|
27
|
+
/** Exit code of a refusal for want of an approval: the same as a run stopped at the gate. */
|
|
28
|
+
export const NOT_APPROVED = 3;
|
|
29
|
+
/** Throws unless the ledger holds an approval of `op`'s gate for `digest`. Returns who approved it. */
|
|
30
|
+
export async function verifyApproval(port, op, digest) {
|
|
31
|
+
if (op.quorum) {
|
|
32
|
+
throw new YodelError(`--execute cannot check gate "${op.gate}" of ${op.name}: it declares an approval quorum or policy, which only the gate step counts; run yodel apply without --execute`, NOT_APPROVED);
|
|
33
|
+
}
|
|
34
|
+
const { latestPendingGate, latestResolutionForPlan } = await import("@intentius/chant/lifecycle/gate-ledger");
|
|
35
|
+
const ledger = await port.read(op.name);
|
|
36
|
+
const rule = (await port.approvalRule?.(op.gate)) ?? null;
|
|
37
|
+
const resolutions = ledger.resolutions.filter((r) => r.gate !== op.gate || rule === null || rule.refuses(r) === null);
|
|
38
|
+
const since = latestPendingGate(ledger.pending, op.gate)?.timestamp ?? EPOCH;
|
|
39
|
+
const found = latestResolutionForPlan(resolutions, op.gate, since, digest);
|
|
40
|
+
if (found.resolution)
|
|
41
|
+
return { resolvedBy: found.resolution.resolvedBy, timestamp: found.resolution.timestamp };
|
|
42
|
+
const other = found.mismatched
|
|
43
|
+
? ` (the newest approval of that gate is for ${found.mismatched.planDigest ?? "no plan"}, by ${found.mismatched.resolvedBy} at ${found.mismatched.timestamp})`
|
|
44
|
+
: "";
|
|
45
|
+
throw new YodelError(`refused: chant's gate ledger holds no approval of ${op.name} / ${op.gate} for plan ${digest}${other}. Nothing was applied. ` +
|
|
46
|
+
`Run yodel apply without --execute; it stops at the gate and prints the chant approve command.`, NOT_APPROVED);
|
|
47
|
+
}
|
|
48
|
+
/** The ledger as the gate step reads it, in the project's checkout. */
|
|
49
|
+
export async function gitLedger(cwd) {
|
|
50
|
+
const { gitGateLedgerPort } = await import("@intentius/chant/op/gate");
|
|
51
|
+
return gitGateLedgerPort({ cwd });
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=approval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../../src/versioned/approval.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AAExC,6FAA6F;AAC7F,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAE9B,uGAAuG;AACvG,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAoB,EAAE,EAAgB,EAAE,MAAc;IACzF,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;QACd,MAAM,IAAI,UAAU,CAAC,gCAAgC,EAAE,CAAC,IAAI,QAAQ,EAAE,CAAC,IAAI,gHAAgH,EAAE,YAAY,CAAC,CAAC;IAC7M,CAAC;IACD,MAAM,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,GAAG,MAAM,MAAM,CAAC,wCAAwC,CAAC,CAAC;IAC9G,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC;IAC1D,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IACtH,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,SAAS,IAAI,KAAK,CAAC;IAC7E,MAAM,KAAK,GAAG,uBAAuB,CAAC,WAAW,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IAC3E,IAAI,KAAK,CAAC,UAAU;QAAE,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,UAAU,EAAE,SAAS,EAAE,KAAK,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;IAChH,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU;QAC5B,CAAC,CAAC,6CAA6C,KAAK,CAAC,UAAU,CAAC,UAAU,IAAI,SAAS,QAAQ,KAAK,CAAC,UAAU,CAAC,UAAU,OAAO,KAAK,CAAC,UAAU,CAAC,SAAS,GAAG;QAC9J,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,IAAI,UAAU,CAClB,qDAAqD,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC,IAAI,aAAa,MAAM,GAAG,KAAK,yBAAyB;QAC3H,+FAA+F,EACjG,YAAY,CACb,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW;IACzC,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;IACvE,OAAO,iBAAiB,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-migration checks (#52), run against an environment.
|
|
3
|
+
*
|
|
4
|
+
* A migration's `checks` (`../migrations/checks.ts`) are read-only queries.
|
|
5
|
+
* `yodel apply <env>` runs the first pending migration's before it runs the
|
|
6
|
+
* migrations Op, so a run that would be refused stops before anyone is
|
|
7
|
+
* asked to approve it; the Op's Apply step runs every migration's again
|
|
8
|
+
* under the lock, just before that migration's first statement, where the
|
|
9
|
+
* data is what the statements will meet. A check that fails, or whose query
|
|
10
|
+
* the server refuses, refuses the run with exit 4: nothing of that
|
|
11
|
+
* migration is sent. Under the lock the history records the outcome: the
|
|
12
|
+
* migration's `started` row carries the checks that passed in its `note`,
|
|
13
|
+
* and a refusal is a `failed` migration row naming the check.
|
|
14
|
+
*
|
|
15
|
+
* A migration resuming part way (a statement of it already succeeded) does
|
|
16
|
+
* not run its checks again: they held before its first statement.
|
|
17
|
+
*/
|
|
18
|
+
import { YodelError } from "../project.js";
|
|
19
|
+
import { type CheckOutcome, type Migration } from "../migrations/index.js";
|
|
20
|
+
import type { VersionedDriver } from "./driver.js";
|
|
21
|
+
import type { PendingMigration } from "./state.js";
|
|
22
|
+
/** Whether `p`'s checks run before it: it has some, and no statement of it has succeeded yet. */
|
|
23
|
+
export declare const checksDue: (p: PendingMigration) => boolean;
|
|
24
|
+
/** Runs each of `m`'s checks; a query the server refuses is a failed check. */
|
|
25
|
+
export declare function runChecks(driver: VersionedDriver, m: Migration): Promise<CheckOutcome[]>;
|
|
26
|
+
/** The checks that failed, one line each. */
|
|
27
|
+
export declare function failedChecks(m: Migration, outcomes: readonly CheckOutcome[]): string[];
|
|
28
|
+
/** The refusal when a check of `m` failed, or `undefined`. */
|
|
29
|
+
export declare function checksRefusal(m: Migration, outcomes: readonly CheckOutcome[], when: string): YodelError | undefined;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-migration checks (#52), run against an environment.
|
|
3
|
+
*
|
|
4
|
+
* A migration's `checks` (`../migrations/checks.ts`) are read-only queries.
|
|
5
|
+
* `yodel apply <env>` runs the first pending migration's before it runs the
|
|
6
|
+
* migrations Op, so a run that would be refused stops before anyone is
|
|
7
|
+
* asked to approve it; the Op's Apply step runs every migration's again
|
|
8
|
+
* under the lock, just before that migration's first statement, where the
|
|
9
|
+
* data is what the statements will meet. A check that fails, or whose query
|
|
10
|
+
* the server refuses, refuses the run with exit 4: nothing of that
|
|
11
|
+
* migration is sent. Under the lock the history records the outcome: the
|
|
12
|
+
* migration's `started` row carries the checks that passed in its `note`,
|
|
13
|
+
* and a refusal is a `failed` migration row naming the check.
|
|
14
|
+
*
|
|
15
|
+
* A migration resuming part way (a statement of it already succeeded) does
|
|
16
|
+
* not run its checks again: they held before its first statement.
|
|
17
|
+
*/
|
|
18
|
+
import { YodelError } from "../project.js";
|
|
19
|
+
import { EXIT_REFUSED } from "../exit.js";
|
|
20
|
+
import { describeExpect, judgeCheck } from "../migrations/index.js";
|
|
21
|
+
/** Whether `p`'s checks run before it: it has some, and no statement of it has succeeded yet. */
|
|
22
|
+
export const checksDue = (p) => (p.migration.document.checks?.length ?? 0) > 0 && (p.partWay?.done.length ?? 0) === 0;
|
|
23
|
+
/** Runs each of `m`'s checks; a query the server refuses is a failed check. */
|
|
24
|
+
export async function runChecks(driver, m) {
|
|
25
|
+
const where = { defaultDatabase: m.document.defaultDatabase, defaultSchema: m.document.defaultSchema };
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const check of m.document.checks ?? []) {
|
|
28
|
+
try {
|
|
29
|
+
out.push(judgeCheck(check, await driver.readCheck(check.sql, where)));
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
out.push({ name: check.name, passed: false, detail: `the query failed: ${(e instanceof Error ? e.message : String(e)).split("\n")[0] ?? ""}` });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/** The checks that failed, one line each. */
|
|
38
|
+
export function failedChecks(m, outcomes) {
|
|
39
|
+
return outcomes
|
|
40
|
+
.filter((o) => !o.passed)
|
|
41
|
+
.map((o) => {
|
|
42
|
+
const check = m.document.checks?.find((c) => c.name === o.name);
|
|
43
|
+
return `check ${o.name} failed: it must ${check ? describeExpect(check).replace(/^returns/, "return") : "pass"}, and returned ${o.detail}`;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** The refusal when a check of `m` failed, or `undefined`. */
|
|
47
|
+
export function checksRefusal(m, outcomes, when) {
|
|
48
|
+
const failed = failedChecks(m, outcomes);
|
|
49
|
+
if (failed.length === 0)
|
|
50
|
+
return undefined;
|
|
51
|
+
return new YodelError(`refused: a pre-migration check of ${m.id} failed ${when}. Nothing of ${m.id} was applied:\n${failed.map((f) => ` ${f}`).join("\n")}\n` +
|
|
52
|
+
`Fix the data, then run yodel apply again; if the check is wrong, edit it in migration.json and run yodel lint --update-checksum ${m.id} (the plan digest moves, so the plan is approved again).`, EXIT_REFUSED);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/versioned/checks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The versioned apply's ClickHouse driver: the Keeper (or local) lock
|
|
3
|
+
* (`../history/lock.ts`), the catalog fingerprint over `system.tables`
|
|
4
|
+
* (`./digest.ts`), and each statement rendered for the environment's
|
|
5
|
+
* topology (#9) and sent, then its `succeeded` row appended. ClickHouse DDL
|
|
6
|
+
* is not transactional, so the row follows the statement; a runner killed
|
|
7
|
+
* between the two leaves the statement's latest row `started`, and the next
|
|
8
|
+
* run sends it again.
|
|
9
|
+
*/
|
|
10
|
+
import type { ClickHouseEndpoint } from "@intentius/chant-lexicon-sql/clickhouse/http";
|
|
11
|
+
import { type ClickHousePort, type HistoryLocation } from "../history/index.js";
|
|
12
|
+
import { type Topology } from "../topology/render.js";
|
|
13
|
+
import { type LiveScope } from "./digest.js";
|
|
14
|
+
import type { VersionedDriver } from "./driver.js";
|
|
15
|
+
export interface ClickHouseDriverInput {
|
|
16
|
+
port: ClickHousePort;
|
|
17
|
+
location: HistoryLocation;
|
|
18
|
+
scope: LiveScope;
|
|
19
|
+
serverKey: string;
|
|
20
|
+
lockTtlMs: number;
|
|
21
|
+
endpoint?: ClickHouseEndpoint | undefined;
|
|
22
|
+
topology?: Topology | undefined;
|
|
23
|
+
}
|
|
24
|
+
export declare function clickhouseDriver(input: ClickHouseDriverInput): VersionedDriver;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The versioned apply's ClickHouse driver: the Keeper (or local) lock
|
|
3
|
+
* (`../history/lock.ts`), the catalog fingerprint over `system.tables`
|
|
4
|
+
* (`./digest.ts`), and each statement rendered for the environment's
|
|
5
|
+
* topology (#9) and sent, then its `succeeded` row appended. ClickHouse DDL
|
|
6
|
+
* is not transactional, so the row follows the statement; a runner killed
|
|
7
|
+
* between the two leaves the statement's latest row `started`, and the next
|
|
8
|
+
* run sends it again.
|
|
9
|
+
*/
|
|
10
|
+
import { acquireLock, SINGLE, serverMessage } from "../history/index.js";
|
|
11
|
+
import { renderForEnvironment, topologyNote } from "../topology/render.js";
|
|
12
|
+
import { catalogFingerprint, readLiveCatalog } from "./digest.js";
|
|
13
|
+
export function clickhouseDriver(input) {
|
|
14
|
+
const { port } = input;
|
|
15
|
+
const topology = input.topology ?? SINGLE;
|
|
16
|
+
return {
|
|
17
|
+
dialect: "clickhouse",
|
|
18
|
+
rowNote: topologyNote(topology),
|
|
19
|
+
acquireLock: (holder) => acquireLock(port, { database: input.location.database, topology: input.location.topology, holder, ttlMs: input.lockTtlMs, serverKey: input.serverKey }),
|
|
20
|
+
liveFingerprint: async () => catalogFingerprint(await readLiveCatalog(port, input.scope)),
|
|
21
|
+
async readCheck(sql, where) {
|
|
22
|
+
try {
|
|
23
|
+
return await port.query(sql.trim().replace(/;+$/, ""), { settings: { readonly: "2", ...(where.defaultDatabase ? { database: where.defaultDatabase } : {}) } });
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
throw new Error(serverMessage(e), { cause: e });
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
async runStatement(step, run) {
|
|
30
|
+
try {
|
|
31
|
+
await port.query(renderForEnvironment(step.sql, topology), step.waitsForMutation ? { settings: { mutations_sync: "2" } } : {});
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
throw new Error(serverMessage(e), { cause: e });
|
|
35
|
+
}
|
|
36
|
+
await run.succeeded("");
|
|
37
|
+
},
|
|
38
|
+
stepContext: () => ({ port, endpoint: input.endpoint }),
|
|
39
|
+
close: () => Promise.resolve(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=clickhouse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clickhouse.js","sourceRoot":"","sources":["../../src/versioned/clickhouse.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAA6C,MAAM,qBAAqB,CAAC;AACpH,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAiB,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAkB,MAAM,aAAa,CAAC;AAalF,MAAM,UAAU,gBAAgB,CAAC,KAA4B;IAC3D,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACvB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,MAAM,CAAC;IAC1C,OAAO;QACL,OAAO,EAAE,YAAY;QACrB,OAAO,EAAE,YAAY,CAAC,QAAQ,CAAC;QAC/B,WAAW,EAAE,CAAC,MAAM,EAAE,EAAE,CACtB,WAAW,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC;QACzJ,eAAe,EAAE,KAAK,IAAI,EAAE,CAAC,kBAAkB,CAAC,MAAM,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACzF,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK;YACxB,IAAI,CAAC;gBACH,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACjK,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;QACD,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,GAAG;YAC1B,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACjI,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YAClD,CAAC;YACD,MAAM,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC;QACD,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;QACvD,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE;KAC/B,CAAC;AACJ,CAAC"}
|