@intentius/sql-yodeler 0.3.0 → 0.5.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.
Files changed (220) hide show
  1. package/README.md +2 -2
  2. package/dist/apply-steps/index.d.ts +65 -0
  3. package/dist/apply-steps/index.js +106 -0
  4. package/dist/apply-steps/index.js.map +1 -0
  5. package/dist/approval-mode/index.d.ts +78 -0
  6. package/dist/approval-mode/index.js +105 -0
  7. package/dist/approval-mode/index.js.map +1 -0
  8. package/dist/chant/access.d.ts +84 -0
  9. package/dist/chant/access.js +155 -0
  10. package/dist/chant/access.js.map +1 -0
  11. package/dist/chant/drift.d.ts +17 -1
  12. package/dist/chant/drift.js +55 -2
  13. package/dist/chant/drift.js.map +1 -1
  14. package/dist/chant/plan.d.ts +18 -2
  15. package/dist/chant/plan.js +21 -3
  16. package/dist/chant/plan.js.map +1 -1
  17. package/dist/ci/login.d.ts +78 -0
  18. package/dist/ci/login.js +199 -0
  19. package/dist/ci/login.js.map +1 -0
  20. package/dist/ci/render.d.ts +64 -10
  21. package/dist/ci/render.js +385 -88
  22. package/dist/ci/render.js.map +1 -1
  23. package/dist/cleanup/index.d.ts +92 -0
  24. package/dist/cleanup/index.js +213 -0
  25. package/dist/cleanup/index.js.map +1 -0
  26. package/dist/commands/apply.js +178 -20
  27. package/dist/commands/apply.js.map +1 -1
  28. package/dist/commands/approve.d.ts +13 -0
  29. package/dist/commands/approve.js +206 -24
  30. package/dist/commands/approve.js.map +1 -1
  31. package/dist/commands/ci.js +12 -0
  32. package/dist/commands/ci.js.map +1 -1
  33. package/dist/commands/cleanup.d.ts +13 -0
  34. package/dist/commands/cleanup.js +158 -0
  35. package/dist/commands/cleanup.js.map +1 -0
  36. package/dist/commands/config.d.ts +3 -0
  37. package/dist/commands/config.js +136 -0
  38. package/dist/commands/config.js.map +1 -0
  39. package/dist/commands/drift.js +33 -3
  40. package/dist/commands/drift.js.map +1 -1
  41. package/dist/commands/index.js +6 -0
  42. package/dist/commands/index.js.map +1 -1
  43. package/dist/commands/init.d.ts +11 -0
  44. package/dist/commands/init.js +98 -10
  45. package/dist/commands/init.js.map +1 -1
  46. package/dist/commands/lint.js +67 -10
  47. package/dist/commands/lint.js.map +1 -1
  48. package/dist/commands/new.js +135 -54
  49. package/dist/commands/new.js.map +1 -1
  50. package/dist/commands/plan.js +39 -5
  51. package/dist/commands/plan.js.map +1 -1
  52. package/dist/commands/test.d.ts +3 -0
  53. package/dist/commands/test.js +164 -0
  54. package/dist/commands/test.js.map +1 -0
  55. package/dist/config/define.d.ts +175 -2
  56. package/dist/config/define.js +2 -0
  57. package/dist/config/define.js.map +1 -1
  58. package/dist/config/load.js +78 -10
  59. package/dist/config/load.js.map +1 -1
  60. package/dist/config/settings.d.ts +20 -0
  61. package/dist/config/settings.js +28 -0
  62. package/dist/config/settings.js.map +1 -1
  63. package/dist/credentials.d.ts +119 -0
  64. package/dist/credentials.js +119 -0
  65. package/dist/credentials.js.map +1 -0
  66. package/dist/exit.js +1 -1
  67. package/dist/exit.js.map +1 -1
  68. package/dist/index.d.ts +5 -1
  69. package/dist/index.js +2 -0
  70. package/dist/index.js.map +1 -1
  71. package/dist/init/adopt.js +1 -1
  72. package/dist/init/adopt.js.map +1 -1
  73. package/dist/init/clickhouse-access.d.ts +64 -0
  74. package/dist/init/clickhouse-access.js +211 -0
  75. package/dist/init/clickhouse-access.js.map +1 -0
  76. package/dist/init/index.d.ts +2 -0
  77. package/dist/init/index.js +2 -0
  78. package/dist/init/index.js.map +1 -1
  79. package/dist/init/live.d.ts +4 -0
  80. package/dist/init/live.js +16 -0
  81. package/dist/init/live.js.map +1 -1
  82. package/dist/init/second.d.ts +70 -0
  83. package/dist/init/second.js +149 -0
  84. package/dist/init/second.js.map +1 -0
  85. package/dist/lint/access.d.ts +18 -0
  86. package/dist/lint/access.js +34 -0
  87. package/dist/lint/access.js.map +1 -0
  88. package/dist/lint/annotations.d.ts +49 -0
  89. package/dist/lint/annotations.js +82 -0
  90. package/dist/lint/annotations.js.map +1 -0
  91. package/dist/lint/check.js +25 -7
  92. package/dist/lint/check.js.map +1 -1
  93. package/dist/lint/config.d.ts +7 -1
  94. package/dist/lint/config.js +11 -5
  95. package/dist/lint/config.js.map +1 -1
  96. package/dist/lint/index.d.ts +4 -0
  97. package/dist/lint/index.js +4 -0
  98. package/dist/lint/index.js.map +1 -1
  99. package/dist/lint/naming.d.ts +62 -0
  100. package/dist/lint/naming.js +138 -0
  101. package/dist/lint/naming.js.map +1 -0
  102. package/dist/lint/rules.d.ts +37 -4
  103. package/dist/lint/rules.js +16 -4
  104. package/dist/lint/rules.js.map +1 -1
  105. package/dist/lint/schema-checks.d.ts +63 -0
  106. package/dist/lint/schema-checks.js +128 -0
  107. package/dist/lint/schema-checks.js.map +1 -0
  108. package/dist/lint/statements.d.ts +22 -1
  109. package/dist/lint/statements.js +40 -1
  110. package/dist/lint/statements.js.map +1 -1
  111. package/dist/mcp/tools.d.ts +1 -1
  112. package/dist/mcp/tools.js +1 -1
  113. package/dist/mcp/tools.js.map +1 -1
  114. package/dist/migrations/checkpoint.js +0 -2
  115. package/dist/migrations/checkpoint.js.map +1 -1
  116. package/dist/migrations/checks.d.ts +19 -1
  117. package/dist/migrations/checks.js +15 -0
  118. package/dist/migrations/checks.js.map +1 -1
  119. package/dist/migrations/create.d.ts +13 -4
  120. package/dist/migrations/create.js +24 -9
  121. package/dist/migrations/create.js.map +1 -1
  122. package/dist/migrations/routines.d.ts +5 -15
  123. package/dist/migrations/routines.js +5 -30
  124. package/dist/migrations/routines.js.map +1 -1
  125. package/dist/pr/model.d.ts +4 -1
  126. package/dist/pr/model.js +13 -5
  127. package/dist/pr/model.js.map +1 -1
  128. package/dist/pr/render.d.ts +4 -1
  129. package/dist/pr/render.js +22 -8
  130. package/dist/pr/render.js.map +1 -1
  131. package/dist/pr/tenants.d.ts +37 -0
  132. package/dist/pr/tenants.js +138 -0
  133. package/dist/pr/tenants.js.map +1 -0
  134. package/dist/probe.d.ts +27 -0
  135. package/dist/probe.js +106 -0
  136. package/dist/probe.js.map +1 -0
  137. package/dist/project.d.ts +16 -0
  138. package/dist/project.js +29 -1
  139. package/dist/project.js.map +1 -1
  140. package/dist/replay/clickhouse.d.ts +31 -0
  141. package/dist/replay/clickhouse.js +103 -6
  142. package/dist/replay/clickhouse.js.map +1 -1
  143. package/dist/replay/replay.d.ts +5 -2
  144. package/dist/replay/replay.js.map +1 -1
  145. package/dist/report/audit.d.ts +1 -1
  146. package/dist/report/audit.js +20 -3
  147. package/dist/report/audit.js.map +1 -1
  148. package/dist/schemas.d.ts +3 -1
  149. package/dist/schemas.js +2 -0
  150. package/dist/schemas.js.map +1 -1
  151. package/dist/sources/ddl.d.ts +15 -20
  152. package/dist/sources/ddl.js +34 -328
  153. package/dist/sources/ddl.js.map +1 -1
  154. package/dist/sources/index.js +2 -2
  155. package/dist/sources/index.js.map +1 -1
  156. package/dist/steps/backfill.d.ts +40 -5
  157. package/dist/steps/backfill.js +128 -8
  158. package/dist/steps/backfill.js.map +1 -1
  159. package/dist/steps/index.d.ts +19 -1
  160. package/dist/steps/index.js +34 -7
  161. package/dist/steps/index.js.map +1 -1
  162. package/dist/steps/kinds.d.ts +42 -4
  163. package/dist/steps/kinds.js +88 -14
  164. package/dist/steps/kinds.js.map +1 -1
  165. package/dist/steps/postgres-migration.d.ts +3 -0
  166. package/dist/steps/postgres-migration.js +7 -3
  167. package/dist/steps/postgres-migration.js.map +1 -1
  168. package/dist/steps/rebuild.d.ts +15 -6
  169. package/dist/steps/rebuild.js +12 -9
  170. package/dist/steps/rebuild.js.map +1 -1
  171. package/dist/tenants/index.d.ts +2 -1
  172. package/dist/tenants/index.js +3 -4
  173. package/dist/tenants/index.js.map +1 -1
  174. package/dist/tenants/wave.d.ts +128 -0
  175. package/dist/tenants/wave.js +254 -0
  176. package/dist/tenants/wave.js.map +1 -0
  177. package/dist/testing/define.d.ts +62 -0
  178. package/dist/testing/define.js +32 -0
  179. package/dist/testing/define.js.map +1 -0
  180. package/dist/testing/files.d.ts +5 -0
  181. package/dist/testing/files.js +18 -0
  182. package/dist/testing/files.js.map +1 -0
  183. package/dist/testing/report.d.ts +14 -0
  184. package/dist/testing/report.js +83 -0
  185. package/dist/testing/report.js.map +1 -0
  186. package/dist/testing/run.d.ts +104 -0
  187. package/dist/testing/run.js +328 -0
  188. package/dist/testing/run.js.map +1 -0
  189. package/dist/versioned/approval.d.ts +6 -0
  190. package/dist/versioned/approval.js +11 -5
  191. package/dist/versioned/approval.js.map +1 -1
  192. package/dist/versioned/checks.d.ts +8 -2
  193. package/dist/versioned/checks.js +34 -13
  194. package/dist/versioned/checks.js.map +1 -1
  195. package/dist/versioned/driver.d.ts +2 -2
  196. package/dist/versioned/engine.d.ts +10 -0
  197. package/dist/versioned/engine.js +8 -4
  198. package/dist/versioned/engine.js.map +1 -1
  199. package/dist/versioned/explain.d.ts +2 -0
  200. package/dist/versioned/explain.js +3 -1
  201. package/dist/versioned/explain.js.map +1 -1
  202. package/dist/versioned/op.d.ts +4 -0
  203. package/dist/versioned/op.js +5 -2
  204. package/dist/versioned/op.js.map +1 -1
  205. package/dist/versioned/state.js +1 -1
  206. package/dist/versioned/state.js.map +1 -1
  207. package/dist/waves/index.d.ts +58 -13
  208. package/dist/waves/index.js +109 -24
  209. package/dist/waves/index.js.map +1 -1
  210. package/dist/waves/record.d.ts +10 -0
  211. package/dist/waves/record.js.map +1 -1
  212. package/package.json +8 -7
  213. package/schemas/apply.schema.json +1 -1
  214. package/schemas/cleanup.schema.json +40 -0
  215. package/schemas/init.schema.json +51 -22
  216. package/schemas/lint-rules.schema.json +4 -0
  217. package/schemas/new.schema.json +41 -3
  218. package/schemas/plan.schema.json +39 -0
  219. package/schemas/report.schema.json +1 -1
  220. 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
