@intentius/sql-yodeler 0.3.0 → 0.4.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/README.md +1 -1
- package/dist/apply-steps/index.d.ts +65 -0
- package/dist/apply-steps/index.js +106 -0
- package/dist/apply-steps/index.js.map +1 -0
- package/dist/approval-mode/index.d.ts +78 -0
- package/dist/approval-mode/index.js +105 -0
- package/dist/approval-mode/index.js.map +1 -0
- package/dist/chant/access.d.ts +87 -0
- package/dist/chant/access.js +169 -0
- package/dist/chant/access.js.map +1 -0
- package/dist/chant/drift.d.ts +17 -1
- package/dist/chant/drift.js +55 -2
- package/dist/chant/drift.js.map +1 -1
- package/dist/chant/plan.d.ts +18 -2
- package/dist/chant/plan.js +21 -3
- package/dist/chant/plan.js.map +1 -1
- package/dist/ci/render.d.ts +54 -10
- package/dist/ci/render.js +249 -52
- package/dist/ci/render.js.map +1 -1
- package/dist/cleanup/index.d.ts +86 -0
- package/dist/cleanup/index.js +207 -0
- package/dist/cleanup/index.js.map +1 -0
- package/dist/commands/apply.js +153 -20
- package/dist/commands/apply.js.map +1 -1
- package/dist/commands/approve.d.ts +13 -0
- package/dist/commands/approve.js +206 -24
- package/dist/commands/approve.js.map +1 -1
- package/dist/commands/ci.js +12 -0
- package/dist/commands/ci.js.map +1 -1
- package/dist/commands/cleanup.d.ts +13 -0
- package/dist/commands/cleanup.js +138 -0
- package/dist/commands/cleanup.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.js +136 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/drift.js +34 -3
- package/dist/commands/drift.js.map +1 -1
- package/dist/commands/index.js +6 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/lint.js +30 -6
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/new.js +133 -34
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/plan.js +42 -5
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/test.d.ts +3 -0
- package/dist/commands/test.js +164 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/config/define.d.ts +117 -2
- package/dist/config/define.js +2 -0
- package/dist/config/define.js.map +1 -1
- package/dist/config/load.js +74 -10
- package/dist/config/load.js.map +1 -1
- package/dist/config/settings.d.ts +18 -0
- package/dist/config/settings.js +26 -0
- package/dist/config/settings.js.map +1 -1
- package/dist/credentials.d.ts +119 -0
- package/dist/credentials.js +119 -0
- package/dist/credentials.js.map +1 -0
- package/dist/exit.js +1 -1
- package/dist/exit.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/init/adopt.js +1 -1
- package/dist/init/adopt.js.map +1 -1
- package/dist/init/clickhouse-access.d.ts +64 -0
- package/dist/init/clickhouse-access.js +211 -0
- package/dist/init/clickhouse-access.js.map +1 -0
- package/dist/init/live.d.ts +4 -0
- package/dist/init/live.js +16 -0
- package/dist/init/live.js.map +1 -1
- package/dist/lint/access.d.ts +18 -0
- package/dist/lint/access.js +34 -0
- package/dist/lint/access.js.map +1 -0
- package/dist/lint/check.js +23 -6
- package/dist/lint/check.js.map +1 -1
- package/dist/lint/config.d.ts +7 -1
- package/dist/lint/config.js +11 -5
- package/dist/lint/config.js.map +1 -1
- package/dist/lint/index.d.ts +2 -0
- package/dist/lint/index.js +2 -0
- package/dist/lint/index.js.map +1 -1
- package/dist/lint/naming.d.ts +62 -0
- package/dist/lint/naming.js +138 -0
- package/dist/lint/naming.js.map +1 -0
- package/dist/lint/rules.d.ts +37 -4
- package/dist/lint/rules.js +16 -4
- package/dist/lint/rules.js.map +1 -1
- package/dist/lint/schema-checks.d.ts +63 -0
- package/dist/lint/schema-checks.js +128 -0
- package/dist/lint/schema-checks.js.map +1 -0
- package/dist/lint/statements.d.ts +22 -1
- package/dist/lint/statements.js +40 -1
- package/dist/lint/statements.js.map +1 -1
- package/dist/mcp/tools.d.ts +1 -1
- package/dist/mcp/tools.js +1 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/migrations/checkpoint.js +0 -2
- package/dist/migrations/checkpoint.js.map +1 -1
- package/dist/migrations/checks.d.ts +19 -1
- package/dist/migrations/checks.js +15 -0
- package/dist/migrations/checks.js.map +1 -1
- package/dist/migrations/create.d.ts +13 -4
- package/dist/migrations/create.js +24 -9
- package/dist/migrations/create.js.map +1 -1
- package/dist/migrations/routines.d.ts +5 -15
- package/dist/migrations/routines.js +5 -30
- package/dist/migrations/routines.js.map +1 -1
- package/dist/pr/model.d.ts +4 -1
- package/dist/pr/model.js +13 -5
- package/dist/pr/model.js.map +1 -1
- package/dist/pr/render.d.ts +4 -1
- package/dist/pr/render.js +22 -8
- package/dist/pr/render.js.map +1 -1
- package/dist/pr/tenants.d.ts +37 -0
- package/dist/pr/tenants.js +138 -0
- package/dist/pr/tenants.js.map +1 -0
- package/dist/probe.d.ts +27 -0
- package/dist/probe.js +106 -0
- package/dist/probe.js.map +1 -0
- package/dist/project.d.ts +16 -0
- package/dist/project.js +29 -1
- package/dist/project.js.map +1 -1
- package/dist/replay/clickhouse.d.ts +31 -0
- package/dist/replay/clickhouse.js +103 -6
- package/dist/replay/clickhouse.js.map +1 -1
- package/dist/replay/replay.d.ts +5 -2
- package/dist/replay/replay.js.map +1 -1
- package/dist/report/audit.d.ts +1 -1
- package/dist/report/audit.js +20 -3
- package/dist/report/audit.js.map +1 -1
- package/dist/schemas.d.ts +3 -1
- package/dist/schemas.js +2 -0
- package/dist/schemas.js.map +1 -1
- package/dist/sources/ddl.d.ts +15 -20
- package/dist/sources/ddl.js +34 -328
- package/dist/sources/ddl.js.map +1 -1
- package/dist/sources/index.js +2 -2
- package/dist/sources/index.js.map +1 -1
- package/dist/steps/backfill.d.ts +40 -5
- package/dist/steps/backfill.js +128 -8
- package/dist/steps/backfill.js.map +1 -1
- package/dist/steps/index.d.ts +19 -1
- package/dist/steps/index.js +34 -7
- package/dist/steps/index.js.map +1 -1
- package/dist/steps/kinds.d.ts +42 -4
- package/dist/steps/kinds.js +88 -14
- package/dist/steps/kinds.js.map +1 -1
- package/dist/steps/postgres-migration.d.ts +3 -0
- package/dist/steps/postgres-migration.js +7 -3
- package/dist/steps/postgres-migration.js.map +1 -1
- package/dist/steps/rebuild.d.ts +15 -6
- package/dist/steps/rebuild.js +12 -9
- package/dist/steps/rebuild.js.map +1 -1
- package/dist/tenants/index.d.ts +2 -1
- package/dist/tenants/index.js +3 -4
- package/dist/tenants/index.js.map +1 -1
- package/dist/tenants/wave.d.ts +128 -0
- package/dist/tenants/wave.js +254 -0
- package/dist/tenants/wave.js.map +1 -0
- package/dist/testing/define.d.ts +62 -0
- package/dist/testing/define.js +32 -0
- package/dist/testing/define.js.map +1 -0
- package/dist/testing/files.d.ts +5 -0
- package/dist/testing/files.js +18 -0
- package/dist/testing/files.js.map +1 -0
- package/dist/testing/report.d.ts +14 -0
- package/dist/testing/report.js +83 -0
- package/dist/testing/report.js.map +1 -0
- package/dist/testing/run.d.ts +104 -0
- package/dist/testing/run.js +328 -0
- package/dist/testing/run.js.map +1 -0
- package/dist/versioned/approval.d.ts +6 -0
- package/dist/versioned/approval.js +11 -5
- package/dist/versioned/approval.js.map +1 -1
- package/dist/versioned/checks.d.ts +8 -2
- package/dist/versioned/checks.js +34 -13
- package/dist/versioned/checks.js.map +1 -1
- package/dist/versioned/driver.d.ts +2 -2
- package/dist/versioned/engine.d.ts +10 -0
- package/dist/versioned/engine.js +8 -4
- package/dist/versioned/engine.js.map +1 -1
- package/dist/versioned/explain.d.ts +2 -0
- package/dist/versioned/explain.js +3 -1
- package/dist/versioned/explain.js.map +1 -1
- package/dist/versioned/state.js +1 -1
- package/dist/versioned/state.js.map +1 -1
- package/dist/waves/index.d.ts +58 -13
- package/dist/waves/index.js +109 -24
- package/dist/waves/index.js.map +1 -1
- package/dist/waves/record.d.ts +10 -0
- package/dist/waves/record.js.map +1 -1
- package/package.json +7 -7
- package/schemas/apply.schema.json +1 -1
- package/schemas/cleanup.schema.json +40 -0
- package/schemas/lint-rules.schema.json +4 -0
- package/schemas/new.schema.json +41 -3
- package/schemas/plan.schema.json +39 -0
- package/schemas/report.schema.json +1 -1
- package/schemas/test.schema.json +41 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yodel test` (#54): the cases of a project's `tests/*.test.ts`
|
|
3
|
+
* (`./define.ts`), each run on a database built by replaying the
|
|
4
|
+
* migrations on a throwaway server.
|
|
5
|
+
*
|
|
6
|
+
* Per case, on the server the environment's profile binds:
|
|
7
|
+
*
|
|
8
|
+
* 1. the databases (ClickHouse) or schemas (Postgres) the history names are
|
|
9
|
+
* emptied, so every case starts from nothing;
|
|
10
|
+
* 2. the migrations up to the case's `at` are replayed as `yodel lint
|
|
11
|
+
* --replay` replays them: statements, and the rebuild and
|
|
12
|
+
* PostgresMigrationOp steps apply runs, but not backfills, which would
|
|
13
|
+
* find no rows;
|
|
14
|
+
* 3. the `seed` statements run;
|
|
15
|
+
* 4. the migrations after `at`, up to `through`, run with every step,
|
|
16
|
+
* backfills included.
|
|
17
|
+
*
|
|
18
|
+
* A case's receipts, a backfill's batches and a ClickHouse rebuild's
|
|
19
|
+
* partitions, are kept in memory for that case alone (#112): nothing is
|
|
20
|
+
* written to the server's receipts table, and no case skips work because
|
|
21
|
+
* another case did it, or a run before this one.
|
|
22
|
+
* 5. each expectation is checked, every one even after one fails.
|
|
23
|
+
*
|
|
24
|
+
* A checkpoint (#63) is left out of the chain, as the replay leaves it out.
|
|
25
|
+
*
|
|
26
|
+
* It never runs against an environment yodel applies to: an environment
|
|
27
|
+
* whose history table holds a row is refused, `--reset` or not, and so is a
|
|
28
|
+
* target that already holds what the history names (unless `--reset`), and
|
|
29
|
+
* one another profile on the same server names (unless `--reset-shared`),
|
|
30
|
+
* as the replay refuses them. Nothing is written to a history. The
|
|
31
|
+
* databases are emptied again at the end unless `keep`.
|
|
32
|
+
*/
|
|
33
|
+
import { relative } from "node:path";
|
|
34
|
+
import { pathToFileURL } from "node:url";
|
|
35
|
+
import { YodelError } from "../project.js";
|
|
36
|
+
import { EXIT_REFUSED } from "../exit.js";
|
|
37
|
+
import { environmentSettings } from "../config/settings.js";
|
|
38
|
+
import { isCheckpoint, readMigrations } from "../migrations/index.js";
|
|
39
|
+
import { migrationsDir } from "../versioned/index.js";
|
|
40
|
+
import { httpPort } from "../history/index.js";
|
|
41
|
+
import { asBackfill, defaultStepRunner, planStep } from "../steps/index.js";
|
|
42
|
+
import { NOT_FRESH, openReplay, refuseShared, sharedProfiles } from "../replay/index.js";
|
|
43
|
+
import { testFiles } from "./files.js";
|
|
44
|
+
export { TESTS_DIR, testFiles } from "./files.js";
|
|
45
|
+
const message = (e) => (e instanceof Error ? e.message : String(e));
|
|
46
|
+
const firstLine = (s) => s.split("\n")[0] ?? s;
|
|
47
|
+
const isRecord = (v) => typeof v === "object" && v !== null;
|
|
48
|
+
/** What is wrong with a case, if anything. */
|
|
49
|
+
function caseProblems(c, i) {
|
|
50
|
+
if (!isRecord(c))
|
|
51
|
+
return [`case ${String(i)} is not an object`];
|
|
52
|
+
const name = typeof c.name === "string" && c.name !== "" ? c.name : undefined;
|
|
53
|
+
const at = name ?? `case ${String(i)}`;
|
|
54
|
+
const out = [];
|
|
55
|
+
if (!name)
|
|
56
|
+
out.push(`case ${String(i)} has no name`);
|
|
57
|
+
for (const k of ["at", "through"])
|
|
58
|
+
if (c[k] !== undefined && typeof c[k] !== "string")
|
|
59
|
+
out.push(`${at}: ${k} is a migration id`);
|
|
60
|
+
if (c.seed !== undefined && !(Array.isArray(c.seed) && c.seed.every((s) => typeof s === "string")))
|
|
61
|
+
out.push(`${at}: seed is a list of SQL statements`);
|
|
62
|
+
if (!Array.isArray(c.expect) || c.expect.length === 0)
|
|
63
|
+
out.push(`${at}: expect lists at least one expectation (expectRows, expectValue, expectError)`);
|
|
64
|
+
else if (!c.expect.every((e) => isRecord(e) && typeof e.sql === "string" && ["rows", "value", "error"].includes(String(e.kind))))
|
|
65
|
+
out.push(`${at}: every expectation is made with expectRows, expectValue or expectError`);
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/** Imports each test file and returns its cases: the default export, or `tests`. Throws naming what is wrong. */
|
|
69
|
+
export async function loadTests(projectDir, files = testFiles(projectDir)) {
|
|
70
|
+
const out = [];
|
|
71
|
+
for (const path of files) {
|
|
72
|
+
const file = relative(projectDir, path);
|
|
73
|
+
let mod;
|
|
74
|
+
try {
|
|
75
|
+
mod = (await import(`${pathToFileURL(path).href}?yodel=${String(Date.now())}`));
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
throw new YodelError(`${file}: could not load it: ${message(e)}`);
|
|
79
|
+
}
|
|
80
|
+
const cases = mod.default ?? mod.tests;
|
|
81
|
+
if (!Array.isArray(cases))
|
|
82
|
+
throw new YodelError(`${file} exports no cases: export default defineTests([...])`);
|
|
83
|
+
const problems = cases.flatMap((c, i) => caseProblems(c, i));
|
|
84
|
+
if (problems.length > 0)
|
|
85
|
+
throw new YodelError(`${file}: ${problems.join("; ")}`);
|
|
86
|
+
out.push({ file, cases: cases });
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/** A value as text, for comparing what a query returned with what a case expects. */
|
|
91
|
+
export function asText(v) {
|
|
92
|
+
if (v === null || v === undefined)
|
|
93
|
+
return "NULL";
|
|
94
|
+
if (v instanceof Date)
|
|
95
|
+
return v.toISOString();
|
|
96
|
+
if (typeof v === "string")
|
|
97
|
+
return v;
|
|
98
|
+
if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint")
|
|
99
|
+
return String(v);
|
|
100
|
+
return JSON.stringify(v);
|
|
101
|
+
}
|
|
102
|
+
const show = (rows) => JSON.stringify(rows.map((r) => r.map(asText)));
|
|
103
|
+
/** Why `e` does not hold for `outcome`, or undefined when it holds. */
|
|
104
|
+
export function expectationFailure(e, outcome) {
|
|
105
|
+
const sql = firstLine(e.sql).slice(0, 120);
|
|
106
|
+
if (e.kind === "error") {
|
|
107
|
+
if (!("error" in outcome))
|
|
108
|
+
return `expected an error from ${sql}, and it succeeded`;
|
|
109
|
+
if (e.match === undefined)
|
|
110
|
+
return undefined;
|
|
111
|
+
const ok = typeof e.match === "string" ? outcome.error.includes(e.match) : new RegExp(e.match.source, e.match.flags.replace("g", "")).test(outcome.error);
|
|
112
|
+
return ok ? undefined : `${sql} failed, but not with ${String(e.match)}: ${firstLine(outcome.error)}`;
|
|
113
|
+
}
|
|
114
|
+
if ("error" in outcome)
|
|
115
|
+
return `${sql} failed: ${firstLine(outcome.error)}`;
|
|
116
|
+
const rows = outcome.rows.map((r) => Object.values(r));
|
|
117
|
+
if (e.kind === "value") {
|
|
118
|
+
const first = rows[0];
|
|
119
|
+
if (!first || first.length === 0)
|
|
120
|
+
return `${sql} returned no row; expected ${asText(e.value)}`;
|
|
121
|
+
return asText(first[0]) === asText(e.value) ? undefined : `${sql} returned ${asText(first[0])}; expected ${asText(e.value)}`;
|
|
122
|
+
}
|
|
123
|
+
return show(rows) === show(e.rows) ? undefined : `${sql} returned ${show(rows)}; expected ${show(e.rows)}`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* A receipt store for one case: a backfill's batches and a rebuild's
|
|
127
|
+
* partitions each run once in it, and nothing outlives the case.
|
|
128
|
+
*
|
|
129
|
+
* It keeps each receipt under its effect. A rebuild reads its receipts with
|
|
130
|
+
* `readAll(prefix)` and looks each up by address, the effect after the
|
|
131
|
+
* store's identity (`<stack>/<env>/`); the identity is the part of the
|
|
132
|
+
* prefix before the effects it asks for.
|
|
133
|
+
*/
|
|
134
|
+
export function memoryReceipts() {
|
|
135
|
+
const kept = new Map();
|
|
136
|
+
return {
|
|
137
|
+
read: (r) => Promise.resolve(kept.get(r.effect)),
|
|
138
|
+
readAll: (prefix) => {
|
|
139
|
+
const out = new Map();
|
|
140
|
+
for (const [effect, expectation] of kept) {
|
|
141
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
142
|
+
if ((i === 0 || prefix[i - 1] === "/") && effect.startsWith(prefix.slice(i))) {
|
|
143
|
+
out.set(prefix.slice(0, i) + effect, expectation);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return Promise.resolve(out);
|
|
149
|
+
},
|
|
150
|
+
write: (r, expectation) => {
|
|
151
|
+
kept.set(r.effect, expectation);
|
|
152
|
+
return Promise.resolve();
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async function openSession(project, env, processEnv) {
|
|
157
|
+
const history = environmentSettings(project, env.name, processEnv).historyDatabase;
|
|
158
|
+
if (project.dialect === "postgres") {
|
|
159
|
+
const { resolvePostgresTarget, isUnresolvedTarget } = await import("@intentius/chant-lexicon-sql/postgres/live/bind");
|
|
160
|
+
const target = resolvePostgresTarget({ environment: env.name, config: project.config, env: processEnv });
|
|
161
|
+
if (isUnresolvedTarget(target))
|
|
162
|
+
throw new YodelError(target.detail);
|
|
163
|
+
const { connectPostgres } = await import("@intentius/chant-lexicon-sql/postgres/live/client");
|
|
164
|
+
const { DEFAULT_PG_TIMEOUTS } = await import("../versioned/postgres/executor.js");
|
|
165
|
+
const client = await connectPostgres(target.endpoint, { applicationName: "yodel test" });
|
|
166
|
+
const timeouts = { ...DEFAULT_PG_TIMEOUTS, ...(target.timeouts ?? {}) };
|
|
167
|
+
return {
|
|
168
|
+
query: (sql) => client.query(sql),
|
|
169
|
+
backfillContext: () => ({ postgres: { client, timeouts, receiptsSchema: history } }),
|
|
170
|
+
async historyRows() {
|
|
171
|
+
const [t] = await client.query(`SELECT to_regclass($1)::text AS t`, [`"${history.replace(/"/g, '""')}".history`]);
|
|
172
|
+
if (!t?.t)
|
|
173
|
+
return 0;
|
|
174
|
+
const [n] = await client.query(`SELECT count(*)::text AS n FROM "${history.replace(/"/g, '""')}".history`);
|
|
175
|
+
return Number(n?.n ?? 0);
|
|
176
|
+
},
|
|
177
|
+
close: () => client.end(),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const { resolveClickHouseTarget, isUnresolvedTarget } = await import("@intentius/chant-lexicon-sql/clickhouse/live/bind");
|
|
181
|
+
const target = resolveClickHouseTarget({ environment: env.name, config: project.config, env: processEnv });
|
|
182
|
+
if (isUnresolvedTarget(target))
|
|
183
|
+
throw new YodelError(target.detail);
|
|
184
|
+
const port = await httpPort(target.endpoint);
|
|
185
|
+
const q = (s) => `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
186
|
+
return {
|
|
187
|
+
query: (sql) => port.query(sql),
|
|
188
|
+
backfillContext: () => ({ port, endpoint: target.endpoint }),
|
|
189
|
+
async historyRows() {
|
|
190
|
+
const [t] = await port.query(`SELECT count() AS n FROM system.tables WHERE database = ${q(history)} AND name = 'history'`);
|
|
191
|
+
if (Number(t?.n ?? 0) === 0)
|
|
192
|
+
return 0;
|
|
193
|
+
const [n] = await port.query(`SELECT count() AS n FROM \`${history.replace(/`/g, "``")}\`.history`);
|
|
194
|
+
return Number(n?.n ?? 0);
|
|
195
|
+
},
|
|
196
|
+
close: () => Promise.resolve(),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/** Runs the project's test cases on `env`'s server; see the module comment. */
|
|
200
|
+
export async function runTests(project, env, files, options = {}) {
|
|
201
|
+
const processEnv = options.processEnv ?? process.env;
|
|
202
|
+
const log = options.log ?? (() => undefined);
|
|
203
|
+
const runStep = options.runStep ?? defaultStepRunner;
|
|
204
|
+
const ordered = readMigrations(migrationsDir(project));
|
|
205
|
+
const chain = ordered.filter((m) => !isCheckpoint(m));
|
|
206
|
+
const ids = chain.map((m) => m.id);
|
|
207
|
+
const driver = await openReplay(project, env, ordered, { processEnv, log });
|
|
208
|
+
let session;
|
|
209
|
+
// Set once every refusal is behind: only then does anything empty the target.
|
|
210
|
+
let started = false;
|
|
211
|
+
try {
|
|
212
|
+
refuseShared(env, await sharedProfiles(project, env, driver.scope, processEnv), options.resetShared ?? [], project.dialect);
|
|
213
|
+
session = await openSession(project, env, processEnv);
|
|
214
|
+
const rows = await session.historyRows();
|
|
215
|
+
if (rows > 0) {
|
|
216
|
+
throw new YodelError(`refused: ${env.name} has a migration history (${String(rows)} rows in ${environmentSettings(project, env.name, processEnv).historyDatabase}.history): it is an environment yodel applies to, and yodel test empties what the history names. ` +
|
|
217
|
+
`Point --env at a throwaway server's profile (chant emulator up --lexicon sql). Nothing was run.`, EXIT_REFUSED);
|
|
218
|
+
}
|
|
219
|
+
const occupied = await driver.occupied();
|
|
220
|
+
if (occupied.length > 0 && options.reset !== true) {
|
|
221
|
+
throw new YodelError(`refused: yodel test needs a fresh database, and ${driver.where} already holds ${occupied.join(", ")}. ` +
|
|
222
|
+
`Point --env at a throwaway server, or pass --reset to drop ${occupied.length === 1 ? "it" : "them"} first. Nothing was run.`, NOT_FRESH);
|
|
223
|
+
}
|
|
224
|
+
started = true;
|
|
225
|
+
const s = session;
|
|
226
|
+
const results = [];
|
|
227
|
+
const runMigration = async (m, backfills, receipts) => {
|
|
228
|
+
for (const [i, step] of m.document.steps.entries()) {
|
|
229
|
+
const st = step;
|
|
230
|
+
if (st.kind === "statement") {
|
|
231
|
+
await driver.statement(m, i, st);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const backfill = asBackfill(st);
|
|
235
|
+
if (backfill) {
|
|
236
|
+
if (!backfills)
|
|
237
|
+
continue;
|
|
238
|
+
const plan = planStep(st);
|
|
239
|
+
if (plan.run !== "backfill")
|
|
240
|
+
throw new Error(`${m.id} step ${String(i)}: not a backfill yodel runs`);
|
|
241
|
+
await runStep({ environment: env.name, projectDir: project.dir, migration: m, index: i, plan, log: (l) => { log(` ${l}`); }, receipts, ...s.backfillContext() });
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const plan = planStep(st);
|
|
245
|
+
if (plan.run === "refused")
|
|
246
|
+
throw new Error(`${m.id} step ${String(i)} cannot run here: ${plan.reason}`);
|
|
247
|
+
const outcome = await driver.opStep(m, i, { receipts });
|
|
248
|
+
if ("refused" in outcome)
|
|
249
|
+
throw new Error(`${m.id} step ${String(i)} cannot run here: ${outcome.refused}`);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
const index = (id, what) => {
|
|
253
|
+
if (id === undefined)
|
|
254
|
+
return ids.length - 1;
|
|
255
|
+
const i = ids.indexOf(id);
|
|
256
|
+
if (i < 0)
|
|
257
|
+
throw new Error(`${what} ${id}: no such migration in the chain${ordered.some((m) => m.id === id) ? " (a checkpoint is left out of it)" : ""}`);
|
|
258
|
+
return i;
|
|
259
|
+
};
|
|
260
|
+
for (const f of files) {
|
|
261
|
+
for (const c of f.cases) {
|
|
262
|
+
if (options.run && !options.run.test(c.name))
|
|
263
|
+
continue;
|
|
264
|
+
const t0 = Date.now();
|
|
265
|
+
const result = { file: f.file, name: c.name, status: "passed", ms: 0, failures: [], before: [], after: [] };
|
|
266
|
+
log(`${f.file}: ${c.name}`);
|
|
267
|
+
try {
|
|
268
|
+
const at = index(c.at, "at");
|
|
269
|
+
const through = index(c.through, "through");
|
|
270
|
+
if (through < at)
|
|
271
|
+
throw new Error(`through ${String(c.through)} comes before at ${String(c.at)}`);
|
|
272
|
+
const receipts = memoryReceipts();
|
|
273
|
+
await driver.clear();
|
|
274
|
+
for (const m of chain.slice(0, at + 1)) {
|
|
275
|
+
await runMigration(m, false, receipts);
|
|
276
|
+
result.before.push(m.id);
|
|
277
|
+
}
|
|
278
|
+
for (const [i, sql] of (c.seed ?? []).entries()) {
|
|
279
|
+
try {
|
|
280
|
+
await s.query(sql);
|
|
281
|
+
}
|
|
282
|
+
catch (e) {
|
|
283
|
+
throw new Error(`seed statement ${String(i)} failed: ${firstLine(message(e))}`, { cause: e });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const m of chain.slice(at + 1, through + 1)) {
|
|
287
|
+
try {
|
|
288
|
+
await runMigration(m, true, receipts);
|
|
289
|
+
}
|
|
290
|
+
catch (e) {
|
|
291
|
+
throw new Error(`${m.id} failed after the seed: ${firstLine(message(e))}`, { cause: e });
|
|
292
|
+
}
|
|
293
|
+
result.after.push(m.id);
|
|
294
|
+
}
|
|
295
|
+
for (const e of c.expect) {
|
|
296
|
+
let outcome;
|
|
297
|
+
try {
|
|
298
|
+
outcome = { rows: await s.query(e.sql) };
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
outcome = { error: message(err) };
|
|
302
|
+
}
|
|
303
|
+
const failure = expectationFailure(e, outcome);
|
|
304
|
+
if (failure !== undefined)
|
|
305
|
+
result.failures.push(failure);
|
|
306
|
+
}
|
|
307
|
+
if (result.failures.length > 0)
|
|
308
|
+
result.status = "failed";
|
|
309
|
+
}
|
|
310
|
+
catch (e) {
|
|
311
|
+
result.status = "error";
|
|
312
|
+
result.error = firstLine(message(e));
|
|
313
|
+
}
|
|
314
|
+
result.ms = Date.now() - t0;
|
|
315
|
+
log(` ${result.status}${result.error ? `: ${result.error}` : ""}${result.failures.map((x) => `\n ${x}`).join("")}`);
|
|
316
|
+
results.push(result);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return { environment: env.name, dialect: project.dialect, where: driver.where, scope: [...driver.scope], reset: occupied.length > 0, files: files.map((f) => f.file), cases: results };
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
if (started && options.keep !== true)
|
|
323
|
+
await driver.clear().catch((e) => { log(`could not empty ${driver.scope.join(", ")} afterwards: ${firstLine(message(e))}`); });
|
|
324
|
+
await session?.close();
|
|
325
|
+
await driver.close();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/testing/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,6 +30,12 @@ export declare const NOT_APPROVED = 3;
|
|
|
30
30
|
export declare function verifyApproval(port: GateLedgerPort, op: MigrateOpRef, digest: string): Promise<{
|
|
31
31
|
resolvedBy: string;
|
|
32
32
|
timestamp: string;
|
|
33
|
+
review?: {
|
|
34
|
+
pr: number;
|
|
35
|
+
head: string;
|
|
36
|
+
approvers: string[];
|
|
37
|
+
};
|
|
38
|
+
signer?: string;
|
|
33
39
|
}>;
|
|
34
40
|
/** The ledger as the gate step reads it, in the project's checkout. */
|
|
35
41
|
export declare function gitLedger(cwd: string): Promise<GateLedgerPort>;
|
|
@@ -37,11 +37,17 @@ export async function verifyApproval(port, op, digest) {
|
|
|
37
37
|
const resolutions = ledger.resolutions.filter((r) => r.gate !== op.gate || rule === null || rule.refuses(r) === null);
|
|
38
38
|
const since = latestPendingGate(ledger.pending, op.gate)?.timestamp ?? EPOCH;
|
|
39
39
|
const found = latestResolutionForPlan(resolutions, op.gate, since, digest);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const r = found.resolution;
|
|
41
|
+
if (r)
|
|
42
|
+
return { resolvedBy: r.resolvedBy, timestamp: r.timestamp, ...(r.review ? { review: r.review } : {}), ...(r.seal ? { signer: r.seal.signer } : {}) };
|
|
43
|
+
// An approval of this digest the gate's rule does not count (#48: one not sealed by a signer at base), and why.
|
|
44
|
+
const uncounted = rule === null ? undefined : latestResolutionForPlan(ledger.resolutions, op.gate, since, digest).resolution;
|
|
45
|
+
const why = uncounted ? rule?.refuses(uncounted) : null;
|
|
46
|
+
const other = why
|
|
47
|
+
? ` (the approval by ${uncounted?.resolvedBy ?? "?"} at ${uncounted?.timestamp ?? "?"} does not count: ${why})`
|
|
48
|
+
: found.mismatched
|
|
49
|
+
? ` (the newest approval of that gate is for ${found.mismatched.planDigest ?? "no plan"}, by ${found.mismatched.resolvedBy} at ${found.mismatched.timestamp})`
|
|
50
|
+
: "";
|
|
45
51
|
throw new YodelError(`refused: chant's gate ledger holds no approval of ${op.name} / ${op.gate} for plan ${digest}${other}. Nothing was applied. ` +
|
|
46
52
|
`Run yodel apply without --execute; it stops at the gate and prints the chant approve command.`, NOT_APPROVED);
|
|
47
53
|
}
|
|
@@ -1 +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,
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../../src/versioned/approval.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,12 @@
|
|
|
14
14
|
*
|
|
15
15
|
* A migration resuming part way (a statement of it already succeeded) does
|
|
16
16
|
* not run its checks again: they held before its first statement.
|
|
17
|
+
*
|
|
18
|
+
* Each statement's pre-check (#51, `migrationChecks`) runs with them, the
|
|
19
|
+
* same way: a count of the rows the statement would fail on, which must be
|
|
20
|
+
* 0. A refusal names the statement and the count. A pre-check whose table
|
|
21
|
+
* or column an earlier statement of the same migration makes cannot run
|
|
22
|
+
* before the first statement; it is passed, since no row of it exists yet.
|
|
17
23
|
*/
|
|
18
24
|
import { YodelError } from "../project.js";
|
|
19
25
|
import { type CheckOutcome, type Migration } from "../migrations/index.js";
|
|
@@ -21,9 +27,9 @@ import type { VersionedDriver } from "./driver.js";
|
|
|
21
27
|
import type { PendingMigration } from "./state.js";
|
|
22
28
|
/** Whether `p`'s checks run before it: it has some, and no statement of it has succeeded yet. */
|
|
23
29
|
export declare const checksDue: (p: PendingMigration) => boolean;
|
|
24
|
-
/** Runs each of `m`'s checks; a query the server refuses is a failed check. */
|
|
30
|
+
/** Runs each of `m`'s checks and pre-checks; a query the server refuses is a failed check. */
|
|
25
31
|
export declare function runChecks(driver: VersionedDriver, m: Migration): Promise<CheckOutcome[]>;
|
|
26
|
-
/** The checks that failed, one line each. */
|
|
32
|
+
/** The checks that failed, one line each; a pre-check's names its statement and the count. */
|
|
27
33
|
export declare function failedChecks(m: Migration, outcomes: readonly CheckOutcome[]): string[];
|
|
28
34
|
/** The refusal when a check of `m` failed, or `undefined`. */
|
|
29
35
|
export declare function checksRefusal(m: Migration, outcomes: readonly CheckOutcome[], when: string): YodelError | undefined;
|
package/dist/versioned/checks.js
CHANGED
|
@@ -14,34 +14,54 @@
|
|
|
14
14
|
*
|
|
15
15
|
* A migration resuming part way (a statement of it already succeeded) does
|
|
16
16
|
* not run its checks again: they held before its first statement.
|
|
17
|
+
*
|
|
18
|
+
* Each statement's pre-check (#51, `migrationChecks`) runs with them, the
|
|
19
|
+
* same way: a count of the rows the statement would fail on, which must be
|
|
20
|
+
* 0. A refusal names the statement and the count. A pre-check whose table
|
|
21
|
+
* or column an earlier statement of the same migration makes cannot run
|
|
22
|
+
* before the first statement; it is passed, since no row of it exists yet.
|
|
17
23
|
*/
|
|
18
24
|
import { YodelError } from "../project.js";
|
|
19
25
|
import { EXIT_REFUSED } from "../exit.js";
|
|
20
|
-
import { describeExpect, judgeCheck } from "../migrations/index.js";
|
|
26
|
+
import { describeExpect, judgeCheck, migrationChecks } from "../migrations/index.js";
|
|
21
27
|
/** 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.
|
|
23
|
-
/**
|
|
28
|
+
export const checksDue = (p) => migrationChecks(p.migration.document).length > 0 && (p.partWay?.done.length ?? 0) === 0;
|
|
29
|
+
/** The server's refusal of a query over a table or column that does not exist yet. */
|
|
30
|
+
const NOT_THERE = /\b(relation|column) "[^"]+"( of relation "[^"]+")? does not exist\b/;
|
|
31
|
+
/** Runs each of `m`'s checks and pre-checks; a query the server refuses is a failed check. */
|
|
24
32
|
export async function runChecks(driver, m) {
|
|
25
33
|
const where = { defaultDatabase: m.document.defaultDatabase, defaultSchema: m.document.defaultSchema };
|
|
26
34
|
const out = [];
|
|
27
|
-
for (const check of m.document
|
|
35
|
+
for (const check of migrationChecks(m.document)) {
|
|
28
36
|
try {
|
|
29
37
|
out.push(judgeCheck(check, await driver.readCheck(check.sql, where)));
|
|
30
38
|
}
|
|
31
39
|
catch (e) {
|
|
32
|
-
|
|
40
|
+
const message = (e instanceof Error ? e.message : String(e)).split("\n")[0] ?? "";
|
|
41
|
+
if (check.step && check.step.index > 0 && NOT_THERE.test(message)) {
|
|
42
|
+
out.push({ name: check.name, passed: true, detail: `not run: ${message}, which an earlier statement of ${m.id} makes` });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
out.push({ name: check.name, passed: false, detail: `the query failed: ${message}` });
|
|
33
46
|
}
|
|
34
47
|
}
|
|
35
48
|
return out;
|
|
36
49
|
}
|
|
37
|
-
|
|
50
|
+
const firstLine = (sql) => {
|
|
51
|
+
const line = sql.trim().split("\n")[0] ?? "";
|
|
52
|
+
return line.length > 120 ? `${line.slice(0, 117)}...` : line;
|
|
53
|
+
};
|
|
54
|
+
function failedLine(check, o) {
|
|
55
|
+
if (check?.step) {
|
|
56
|
+
const count = /^\d+$/.test(o.detail) ? `${o.detail} ${check.step.detail}` : `its pre-check ${o.detail}`;
|
|
57
|
+
return `step ${String(check.step.index)}, ${firstLine(check.step.sql)}, would fail: ${count} (pre-check ${o.name}: ${check.sql})`;
|
|
58
|
+
}
|
|
59
|
+
return `check ${o.name} failed: it must ${check ? describeExpect(check).replace(/^returns/, "return") : "pass"}, and returned ${o.detail}`;
|
|
60
|
+
}
|
|
61
|
+
/** The checks that failed, one line each; a pre-check's names its statement and the count. */
|
|
38
62
|
export function failedChecks(m, outcomes) {
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
});
|
|
63
|
+
const checks = migrationChecks(m.document);
|
|
64
|
+
return outcomes.filter((o) => !o.passed).map((o) => failedLine(checks.find((c) => c.name === o.name), o));
|
|
45
65
|
}
|
|
46
66
|
/** The refusal when a check of `m` failed, or `undefined`. */
|
|
47
67
|
export function checksRefusal(m, outcomes, when) {
|
|
@@ -49,6 +69,7 @@ export function checksRefusal(m, outcomes, when) {
|
|
|
49
69
|
if (failed.length === 0)
|
|
50
70
|
return undefined;
|
|
51
71
|
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)
|
|
72
|
+
`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).` +
|
|
73
|
+
(failed.some((f) => f.startsWith("step ")) ? ` A statement's pre-check is chant's count of the rows the statement fails on: fix those rows, or change the declaration.` : ""), EXIT_REFUSED);
|
|
53
74
|
}
|
|
54
75
|
//# sourceMappingURL=checks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/versioned/checks.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/versioned/checks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAyD,MAAM,wBAAwB,CAAC;AAI5I,iGAAiG;AACjG,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAmB,EAAW,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;AAEnJ,sFAAsF;AACtF,MAAM,SAAS,GAAG,qEAAqE,CAAC;AAExF,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAuB,EAAE,CAAY;IACnE,MAAM,KAAK,GAAG,EAAE,eAAe,EAAE,CAAC,CAAC,QAAQ,CAAC,eAAe,EAAE,aAAa,EAAE,CAAC,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;IACvG,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChD,IAAI,CAAC;YACH,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QACxE,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAClF,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAClE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,OAAO,mCAAmC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;gBACzH,SAAS;YACX,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,OAAO,EAAE,EAAE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,SAAS,GAAG,CAAC,GAAW,EAAU,EAAE;IACxC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7C,OAAO,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/D,CAAC,CAAC;AAEF,SAAS,UAAU,CAAC,KAAgC,EAAE,CAAe;IACnE,IAAI,KAAK,EAAE,IAAI,EAAE,CAAC;QAChB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,MAAM,EAAE,CAAC;QACxG,OAAO,QAAQ,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,KAAK,eAAe,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,GAAG,GAAG,CAAC;IACpI,CAAC;IACD,OAAO,SAAS,CAAC,CAAC,IAAI,oBAAoB,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,kBAAkB,CAAC,CAAC,MAAM,EAAE,CAAC;AAC7I,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,YAAY,CAAC,CAAY,EAAE,QAAiC;IAC1E,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC3C,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5G,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,aAAa,CAAC,CAAY,EAAE,QAAiC,EAAE,IAAY;IACzF,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IACzC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1C,OAAO,IAAI,UAAU,CACnB,qCAAqC,CAAC,CAAC,EAAE,WAAW,IAAI,gBAAgB,CAAC,CAAC,EAAE,kBAAkB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;QACtI,mIAAmI,CAAC,CAAC,EAAE,0DAA0D;QACjM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,0HAA0H,CAAC,CAAC,CAAC,EAAE,CAAC,EAC/K,YAAY,CACb,CAAC;AACJ,CAAC"}
|
|
@@ -44,8 +44,8 @@ export interface VersionedDriver {
|
|
|
44
44
|
}): Promise<Array<Record<string, unknown>>>;
|
|
45
45
|
/** Sends one statement and writes its `succeeded` row through `run`; throws with the server's message when it fails. */
|
|
46
46
|
runStatement(step: StatementStep, run: StatementRun): Promise<void>;
|
|
47
|
-
/** What an Op step's runner gets from the dialect: the ClickHouse port and endpoint; the Postgres lock timeout, and the connection, timeouts and receipts schema a backfill uses. */
|
|
48
|
-
stepContext(): Pick<StepContext, "port" | "endpoint" | "lockTimeoutMs" | "postgres">;
|
|
47
|
+
/** What an Op step's runner gets from the dialect: the ClickHouse port and endpoint; the Postgres lock timeout, and the connection, timeouts and receipts schema a backfill uses; and on a tenant (#56), which tenant. */
|
|
48
|
+
stepContext(): Pick<StepContext, "port" | "endpoint" | "lockTimeoutMs" | "postgres" | "tenant">;
|
|
49
49
|
/** Closes what the driver opened (a Postgres connection). Safe to call twice. */
|
|
50
50
|
close(): Promise<void>;
|
|
51
51
|
}
|
|
@@ -162,6 +162,16 @@ export interface ExecuteOptions {
|
|
|
162
162
|
* through (`wave`, ../waves/index.ts).
|
|
163
163
|
*/
|
|
164
164
|
runNote?: () => Record<string, unknown> | undefined;
|
|
165
|
+
/**
|
|
166
|
+
* The `after` steps (#64): run under the lock once the last migration's
|
|
167
|
+
* statements succeeded, before its `succeeded` row, whose note records
|
|
168
|
+
* what they return. An error they return is thrown once that row is
|
|
169
|
+
* written: the migrations stay applied.
|
|
170
|
+
*/
|
|
171
|
+
after?: () => Promise<{
|
|
172
|
+
note: Record<string, unknown>;
|
|
173
|
+
error?: Error;
|
|
174
|
+
}>;
|
|
165
175
|
}
|
|
166
176
|
/** How long a run waited for the lock, and who held it. */
|
|
167
177
|
export interface LockWait {
|
package/dist/versioned/engine.js
CHANGED
|
@@ -271,14 +271,16 @@ export async function executeApply(venv, ordered, options) {
|
|
|
271
271
|
const overrides = options.policy ? await options.policy(digest, toApply) : [];
|
|
272
272
|
const recorder = new HistoryRecorder(venv.store, newRunContext(options.approvedDigest, options.processEnv), state.maxSeq);
|
|
273
273
|
const result = { applied: [], skipped: [], lock: lockDoc };
|
|
274
|
-
for (const p of toApply) {
|
|
275
|
-
await applyMigration(venv, p, state.byId.get(p.id), recorder, beat, options, result, overrides);
|
|
274
|
+
for (const [i, p] of toApply.entries()) {
|
|
275
|
+
const after = await applyMigration(venv, p, state.byId.get(p.id), recorder, beat, options, result, overrides, i === toApply.length - 1);
|
|
276
276
|
result.applied.push(p.id);
|
|
277
|
+
if (after)
|
|
278
|
+
throw after;
|
|
277
279
|
}
|
|
278
280
|
return result;
|
|
279
281
|
});
|
|
280
282
|
}
|
|
281
|
-
async function applyMigration(venv, p, rec, recorder, beat, options, result, overrides = []) {
|
|
283
|
+
async function applyMigration(venv, p, rec, recorder, beat, options, result, overrides = [], last = false) {
|
|
282
284
|
const m = p.migration;
|
|
283
285
|
const silences = options.silences?.(m) ?? "[]";
|
|
284
286
|
const base = { migration_id: m.id, parent: m.parent ?? "", checksum: p.checksum, silences };
|
|
@@ -334,8 +336,10 @@ async function applyMigration(venv, p, rec, recorder, beat, options, result, ove
|
|
|
334
336
|
}
|
|
335
337
|
options.log(` statement ${String(i)}: ok (${step.rule} ${step.class}, ${step.name})`);
|
|
336
338
|
}
|
|
337
|
-
|
|
339
|
+
const after = last && options.after ? await options.after() : undefined;
|
|
340
|
+
await recorder.record({ ...base, kind: "migration", statement_index: MIGRATION_ROW, statement_sha: "", status: "succeeded", ...(after ? { note: JSON.stringify(after.note) } : {}) });
|
|
338
341
|
options.log(`${m.id}: applied`);
|
|
342
|
+
return after?.error;
|
|
339
343
|
}
|
|
340
344
|
/** Runs Op step `i` of `p`, recording its start and outcome. */
|
|
341
345
|
async function runOpStep(venv, p, i, recorder, base, options) {
|