@intentius/sql-yodeler 0.0.0-stage → 0.1.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 +110 -2
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +14 -0
- package/dist/bin.js.map +1 -0
- package/dist/chant/apply.d.ts +51 -0
- package/dist/chant/apply.js +104 -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 +44 -0
- package/dist/chant/drift.js +91 -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/cli.d.ts +4 -0
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -0
- package/dist/command.d.ts +17 -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 +306 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/drift.d.ts +3 -0
- package/dist/commands/drift.js +38 -0
- package/dist/commands/drift.js.map +1 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +22 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +135 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +3 -0
- package/dist/commands/lint.js +193 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/new.d.ts +3 -0
- package/dist/commands/new.js +168 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.js +171 -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 +3 -0
- package/dist/commands/repair.js +117 -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 +50 -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 +102 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/settings.d.ts +40 -0
- package/dist/config/settings.js +88 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/env-args.d.ts +18 -0
- package/dist/env-args.js +62 -0
- package/dist/env-args.js.map +1 -0
- package/dist/history/client.d.ts +17 -0
- package/dist/history/client.js +19 -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 +100 -0
- package/dist/history/lock.js +224 -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 +138 -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 +28 -0
- package/dist/init/live.js +45 -0
- package/dist/init/live.js.map +1 -0
- package/dist/lint/check.d.ts +81 -0
- package/dist/lint/check.js +208 -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/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 +73 -0
- package/dist/migrations/create.js +119 -0
- package/dist/migrations/create.js.map +1 -0
- package/dist/migrations/directory.d.ts +119 -0
- package/dist/migrations/directory.js +225 -0
- package/dist/migrations/directory.js.map +1 -0
- package/dist/migrations/document.d.ts +83 -0
- package/dist/migrations/document.js +82 -0
- package/dist/migrations/document.js.map +1 -0
- package/dist/migrations/index.d.ts +20 -0
- package/dist/migrations/index.js +21 -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 +50 -0
- package/dist/pr/forge.js +105 -0
- package/dist/pr/forge.js.map +1 -0
- package/dist/pr/model.d.ts +91 -0
- package/dist/pr/model.js +113 -0
- package/dist/pr/model.js.map +1 -0
- package/dist/pr/render.d.ts +27 -0
- package/dist/pr/render.js +246 -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 +36 -0
- package/dist/replay/index.js +54 -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/steps/backfill.d.ts +128 -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 +162 -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/clickhouse.d.ts +24 -0
- package/dist/versioned/clickhouse.js +34 -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 +41 -0
- package/dist/versioned/driver.js +13 -0
- package/dist/versioned/driver.js.map +1 -0
- package/dist/versioned/engine.d.ts +152 -0
- package/dist/versioned/engine.js +277 -0
- package/dist/versioned/engine.js.map +1 -0
- package/dist/versioned/explain.d.ts +123 -0
- package/dist/versioned/explain.js +289 -0
- package/dist/versioned/explain.js.map +1 -0
- package/dist/versioned/index.d.ts +37 -0
- package/dist/versioned/index.js +54 -0
- package/dist/versioned/index.js.map +1 -0
- package/dist/versioned/op.d.ts +62 -0
- package/dist/versioned/op.js +153 -0
- package/dist/versioned/op.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 +69 -0
- package/dist/versioned/postgres/driver.js +113 -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/report.d.ts +22 -0
- package/dist/versioned/report.js +81 -0
- package/dist/versioned/report.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 +51 -4
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replaying a ClickHouse history: the server `sql.profiles.<env>` binds (the
|
|
3
|
+
* emulator, `chant emulator up --lexicon sql`), statements sent as
|
|
4
|
+
* `yodel apply` sends them (rendered for the environment's topology, a
|
|
5
|
+
* mutation waited for), rebuild steps run as apply runs them, and each
|
|
6
|
+
* migration's recorded schema compared with what the server then holds, the
|
|
7
|
+
* way `chant sql plan` compares a build with a server (chant's normalizer,
|
|
8
|
+
* then the server's own formatter for expressions the rules leave
|
|
9
|
+
* different).
|
|
10
|
+
*
|
|
11
|
+
* The comparison reads every database the history names, so an object the
|
|
12
|
+
* replay made that the recorded schema leaves out is a difference too, and
|
|
13
|
+
* so is a database a migration should have dropped. chant's working objects
|
|
14
|
+
* (a rebuild's retained `__chant_old` table) are not read, as a plan does
|
|
15
|
+
* not read them.
|
|
16
|
+
*/
|
|
17
|
+
import { YodelError } from "../project.js";
|
|
18
|
+
import { environmentSettings } from "../config/settings.js";
|
|
19
|
+
import { httpPort } from "../history/index.js";
|
|
20
|
+
import { renderForEnvironment } from "../topology/render.js";
|
|
21
|
+
import { defaultStepRunner, planStep } from "../steps/index.js";
|
|
22
|
+
const SYSTEM = new Set(["system", "information_schema", "INFORMATION_SCHEMA"]);
|
|
23
|
+
const DEFAULT = "default";
|
|
24
|
+
const ident = (s) => `\`${s.replace(/`/g, "``")}\``;
|
|
25
|
+
const quote = (s) => `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
26
|
+
/** The databases a ClickHouse build output names, with bare names in `defaultDatabase`. */
|
|
27
|
+
export async function clickhouseDatabases(schema, defaultDatabase) {
|
|
28
|
+
const { schemaFromBuildOutput } = await import("@intentius/chant-lexicon-sql/clickhouse/plan/schema");
|
|
29
|
+
return [...new Set(schemaFromBuildOutput(JSON.stringify(schema), defaultDatabase).map((o) => o.canonical.database ?? o.canonical.name))];
|
|
30
|
+
}
|
|
31
|
+
/** The replay driver for `env`, a ClickHouse environment, over the history `ordered`. */
|
|
32
|
+
export async function clickhouseReplay(project, env, ordered, options = {}) {
|
|
33
|
+
const processEnv = options.processEnv ?? process.env;
|
|
34
|
+
const settings = environmentSettings(project, env.name, processEnv);
|
|
35
|
+
const { resolveClickHouseTarget, isUnresolvedTarget } = await import("@intentius/chant-lexicon-sql/clickhouse/live/bind");
|
|
36
|
+
const target = resolveClickHouseTarget({ environment: env.name, config: project.config, env: processEnv });
|
|
37
|
+
if (isUnresolvedTarget(target))
|
|
38
|
+
throw new YodelError(target.detail);
|
|
39
|
+
const endpoint = target.endpoint;
|
|
40
|
+
const topology = settings.topology;
|
|
41
|
+
const defaultDatabase = (m) => m.document.defaultDatabase ?? target.defaultDatabase;
|
|
42
|
+
const named = new Set();
|
|
43
|
+
for (const m of ordered)
|
|
44
|
+
for (const d of await clickhouseDatabases(m.document.schema, defaultDatabase(m)))
|
|
45
|
+
named.add(d);
|
|
46
|
+
const scope = [...named].sort();
|
|
47
|
+
const system = scope.filter((d) => SYSTEM.has(d));
|
|
48
|
+
if (system.length > 0)
|
|
49
|
+
throw new YodelError(`the history declares objects in ${system.join(", ")}, the server's own database; there is nothing a replay can start fresh there`);
|
|
50
|
+
if (scope.includes(settings.historyDatabase)) {
|
|
51
|
+
throw new YodelError(`the history declares objects in ${settings.historyDatabase}, the migration history's own database (${env.name}); the replay empties every database it replays into, so it refuses to touch that one`);
|
|
52
|
+
}
|
|
53
|
+
const port = options.port ?? (await httpPort(endpoint));
|
|
54
|
+
const log = options.log ?? (() => undefined);
|
|
55
|
+
const runStep = options.runStep ?? defaultStepRunner;
|
|
56
|
+
const tablesInDefault = async () => (await port.query(`SELECT name FROM system.tables WHERE database = ${quote(DEFAULT)} AND NOT is_temporary ORDER BY name`)).map((r) => r.name);
|
|
57
|
+
return {
|
|
58
|
+
dialect: "clickhouse",
|
|
59
|
+
where: `${endpoint.url} (${target.source})`,
|
|
60
|
+
scope,
|
|
61
|
+
async occupied() {
|
|
62
|
+
const others = scope.filter((d) => d !== DEFAULT);
|
|
63
|
+
const there = others.length === 0 ? [] : (await port.query(`SELECT name FROM system.databases WHERE name IN (${others.map(quote).join(", ")}) ORDER BY name`)).map((r) => r.name);
|
|
64
|
+
if (scope.includes(DEFAULT) && (await tablesInDefault()).length > 0)
|
|
65
|
+
there.unshift(`${DEFAULT} (not empty)`);
|
|
66
|
+
return there;
|
|
67
|
+
},
|
|
68
|
+
async clear() {
|
|
69
|
+
for (const d of scope) {
|
|
70
|
+
if (d !== DEFAULT)
|
|
71
|
+
await port.query(`DROP DATABASE IF EXISTS ${ident(d)} SYNC`);
|
|
72
|
+
else
|
|
73
|
+
for (const t of await tablesInDefault())
|
|
74
|
+
await port.query(`DROP TABLE IF EXISTS ${ident(DEFAULT)}.${ident(t)} SYNC`);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
async statement(_m, _index, step) {
|
|
78
|
+
await port.query(renderForEnvironment(step.sql, topology), step.waitsForMutation ? { settings: { mutations_sync: "2" } } : {});
|
|
79
|
+
},
|
|
80
|
+
async opStep(m, index) {
|
|
81
|
+
const step = m.document.steps[index];
|
|
82
|
+
if (!step)
|
|
83
|
+
return { refused: `${m.id} has no step ${String(index)}` };
|
|
84
|
+
const plan = planStep(step);
|
|
85
|
+
if (plan.run !== "rebuild")
|
|
86
|
+
return { refused: plan.run === "refused" ? plan.reason : `step ${String(index)} is not one the replay runs` };
|
|
87
|
+
const { summary } = await runStep({ environment: env.name, projectDir: project.dir, port, endpoint, migration: m, index, plan, log: (line) => { log(` ${line}`); } });
|
|
88
|
+
const s = summary;
|
|
89
|
+
return { ran: true, ...(s?.swapped !== undefined ? { note: s.swapped ? "swapped" : "not swapped" } : {}) };
|
|
90
|
+
},
|
|
91
|
+
async compare(m) {
|
|
92
|
+
const { keyedByQualifiedName, schemaFromBuildOutput, schemaFromServer } = await import("@intentius/chant-lexicon-sql/clickhouse/plan/schema");
|
|
93
|
+
const { diffSchemas } = await import("@intentius/chant-lexicon-sql/clickhouse/plan/diff");
|
|
94
|
+
const { dropFormattingOnly } = await import("@intentius/chant-lexicon-sql/clickhouse/plan/server-format");
|
|
95
|
+
const db = defaultDatabase(m);
|
|
96
|
+
const declared = keyedByQualifiedName(schemaFromBuildOutput(JSON.stringify(m.document.schema), db, topology));
|
|
97
|
+
const live = await schemaFromServer({ ...target, databases: scope, defaultDatabase: db }, new Set(scope));
|
|
98
|
+
const diff = diffSchemas(live, declared);
|
|
99
|
+
const changes = await dropFormattingOnly(endpoint, diff.changes);
|
|
100
|
+
const label = new Map(declared.map((o) => [o.key, `${o.exportName} (${o.key})`]));
|
|
101
|
+
return changes.map((c) => ({ ...c, object: label.get(c.object) ?? c.object }));
|
|
102
|
+
},
|
|
103
|
+
close: () => Promise.resolve(),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=clickhouse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clickhouse.js","sourceRoot":"","sources":["../../src/replay/clickhouse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The replay check (#11): the migration history, replayed into the
|
|
3
|
+
* dialect's emulator, must produce each migration's recorded schema.
|
|
4
|
+
*
|
|
5
|
+
* replayHistory(project, env, options) read migrations/, replay them into env's server, compare after each
|
|
6
|
+
* replayMigrations(ordered, driver) the same over any driver (pure of dialect)
|
|
7
|
+
* replayExitCode / replayFailure 0, 1 or 2; the first failing migration and why
|
|
8
|
+
* renderReplay / replayJson the result for people and for tools
|
|
9
|
+
*
|
|
10
|
+
* `yodel lint --replay <env>` and the CI template run it against the
|
|
11
|
+
* emulator (`chant emulator up --lexicon sql`), bound by the environment's
|
|
12
|
+
* `sql.profiles.<env>`.
|
|
13
|
+
*/
|
|
14
|
+
import type { Environment, Project } from "../project.js";
|
|
15
|
+
import type { StepRunner } from "../steps/index.js";
|
|
16
|
+
import type { ClickHousePort } from "../history/index.js";
|
|
17
|
+
import { replayMigrations, type ReplayDriver, type ReplayOptions, type ReplayResult } from "./replay.js";
|
|
18
|
+
export * from "./replay.js";
|
|
19
|
+
export * from "./report.js";
|
|
20
|
+
export { clickhouseDatabases, clickhouseReplay } from "./clickhouse.js";
|
|
21
|
+
export { postgresReplay, postgresSchemas } from "./postgres.js";
|
|
22
|
+
export interface ReplayHistoryOptions extends ReplayOptions {
|
|
23
|
+
processEnv?: NodeJS.ProcessEnv;
|
|
24
|
+
/** ClickHouse: the server, in place of an HTTP port to the bound endpoint. */
|
|
25
|
+
port?: ClickHousePort;
|
|
26
|
+
/** Runs a ClickHouseRebuildOp or PostgresMigrationOp step. Default: chant's local executor, as apply runs it. */
|
|
27
|
+
runStep?: StepRunner;
|
|
28
|
+
}
|
|
29
|
+
/** The dialect's driver for `env`. */
|
|
30
|
+
export declare function openReplay(project: Project, env: Environment, ordered: Parameters<typeof replayMigrations>[0], options?: ReplayHistoryOptions): Promise<ReplayDriver>;
|
|
31
|
+
/**
|
|
32
|
+
* Replays the project's migrations (well formed, or a `MigrationDirectoryError`)
|
|
33
|
+
* into `env`'s server and compares each with its recorded schema. Checksums
|
|
34
|
+
* are not this check's: `yodel lint` checks them.
|
|
35
|
+
*/
|
|
36
|
+
export declare function replayHistory(project: Project, env: Environment, options?: ReplayHistoryOptions): Promise<ReplayResult>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The replay check (#11): the migration history, replayed into the
|
|
3
|
+
* dialect's emulator, must produce each migration's recorded schema.
|
|
4
|
+
*
|
|
5
|
+
* replayHistory(project, env, options) read migrations/, replay them into env's server, compare after each
|
|
6
|
+
* replayMigrations(ordered, driver) the same over any driver (pure of dialect)
|
|
7
|
+
* replayExitCode / replayFailure 0, 1 or 2; the first failing migration and why
|
|
8
|
+
* renderReplay / replayJson the result for people and for tools
|
|
9
|
+
*
|
|
10
|
+
* `yodel lint --replay <env>` and the CI template run it against the
|
|
11
|
+
* emulator (`chant emulator up --lexicon sql`), bound by the environment's
|
|
12
|
+
* `sql.profiles.<env>`.
|
|
13
|
+
*/
|
|
14
|
+
import { readMigrations } from "../migrations/index.js";
|
|
15
|
+
import { migrationsDir } from "../versioned/index.js";
|
|
16
|
+
import { clickhouseReplay } from "./clickhouse.js";
|
|
17
|
+
import { postgresReplay } from "./postgres.js";
|
|
18
|
+
import { replayMigrations } from "./replay.js";
|
|
19
|
+
export * from "./replay.js";
|
|
20
|
+
export * from "./report.js";
|
|
21
|
+
export { clickhouseDatabases, clickhouseReplay } from "./clickhouse.js";
|
|
22
|
+
export { postgresReplay, postgresSchemas } from "./postgres.js";
|
|
23
|
+
/** The dialect's driver for `env`. */
|
|
24
|
+
export async function openReplay(project, env, ordered, options = {}) {
|
|
25
|
+
if (project.dialect === "postgres") {
|
|
26
|
+
return postgresReplay(project, env, ordered, {
|
|
27
|
+
...(options.processEnv ? { processEnv: options.processEnv } : {}),
|
|
28
|
+
...(options.runStep ? { runStep: options.runStep } : {}),
|
|
29
|
+
...(options.log ? { log: options.log } : {}),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return clickhouseReplay(project, env, ordered, {
|
|
33
|
+
...(options.processEnv ? { processEnv: options.processEnv } : {}),
|
|
34
|
+
...(options.port ? { port: options.port } : {}),
|
|
35
|
+
...(options.runStep ? { runStep: options.runStep } : {}),
|
|
36
|
+
...(options.log ? { log: options.log } : {}),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Replays the project's migrations (well formed, or a `MigrationDirectoryError`)
|
|
41
|
+
* into `env`'s server and compares each with its recorded schema. Checksums
|
|
42
|
+
* are not this check's: `yodel lint` checks them.
|
|
43
|
+
*/
|
|
44
|
+
export async function replayHistory(project, env, options = {}) {
|
|
45
|
+
const ordered = readMigrations(migrationsDir(project));
|
|
46
|
+
const driver = await openReplay(project, env, ordered, options);
|
|
47
|
+
try {
|
|
48
|
+
return await replayMigrations(ordered, driver, options);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
await driver.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/replay/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAA4D,MAAM,aAAa,CAAC;AAEzG,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAUhE,sCAAsC;AACtC,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAgB,EAAE,GAAgB,EAAE,OAA+C,EAAE,UAAgC,EAAE;IACtJ,IAAI,OAAO,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;QACnC,OAAO,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE;YAC3C,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjE,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxD,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7C,CAAC,CAAC;IACL,CAAC;IACD,OAAO,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE;QAC7C,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7C,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAgB,EAAE,GAAgB,EAAE,UAAgC,EAAE;IACxG,MAAM,OAAO,GAAG,cAAc,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAChE,IAAI,CAAC;QACH,OAAO,MAAM,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1D,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replaying a Postgres history: the server `sql.profiles.<env>` binds (the
|
|
3
|
+
* emulator), each statement sent with `search_path` set to the migration's
|
|
4
|
+
* default schema, and each migration's recorded schema compared with the
|
|
5
|
+
* server the way `chant sql plan` compares a build with one
|
|
6
|
+
* (`planAgainstClient`: chant's normalizer, then the server's own
|
|
7
|
+
* formatting for expressions the rules leave different).
|
|
8
|
+
*
|
|
9
|
+
* Statements are sent one by one outside a transaction, so a
|
|
10
|
+
* `CONCURRENTLY` statement runs as written. The comparison reads every
|
|
11
|
+
* schema the history names.
|
|
12
|
+
*
|
|
13
|
+
* A `PostgresMigrationOp` step (a column rename, a type change across
|
|
14
|
+
* kinds) runs through the same step runner `yodel apply` uses: expand, dual
|
|
15
|
+
* write, backfill (of no rows), switch, contract, with the old column
|
|
16
|
+
* dropped as the recorded schema says.
|
|
17
|
+
*/
|
|
18
|
+
import { type Environment, type Project } from "../project.js";
|
|
19
|
+
import type { Migration } from "../migrations/index.js";
|
|
20
|
+
import { type StepRunner } from "../steps/index.js";
|
|
21
|
+
import type { ReplayDriver } from "./replay.js";
|
|
22
|
+
export interface PostgresReplayOptions {
|
|
23
|
+
processEnv?: NodeJS.ProcessEnv;
|
|
24
|
+
/** Runs a PostgresMigrationOp step. Default: chant's local executor, as apply runs it. */
|
|
25
|
+
runStep?: StepRunner;
|
|
26
|
+
log?: (line: string) => void;
|
|
27
|
+
}
|
|
28
|
+
/** The schemas a Postgres build output names, with bare names in `defaultSchema`. */
|
|
29
|
+
export declare function postgresSchemas(schema: unknown, defaultSchema: string): Promise<string[]>;
|
|
30
|
+
/** The replay driver for `env`, a Postgres environment, over the history `ordered`. */
|
|
31
|
+
export declare function postgresReplay(project: Project, env: Environment, ordered: readonly Migration[], options?: PostgresReplayOptions): Promise<ReplayDriver>;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replaying a Postgres history: the server `sql.profiles.<env>` binds (the
|
|
3
|
+
* emulator), each statement sent with `search_path` set to the migration's
|
|
4
|
+
* default schema, and each migration's recorded schema compared with the
|
|
5
|
+
* server the way `chant sql plan` compares a build with one
|
|
6
|
+
* (`planAgainstClient`: chant's normalizer, then the server's own
|
|
7
|
+
* formatting for expressions the rules leave different).
|
|
8
|
+
*
|
|
9
|
+
* Statements are sent one by one outside a transaction, so a
|
|
10
|
+
* `CONCURRENTLY` statement runs as written. The comparison reads every
|
|
11
|
+
* schema the history names.
|
|
12
|
+
*
|
|
13
|
+
* A `PostgresMigrationOp` step (a column rename, a type change across
|
|
14
|
+
* kinds) runs through the same step runner `yodel apply` uses: expand, dual
|
|
15
|
+
* write, backfill (of no rows), switch, contract, with the old column
|
|
16
|
+
* dropped as the recorded schema says.
|
|
17
|
+
*/
|
|
18
|
+
import { YodelError } from "../project.js";
|
|
19
|
+
import { environmentSettings } from "../config/settings.js";
|
|
20
|
+
import { defaultStepRunner, planStep } from "../steps/index.js";
|
|
21
|
+
const SYSTEM = new Set(["pg_catalog", "information_schema", "pg_toast"]);
|
|
22
|
+
const PUBLIC = "public";
|
|
23
|
+
const ident = (s) => `"${s.replace(/"/g, '""')}"`;
|
|
24
|
+
/** The schemas a Postgres build output names, with bare names in `defaultSchema`. */
|
|
25
|
+
export async function postgresSchemas(schema, defaultSchema) {
|
|
26
|
+
const { pgSchemaFromBuildOutput } = await import("@intentius/chant-lexicon-sql/postgres/plan/schema");
|
|
27
|
+
const out = new Set();
|
|
28
|
+
for (const o of pgSchemaFromBuildOutput(JSON.stringify(schema), defaultSchema)) {
|
|
29
|
+
if (o.canonical.kind === "schema")
|
|
30
|
+
out.add(o.canonical.name);
|
|
31
|
+
else if (o.canonical.schema)
|
|
32
|
+
out.add(o.canonical.schema);
|
|
33
|
+
}
|
|
34
|
+
return [...out];
|
|
35
|
+
}
|
|
36
|
+
/** The replay driver for `env`, a Postgres environment, over the history `ordered`. */
|
|
37
|
+
export async function postgresReplay(project, env, ordered, options = {}) {
|
|
38
|
+
const processEnv = options.processEnv ?? process.env;
|
|
39
|
+
const settings = environmentSettings(project, env.name, processEnv);
|
|
40
|
+
const { resolvePostgresTarget, isUnresolvedTarget, redactUrl } = await import("@intentius/chant-lexicon-sql/postgres/live/bind");
|
|
41
|
+
const target = resolvePostgresTarget({ environment: env.name, config: project.config, env: processEnv });
|
|
42
|
+
if (isUnresolvedTarget(target))
|
|
43
|
+
throw new YodelError(target.detail);
|
|
44
|
+
const defaultSchema = (m) => m.document.defaultSchema ?? target.defaultSchema;
|
|
45
|
+
const runStep = options.runStep ?? defaultStepRunner;
|
|
46
|
+
const log = options.log ?? (() => undefined);
|
|
47
|
+
const named = new Set();
|
|
48
|
+
for (const m of ordered)
|
|
49
|
+
for (const s of await postgresSchemas(m.document.schema, defaultSchema(m)))
|
|
50
|
+
named.add(s);
|
|
51
|
+
const scope = [...named].sort();
|
|
52
|
+
const system = scope.filter((s) => SYSTEM.has(s) || s.startsWith("pg_"));
|
|
53
|
+
if (system.length > 0)
|
|
54
|
+
throw new YodelError(`the history declares objects in ${system.join(", ")}, the server's own schema; there is nothing a replay can start fresh there`);
|
|
55
|
+
if (scope.includes(settings.historyDatabase)) {
|
|
56
|
+
throw new YodelError(`the history declares objects in ${settings.historyDatabase}, the migration history's own schema (${env.name}); the replay empties every schema it replays into, so it refuses to touch that one`);
|
|
57
|
+
}
|
|
58
|
+
const { connectPostgres } = await import("@intentius/chant-lexicon-sql/postgres/live/client");
|
|
59
|
+
// Two sessions: the catalog is read with an empty search_path (as chant reads it), statements are sent with the migration's.
|
|
60
|
+
const reader = await connectPostgres(target.endpoint, { applicationName: "yodel replay" });
|
|
61
|
+
let writer;
|
|
62
|
+
try {
|
|
63
|
+
writer = await connectPostgres(target.endpoint, { applicationName: "yodel replay" });
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
await reader.end();
|
|
67
|
+
throw e;
|
|
68
|
+
}
|
|
69
|
+
const publicObjects = async () => {
|
|
70
|
+
const [row] = await reader.query(`SELECT (SELECT count(*) FROM pg_catalog.pg_class c WHERE c.relnamespace = n.oid)
|
|
71
|
+
+ (SELECT count(*) FROM pg_catalog.pg_type t WHERE t.typnamespace = n.oid AND t.typtype IN ('e', 'd'))
|
|
72
|
+
+ (SELECT count(*) FROM pg_catalog.pg_proc p WHERE p.pronamespace = n.oid) AS n
|
|
73
|
+
FROM pg_catalog.pg_namespace n WHERE n.nspname = $1`, [PUBLIC]);
|
|
74
|
+
return Number(row?.n ?? 0);
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
dialect: "postgres",
|
|
78
|
+
where: `${redactUrl(target.endpoint.url)} (${target.source})`,
|
|
79
|
+
scope,
|
|
80
|
+
async occupied() {
|
|
81
|
+
const others = scope.filter((s) => s !== PUBLIC);
|
|
82
|
+
const there = (await reader.query(`SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname = ANY($1::text[]) ORDER BY nspname`, [others])).map((r) => r.nspname);
|
|
83
|
+
if (scope.includes(PUBLIC) && (await publicObjects()) > 0)
|
|
84
|
+
there.unshift(`${PUBLIC} (not empty)`);
|
|
85
|
+
return there;
|
|
86
|
+
},
|
|
87
|
+
async clear() {
|
|
88
|
+
for (const s of scope) {
|
|
89
|
+
if (s !== PUBLIC)
|
|
90
|
+
await writer.query(`DROP SCHEMA IF EXISTS ${ident(s)} CASCADE`);
|
|
91
|
+
else
|
|
92
|
+
await writer.query(`DROP SCHEMA IF EXISTS ${PUBLIC} CASCADE; CREATE SCHEMA ${PUBLIC}`);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
async statement(m, _index, step) {
|
|
96
|
+
await writer.query(`SELECT pg_catalog.set_config('search_path', $1, false)`, [ident(defaultSchema(m))]);
|
|
97
|
+
await writer.query(step.sql);
|
|
98
|
+
},
|
|
99
|
+
async opStep(m, index) {
|
|
100
|
+
const step = m.document.steps[index];
|
|
101
|
+
if (!step)
|
|
102
|
+
return { refused: `${m.id} has no step ${String(index)}` };
|
|
103
|
+
const plan = planStep(step);
|
|
104
|
+
if (plan.run !== "pgMigration")
|
|
105
|
+
return { refused: plan.run === "refused" ? plan.reason : `step ${String(index)} is not one the replay runs on Postgres` };
|
|
106
|
+
const { summary } = await runStep({
|
|
107
|
+
environment: env.name,
|
|
108
|
+
projectDir: project.dir,
|
|
109
|
+
lockTimeoutMs: target.timeouts?.lockTimeoutMs,
|
|
110
|
+
migration: m,
|
|
111
|
+
index,
|
|
112
|
+
plan,
|
|
113
|
+
log: (line) => {
|
|
114
|
+
log(` ${line}`);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
const s = summary;
|
|
118
|
+
const note = [s?.switched ? "switched" : undefined, s?.dropped ? "old column dropped" : undefined].filter(Boolean).join(", ");
|
|
119
|
+
return { ran: true, ...(note ? { note } : {}) };
|
|
120
|
+
},
|
|
121
|
+
async compare(m) {
|
|
122
|
+
const { pgSchemaFromBuildOutput } = await import("@intentius/chant-lexicon-sql/postgres/plan/schema");
|
|
123
|
+
const { planAgainstClient } = await import("@intentius/chant-lexicon-sql/postgres/plan/commands");
|
|
124
|
+
const ds = defaultSchema(m);
|
|
125
|
+
const recorded = m.document.schema;
|
|
126
|
+
const major = m.document.major ?? (typeof recorded.postgresMajor === "number" ? recorded.postgresMajor : undefined);
|
|
127
|
+
const { declared, diff } = await planAgainstClient(reader, { ...target, schemas: scope, defaultSchema: ds }, pgSchemaFromBuildOutput(JSON.stringify(m.document.schema), ds), {
|
|
128
|
+
...(major !== undefined ? { major } : {}),
|
|
129
|
+
environment: env.name,
|
|
130
|
+
});
|
|
131
|
+
const label = new Map(declared.map((o) => [o.key, `${o.canonical.exportName ?? o.key} (${o.key.split(" ")[1] ?? o.key})`]));
|
|
132
|
+
return diff.changes.map((c) => ({ ...c, object: label.get(c.object) ?? c.object }));
|
|
133
|
+
},
|
|
134
|
+
async close() {
|
|
135
|
+
await writer.end();
|
|
136
|
+
await reader.end();
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=postgres.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/replay/postgres.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The replay check, dialect-free: every migration, in chain order, into a
|
|
3
|
+
* database that starts empty, and after each one the database compared with
|
|
4
|
+
* the schema that migration records (`migration.json`'s `schema`). The first
|
|
5
|
+
* migration whose result differs fails the check, named, with what differs.
|
|
6
|
+
*
|
|
7
|
+
* A migration's recorded schema is what the next `yodel new` diffs from, so
|
|
8
|
+
* a recorded schema that its statements do not produce (a hand edit to
|
|
9
|
+
* either file, a rebase that recomputed one and not the other, a statement
|
|
10
|
+
* chant renders differently from what the server makes) would rot every
|
|
11
|
+
* migration after it. Replaying catches that while it is one migration.
|
|
12
|
+
*
|
|
13
|
+
* Steps, as replay treats them:
|
|
14
|
+
*
|
|
15
|
+
* - a `statement` is sent as `yodel apply` sends it;
|
|
16
|
+
* - a `ClickHouseRebuildOp` step (materialized-view mode) or a
|
|
17
|
+
* `PostgresMigrationOp` step runs, as apply runs it: it changes the
|
|
18
|
+
* table's schema, so the replay needs it;
|
|
19
|
+
* - a backfill step is skipped: it moves data, and the replayed database has
|
|
20
|
+
* none, so it changes nothing the comparison reads;
|
|
21
|
+
* - anything else (a manual step, an Op apply does not run) cannot be
|
|
22
|
+
* replayed: the check stops there, naming it ({@link ReplayResult} outcome
|
|
23
|
+
* `unreplayable`).
|
|
24
|
+
*
|
|
25
|
+
* The database is the replay's: it must be fresh when the replay starts
|
|
26
|
+
* (every database or schema the history names absent, the default one
|
|
27
|
+
* empty), or emptied first when the caller says so (`reset`), and it is
|
|
28
|
+
* emptied again at the end unless `keep`. Nothing is recorded in a history
|
|
29
|
+
* table; no lock is taken.
|
|
30
|
+
*/
|
|
31
|
+
import type { ClassifiedChange } from "@intentius/chant-lexicon-sql/core/classifier";
|
|
32
|
+
import type { StatementStep } from "@intentius/chant-lexicon-sql/migration-statements";
|
|
33
|
+
import { type SqlDialect } from "../project.js";
|
|
34
|
+
import type { Migration } from "../migrations/index.js";
|
|
35
|
+
/** Exit code: the target is not fresh and `reset` was not given; nothing was run. */
|
|
36
|
+
export declare const NOT_FRESH = 4;
|
|
37
|
+
/** Exit code: a step the replay cannot run (a manual step, an Op it does not run). */
|
|
38
|
+
export declare const UNREPLAYABLE = 2;
|
|
39
|
+
/** One difference between the replayed database and a recorded schema: `before` is the replay's, `after` the record's. */
|
|
40
|
+
export type ReplayChange = ClassifiedChange;
|
|
41
|
+
/** What an Op step did in the replay. */
|
|
42
|
+
export type OpStepOutcome = {
|
|
43
|
+
ran: true;
|
|
44
|
+
note?: string;
|
|
45
|
+
} | {
|
|
46
|
+
refused: string;
|
|
47
|
+
};
|
|
48
|
+
/** What the replay needs from a dialect: a server it owns for the run. */
|
|
49
|
+
export interface ReplayDriver {
|
|
50
|
+
dialect: SqlDialect;
|
|
51
|
+
/** The server, for messages (no credentials). */
|
|
52
|
+
where: string;
|
|
53
|
+
/** The databases (ClickHouse) or schemas (Postgres) the history names: the replay empties and compares all of them. */
|
|
54
|
+
scope: readonly string[];
|
|
55
|
+
/** The ones of {@link scope} that are not fresh: there (or, for the default one, not empty). */
|
|
56
|
+
occupied(): Promise<string[]>;
|
|
57
|
+
/** Empties {@link scope}: drops each one, and empties the default one. */
|
|
58
|
+
clear(): Promise<void>;
|
|
59
|
+
/** Sends one statement step of `m`, as apply sends it. Throws the server's error. */
|
|
60
|
+
statement(m: Migration, index: number, step: StatementStep): Promise<void>;
|
|
61
|
+
/** Runs an Op step that is neither a backfill nor refused by {@link planStep}; or says why it cannot. */
|
|
62
|
+
opStep(m: Migration, index: number): Promise<OpStepOutcome>;
|
|
63
|
+
/** The replayed database against `m`'s recorded schema. Empty when they match. */
|
|
64
|
+
compare(m: Migration): Promise<ReplayChange[]>;
|
|
65
|
+
close(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
/** One migration the replay got through. */
|
|
68
|
+
export interface ReplayedMigration {
|
|
69
|
+
id: string;
|
|
70
|
+
statements: number;
|
|
71
|
+
/** Op steps run, as `step <i>: <what>`. */
|
|
72
|
+
ran: string[];
|
|
73
|
+
/** Steps skipped, as `step <i>: <what> (<why>)`. */
|
|
74
|
+
skipped: string[];
|
|
75
|
+
}
|
|
76
|
+
export type ReplayResult = {
|
|
77
|
+
dialect: SqlDialect;
|
|
78
|
+
where: string;
|
|
79
|
+
scope: string[];
|
|
80
|
+
/** Whether the target held anything at the start, and was emptied (`reset`). */
|
|
81
|
+
reset: boolean;
|
|
82
|
+
/** Whether the replayed database was left in place (`keep`). */
|
|
83
|
+
kept: boolean;
|
|
84
|
+
/** The migrations that replayed to their recorded schema, in order. */
|
|
85
|
+
matched: ReplayedMigration[];
|
|
86
|
+
/** How many migrations the history has. */
|
|
87
|
+
total: number;
|
|
88
|
+
} & ({
|
|
89
|
+
outcome: "matches";
|
|
90
|
+
} | {
|
|
91
|
+
outcome: "differs";
|
|
92
|
+
migration: ReplayedMigration;
|
|
93
|
+
changes: ReplayChange[];
|
|
94
|
+
} | {
|
|
95
|
+
outcome: "failed";
|
|
96
|
+
migration: string;
|
|
97
|
+
step: number;
|
|
98
|
+
error: string;
|
|
99
|
+
} | {
|
|
100
|
+
outcome: "unreplayable";
|
|
101
|
+
migration: string;
|
|
102
|
+
step: number;
|
|
103
|
+
reason: string;
|
|
104
|
+
});
|
|
105
|
+
export interface ReplayOptions {
|
|
106
|
+
/** Empty the target first when it is not fresh. Without it, a target that is not fresh is refused ({@link NOT_FRESH}). */
|
|
107
|
+
reset?: boolean;
|
|
108
|
+
/** Leave the replayed database in place at the end, to look at. Default: emptied. */
|
|
109
|
+
keep?: boolean;
|
|
110
|
+
/** Progress, one line at a time. */
|
|
111
|
+
log?: (line: string) => void;
|
|
112
|
+
}
|
|
113
|
+
/** Replays `ordered` (first to newest) through `driver`; see the module comment. Throws only for a target that is not fresh, or a server that fails outside a statement. */
|
|
114
|
+
export declare function replayMigrations(ordered: readonly Migration[], driver: ReplayDriver, options?: ReplayOptions): Promise<ReplayResult>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The replay check, dialect-free: every migration, in chain order, into a
|
|
3
|
+
* database that starts empty, and after each one the database compared with
|
|
4
|
+
* the schema that migration records (`migration.json`'s `schema`). The first
|
|
5
|
+
* migration whose result differs fails the check, named, with what differs.
|
|
6
|
+
*
|
|
7
|
+
* A migration's recorded schema is what the next `yodel new` diffs from, so
|
|
8
|
+
* a recorded schema that its statements do not produce (a hand edit to
|
|
9
|
+
* either file, a rebase that recomputed one and not the other, a statement
|
|
10
|
+
* chant renders differently from what the server makes) would rot every
|
|
11
|
+
* migration after it. Replaying catches that while it is one migration.
|
|
12
|
+
*
|
|
13
|
+
* Steps, as replay treats them:
|
|
14
|
+
*
|
|
15
|
+
* - a `statement` is sent as `yodel apply` sends it;
|
|
16
|
+
* - a `ClickHouseRebuildOp` step (materialized-view mode) or a
|
|
17
|
+
* `PostgresMigrationOp` step runs, as apply runs it: it changes the
|
|
18
|
+
* table's schema, so the replay needs it;
|
|
19
|
+
* - a backfill step is skipped: it moves data, and the replayed database has
|
|
20
|
+
* none, so it changes nothing the comparison reads;
|
|
21
|
+
* - anything else (a manual step, an Op apply does not run) cannot be
|
|
22
|
+
* replayed: the check stops there, naming it ({@link ReplayResult} outcome
|
|
23
|
+
* `unreplayable`).
|
|
24
|
+
*
|
|
25
|
+
* The database is the replay's: it must be fresh when the replay starts
|
|
26
|
+
* (every database or schema the history names absent, the default one
|
|
27
|
+
* empty), or emptied first when the caller says so (`reset`), and it is
|
|
28
|
+
* emptied again at the end unless `keep`. Nothing is recorded in a history
|
|
29
|
+
* table; no lock is taken.
|
|
30
|
+
*/
|
|
31
|
+
import { YodelError } from "../project.js";
|
|
32
|
+
import { asBackfill, describeStep, planStep } from "../steps/kinds.js";
|
|
33
|
+
/** Exit code: the target is not fresh and `reset` was not given; nothing was run. */
|
|
34
|
+
export const NOT_FRESH = 4;
|
|
35
|
+
/** Exit code: a step the replay cannot run (a manual step, an Op it does not run). */
|
|
36
|
+
export const UNREPLAYABLE = 2;
|
|
37
|
+
const firstLine = (s) => s.split("\n")[0] ?? s;
|
|
38
|
+
const message = (e) => (e instanceof Error ? e.message : String(e));
|
|
39
|
+
/** Replays `ordered` (first to newest) through `driver`; see the module comment. Throws only for a target that is not fresh, or a server that fails outside a statement. */
|
|
40
|
+
export async function replayMigrations(ordered, driver, options = {}) {
|
|
41
|
+
const log = options.log ?? (() => undefined);
|
|
42
|
+
const base = { dialect: driver.dialect, where: driver.where, scope: [...driver.scope], total: ordered.length, kept: options.keep === true };
|
|
43
|
+
if (ordered.length === 0)
|
|
44
|
+
return { ...base, reset: false, kept: false, matched: [], outcome: "matches" };
|
|
45
|
+
const occupied = await driver.occupied();
|
|
46
|
+
if (occupied.length > 0) {
|
|
47
|
+
if (options.reset !== true) {
|
|
48
|
+
throw new YodelError(`refused: the replay needs a fresh database, and ${driver.where} already holds ${occupied.join(", ")}. ` +
|
|
49
|
+
`Point the replay at a throwaway emulator (chant emulator up --lexicon sql), or pass reset to drop ${occupied.length === 1 ? "it" : "them"} first. Nothing was run.`, NOT_FRESH);
|
|
50
|
+
}
|
|
51
|
+
log(`emptying ${occupied.join(", ")} on ${driver.where}`);
|
|
52
|
+
await driver.clear();
|
|
53
|
+
}
|
|
54
|
+
const matched = [];
|
|
55
|
+
try {
|
|
56
|
+
for (const m of ordered) {
|
|
57
|
+
const done = { id: m.id, statements: 0, ran: [], skipped: [] };
|
|
58
|
+
log(`${m.id}: replaying`);
|
|
59
|
+
for (const [i, step] of m.document.steps.entries()) {
|
|
60
|
+
const s = step;
|
|
61
|
+
if (s.kind === "statement") {
|
|
62
|
+
try {
|
|
63
|
+
await driver.statement(m, i, s);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
return { ...base, reset: occupied.length > 0, matched, outcome: "failed", migration: m.id, step: i, error: firstLine(message(e)) };
|
|
67
|
+
}
|
|
68
|
+
done.statements++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (asBackfill(s)) {
|
|
72
|
+
done.skipped.push(`step ${String(i)}: ${describeStep(s)} (moves data; the replayed database has none)`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const plan = planStep(s);
|
|
76
|
+
const outcome = plan.run === "refused" ? { refused: plan.reason } : await driver.opStep(m, i);
|
|
77
|
+
if ("refused" in outcome) {
|
|
78
|
+
return { ...base, reset: occupied.length > 0, matched, outcome: "unreplayable", migration: m.id, step: i, reason: outcome.refused };
|
|
79
|
+
}
|
|
80
|
+
done.ran.push(`step ${String(i)}: ${describeStep(s)}${outcome.note ? ` (${outcome.note})` : ""}`);
|
|
81
|
+
}
|
|
82
|
+
const changes = await driver.compare(m);
|
|
83
|
+
if (changes.length > 0) {
|
|
84
|
+
log(`${m.id}: differs from its recorded schema (${String(changes.length)} difference${changes.length === 1 ? "" : "s"})`);
|
|
85
|
+
return { ...base, reset: occupied.length > 0, matched, outcome: "differs", migration: done, changes };
|
|
86
|
+
}
|
|
87
|
+
log(`${m.id}: matches its recorded schema`);
|
|
88
|
+
matched.push(done);
|
|
89
|
+
}
|
|
90
|
+
return { ...base, reset: occupied.length > 0, matched, outcome: "matches" };
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
// Emptying afterwards is housekeeping: its failure is reported, never put in place of the result.
|
|
94
|
+
if (options.keep !== true)
|
|
95
|
+
await driver.clear().catch((e) => { log(`could not empty ${driver.scope.join(", ")} afterwards: ${firstLine(message(e))}`); });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=replay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay.js","sourceRoot":"","sources":["../../src/replay/replay.ts"],"names":[],"mappings":"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"}
|