- if (found.resolution)
41
- return { resolvedBy: found.resolution.resolvedBy, timestamp: found.resolution.timestamp };
42
- const other = found.mismatched
43
- ? ` (the newest approval of that gate is for ${found.mismatched.planDigest ?? "no plan"}, by ${found.mismatched.resolvedBy} at ${found.mismatched.timestamp})`
44
- : "";
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,IAAI,KAAK,CAAC,UAAU;QAAE,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,UAAU,EAAE,SAAS,EAAE,KAAK,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;IAChH,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU;QAC5B,CAAC,CAAC,6CAA6C,KAAK,CAAC,UAAU,CAAC,UAAU,IAAI,SAAS,QAAQ,KAAK,CAAC,UAAU,CAAC,UAAU,OAAO,KAAK,CAAC,UAAU,CAAC,SAAS,GAAG;QAC9J,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,IAAI,UAAU,CAClB,qDAAqD,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC,IAAI,aAAa,MAAM,GAAG,KAAK,yBAAyB;QAC3H,+FAA+F,EACjG,YAAY,CACb,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW;IACzC,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;IACvE,OAAO,iBAAiB,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;AACpC,CAAC"}
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;
@@ -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.checks?.length ?? 0) > 0 && (p.partWay?.done.length ?? 0) === 0;
23
- /** Runs each of `m`'s checks; a query the server refuses is a failed check. */
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.checks ?? []) {
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
- out.push({ name: check.name, passed: false, detail: `the query failed: ${(e instanceof Error ? e.message : String(e)).split("\n")[0] ?? ""}` });
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
- /** The checks that failed, one line each. */
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
- return outcomes
40
- .filter((o) => !o.passed)
41
- .map((o) => {
42
- const check = m.document.checks?.find((c) => c.name === o.name);
43
- return `check ${o.name} failed: it must ${check ? describeExpect(check).replace(/^returns/, "return") : "pass"}, and returned ${o.detail}`;
44
- });
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).`, EXIT_REFUSED);
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":"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"}
1
+ {"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/versioned/checks.ts"],"names":[],"mappings":"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"}
@@ -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 {
@@ -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
- await recorder.record({ ...base, kind: "migration", statement_index: MIGRATION_ROW, statement_sha: "", status: "succeeded" });
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) {