okmodel 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 +182 -22
- package/dist/contracts/catalog/document.d.ts +6 -2
- package/dist/dialects/pg/ext/citext.js +1 -1
- package/dist/dialects/pg/ext/index.js +1 -1
- package/dist/dialects/pg/ext/pg-trgm.js +1 -1
- package/dist/dialects/pg/index.js +3 -3
- package/dist/dialects/pg/role/sql.d.ts +11 -4
- package/dist/dialects/pg/table.d.ts +17 -1
- package/dist/dialects/pg/view/index.js +1 -1
- package/dist/dialects/pg/view/scratch.d.ts +3 -2
- package/dist/runtime/catalog-cache.d.ts +17 -0
- package/dist/runtime/compat-words.d.ts +23 -0
- package/dist/runtime/drift-detail.d.ts +20 -4
- package/dist/runtime/drift.d.ts +4 -2
- package/dist/runtime/pg/bun.js +2 -2
- package/dist/runtime/pg/pg.js +2 -2
- package/dist/runtime/pg/pglite.js +2 -2
- package/dist/runtime/pg/postgresjs.js +2 -2
- package/dist/runtime/traits/index.js +5 -4
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/validate/index.js +2 -2
- package/dist/shared/2321avmn.js +418 -0
- package/dist/shared/{x6wysh83.js → 3p4b1fg0.js} +3 -5
- package/dist/shared/{wekze34d.js → 41kv2xdm.js} +74 -2
- package/dist/shared/{fs0ex00p.js → 4ef7mmqf.js} +2 -2
- package/dist/shared/5mtf6cj3.js +94 -0
- package/dist/shared/{999ade2q.js → 5vdm4sgp.js} +1 -1
- package/dist/shared/{qybh48nd.js → 6693j9rs.js} +1 -1
- package/dist/shared/{bqha5cg6.js → 6p5k20sm.js} +2 -2
- package/dist/shared/7nk68fj8.js +751 -0
- package/dist/shared/{ne02ng5c.js → 84ptn35q.js} +7 -4
- package/dist/shared/{t616ynfn.js → 8jb4ymh2.js} +59 -12
- package/dist/shared/{th14ygv2.js → 8ktwd3rh.js} +29 -1
- package/dist/shared/9yn6a2vd.js +11 -0
- package/dist/shared/ady1ms74.js +72 -0
- package/dist/shared/{scwxrpx2.js → c5g81ngb.js} +82 -15
- package/dist/shared/{6bazvqwm.js → fb00y8fp.js} +2 -2
- package/dist/shared/{jkc6ws1p.js → g7ez901n.js} +83 -2
- package/dist/shared/{kgnc8grt.js → gdh9mrtp.js} +1 -1
- package/dist/shared/hqjsxnz7.js +13 -0
- package/dist/shared/htnaj77v.js +17 -0
- package/dist/shared/{mt5wswjz.js → jgxzjzcz.js} +1 -1
- package/dist/shared/{6w5j844a.js → jsd07prz.js} +2 -2
- package/dist/shared/{m78s7898.js → kafaa5zv.js} +1 -1
- package/dist/shared/kj680nxv.js +19 -0
- package/dist/shared/{k34e0xf2.js → kkzwp3d2.js} +8 -8
- package/dist/shared/{2ssqrv66.js → n5y9zyj1.js} +1029 -174
- package/dist/shared/{ty3v3hfk.js → nxb42q7h.js} +2 -2
- package/dist/shared/{5nmdenb6.js → q6zsyh31.js} +1003 -40
- package/dist/shared/{fptpmvxp.js → q795afwa.js} +26 -21
- package/dist/shared/{3ekj9x92.js → qn0srwwq.js} +1 -1
- package/dist/shared/s4mb6a4b.js +545 -0
- package/dist/shared/{01yr20s5.js → sf8m6tfy.js} +2 -2
- package/dist/shared/{bryqk9k9.js → sr5ms7b3.js} +1 -1
- package/dist/shared/{meck53jv.js → tny2t3sm.js} +6 -5
- package/dist/shared/{p24x2334.js → v7tpnhnz.js} +1 -1
- package/dist/shared/{xw1d69vd.js → wqem4jed.js} +6 -6
- package/dist/shared/{49ypnfwt.js → xghkt7a9.js} +50 -28
- package/dist/shared/{7fvtrkwr.js → ycatkxmq.js} +1 -1
- package/dist/shared/ygem90rn.js +900 -0
- package/dist/shared/{1tfdfvhh.js → yjw02enp.js} +6 -4
- package/dist/shared/{ydaz1sv3.js → zg24x0hm.js} +1 -1
- package/dist/tooling/cli.js +46 -17
- package/dist/tooling/migrate/apply.d.ts +59 -5
- package/dist/tooling/migrate/backfill.d.ts +136 -0
- package/dist/tooling/migrate/check.d.ts +65 -0
- package/dist/tooling/migrate/classify.d.ts +108 -0
- package/dist/tooling/migrate/commands.d.ts +2 -2
- package/dist/tooling/migrate/config.d.ts +22 -0
- package/dist/tooling/migrate/doctor.d.ts +3 -0
- package/dist/tooling/migrate/drift.d.ts +18 -2
- package/dist/tooling/migrate/estimate.d.ts +50 -0
- package/dist/tooling/migrate/extensions.d.ts +2 -0
- package/dist/tooling/migrate/files.d.ts +12 -0
- package/dist/tooling/migrate/index.d.ts +3 -2
- package/dist/tooling/migrate/index.js +67 -1
- package/dist/tooling/migrate/lint-rules.d.ts +51 -0
- package/dist/tooling/migrate/lint.d.ts +71 -0
- package/dist/tooling/migrate/managed.d.ts +1 -1
- package/dist/tooling/migrate/plan.d.ts +64 -9
- package/dist/tooling/migrate/project.d.ts +42 -7
- package/dist/tooling/migrate/provision.d.ts +18 -0
- package/dist/tooling/migrate/reference.d.ts +40 -0
- package/dist/tooling/migrate/runner.d.ts +108 -0
- package/dist/tooling/migrate/safe.d.ts +77 -0
- package/dist/tooling/migrate/seed.d.ts +20 -0
- package/dist/tooling/migrate/snapshot.d.ts +78 -0
- package/dist/tooling/migrate/status.d.ts +25 -6
- package/dist/tooling/testing/factories.d.ts +117 -0
- package/dist/tooling/testing/facts.d.ts +72 -0
- package/dist/tooling/testing/index.d.ts +89 -0
- package/dist/tooling/testing/index.js +8 -0
- package/dist/tooling/testing/isolation.d.ts +30 -0
- package/dist/tooling/testing/random.d.ts +40 -0
- package/dist/tooling/testing/record.d.ts +38 -0
- package/package.json +6 -2
- package/dist/shared/dbjej1tw.js +0 -456
- package/dist/shared/g4f5kcx9.js +0 -37
- package/dist/shared/qbxzvwwc.js +0 -88
- package/dist/shared/zwmqxb0g.js +0 -79
- package/dist/shared/{xva8v7qk.js → awrsqrn7.js} +3 -3
- package/dist/shared/{75xkjv0t.js → fa9vt245.js} +0 -0
- package/dist/shared/{m9tv7xn7.js → radd3z4g.js} +3 -3
- /package/dist/shared/{0jc3ffzf.js → zvbjc949.js} +0 -0
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
import {
|
|
2
|
+
serializeCatalog2,
|
|
3
|
+
catalogHash2,
|
|
4
|
+
parseCatalog2
|
|
5
|
+
} from "./8jb4ymh2.js";
|
|
6
|
+
import {
|
|
7
|
+
roleExists
|
|
8
|
+
} from "./hcmdtwgh.js";
|
|
9
|
+
import {
|
|
10
|
+
assertTargetPolicy
|
|
11
|
+
} from "./q6zsyh31.js";
|
|
12
|
+
import {
|
|
13
|
+
canonicalTypeName,
|
|
14
|
+
createdRoleName,
|
|
15
|
+
DEFAULT_BACKFILL_PAUSE_MS,
|
|
16
|
+
DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS,
|
|
17
|
+
boundaryQuery,
|
|
18
|
+
checkedCast,
|
|
19
|
+
omitManagedObjects,
|
|
20
|
+
planMigration,
|
|
21
|
+
parsePlan
|
|
22
|
+
} from "./n5y9zyj1.js";
|
|
23
|
+
import {
|
|
24
|
+
quoteIdent,
|
|
25
|
+
qualify
|
|
26
|
+
} from "./41kv2xdm.js";
|
|
27
|
+
import {
|
|
28
|
+
alignExtensionVersions
|
|
29
|
+
} from "./8ktwd3rh.js";
|
|
30
|
+
import {
|
|
31
|
+
catalog,
|
|
32
|
+
isDomain
|
|
33
|
+
} from "./g7ez901n.js";
|
|
34
|
+
import {
|
|
35
|
+
snakeCase
|
|
36
|
+
} from "./jsd07prz.js";
|
|
37
|
+
import {
|
|
38
|
+
quoteLiteral
|
|
39
|
+
} from "./94ez8kzh.js";
|
|
40
|
+
import {
|
|
41
|
+
identityKey2,
|
|
42
|
+
identityLabel2
|
|
43
|
+
} from "./t4tbsy9h.js";
|
|
44
|
+
import {
|
|
45
|
+
OkmError2
|
|
46
|
+
} from "./52szzakm.js";
|
|
47
|
+
|
|
48
|
+
// src/tooling/migrate/runner.ts
|
|
49
|
+
var BACKOFF_MS = 50;
|
|
50
|
+
var KEY_TYPES = `select a.attname, format_type(a.atttypid, a.atttypmod)
|
|
51
|
+
from pg_attribute a
|
|
52
|
+
where a.attrelid = $1::regclass
|
|
53
|
+
and a.attnum > 0
|
|
54
|
+
and not a.attisdropped`;
|
|
55
|
+
async function runTarget(connection, run, session) {
|
|
56
|
+
const applied = [];
|
|
57
|
+
for (let index = 0;index < run.units.length; index += 1) {
|
|
58
|
+
const unit = run.units[index];
|
|
59
|
+
if (unit === undefined)
|
|
60
|
+
continue;
|
|
61
|
+
assertUnitPolicy(run.policy, unit);
|
|
62
|
+
await runUnit(connection, unit, run, session);
|
|
63
|
+
const next = run.units[index + 1];
|
|
64
|
+
if (next === undefined || next.migrationId !== unit.migrationId)
|
|
65
|
+
applied.push(unit.migrationId);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
target: run.policy.target,
|
|
69
|
+
applied,
|
|
70
|
+
...session.setRole !== undefined ? { setRole: session.setRole } : {}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async function assumeRole(connection, role, session) {
|
|
74
|
+
if (session.setRole === role)
|
|
75
|
+
return;
|
|
76
|
+
await connection.execute(`set role ${quoteIdent(role)}`);
|
|
77
|
+
session.setRole = role;
|
|
78
|
+
}
|
|
79
|
+
async function grantSchema(connection, schema, role) {
|
|
80
|
+
await connection.execute(`grant usage, create on schema ${quoteIdent(schema)} to ${quoteIdent(role)}`);
|
|
81
|
+
}
|
|
82
|
+
async function runUnit(connection, unit, run, session) {
|
|
83
|
+
const only = unit.steps.length === 1 ? unit.steps[0] : undefined;
|
|
84
|
+
if (only?.step.backfill !== undefined) {
|
|
85
|
+
await runBackfill(connection, unit, only, only.step.backfill, run);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
for (let attempt = 0;attempt <= run.retries; attempt += 1) {
|
|
89
|
+
let began = false;
|
|
90
|
+
let current = unit.steps[0]?.index ?? 0;
|
|
91
|
+
try {
|
|
92
|
+
if (unit.transactional) {
|
|
93
|
+
await connection.execute("begin");
|
|
94
|
+
began = true;
|
|
95
|
+
}
|
|
96
|
+
for (const item of unit.steps) {
|
|
97
|
+
current = item.index;
|
|
98
|
+
await rebuildInvalidIndex(connection, item.step.sql);
|
|
99
|
+
const created = createdRoleName(item.step.sql);
|
|
100
|
+
const existed = created !== undefined && await roleExists(connection, created);
|
|
101
|
+
if (created === undefined || !existed)
|
|
102
|
+
await connection.execute(item.step.sql);
|
|
103
|
+
if (created !== undefined && created === run.migrationRole) {
|
|
104
|
+
if (!existed) {
|
|
105
|
+
await grantSchema(connection, run.schema ?? "public", created);
|
|
106
|
+
await connection.execute(`grant select, insert, update on okm_meta, okm_history, okm_backfill to ${quoteIdent(created)}`);
|
|
107
|
+
}
|
|
108
|
+
await assumeRole(connection, created, session);
|
|
109
|
+
}
|
|
110
|
+
if (!unit.transactional) {
|
|
111
|
+
await connection.execute("begin");
|
|
112
|
+
began = true;
|
|
113
|
+
await writeStep(connection, unit, item);
|
|
114
|
+
if (unit.finishes)
|
|
115
|
+
await writeMeta(connection, unit);
|
|
116
|
+
await connection.execute("commit");
|
|
117
|
+
began = false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (unit.transactional) {
|
|
121
|
+
for (const item of unit.steps)
|
|
122
|
+
await writeStep(connection, unit, item);
|
|
123
|
+
if (unit.finishes)
|
|
124
|
+
await writeMeta(connection, unit);
|
|
125
|
+
await connection.execute("commit");
|
|
126
|
+
began = false;
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (began)
|
|
131
|
+
await connection.execute("rollback").catch(() => {
|
|
132
|
+
return;
|
|
133
|
+
});
|
|
134
|
+
if (!isLockTimeout(error) || attempt === run.retries)
|
|
135
|
+
throw failStep(unit, current, error);
|
|
136
|
+
await delay(BACKOFF_MS * 2 ** attempt);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function runBackfill(connection, unit, item, spec, run) {
|
|
141
|
+
const columns = await keyColumns(connection, spec);
|
|
142
|
+
const query = boundaryQuery(spec.table, columns);
|
|
143
|
+
const stored = await readCheckpoint(connection, unit.migrationId, item.index);
|
|
144
|
+
if (stored?.state === "done") {
|
|
145
|
+
await connection.execute("begin");
|
|
146
|
+
await connection.execute(`insert into okm_history (migration_id, step_index, class, catalog_hash) values ($1, $2, $3, $4)
|
|
147
|
+
on conflict (migration_id, step_index) do nothing`, [unit.migrationId, String(item.index), item.step.class, unit.catalogHash]);
|
|
148
|
+
if (unit.finishes)
|
|
149
|
+
await writeMeta(connection, unit);
|
|
150
|
+
await connection.execute("commit");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
let last = stored?.lastKey ?? null;
|
|
154
|
+
let rows = stored?.rows ?? 0;
|
|
155
|
+
let batches = stored?.batches ?? 0;
|
|
156
|
+
const pause = run.backfillPauseMs ?? DEFAULT_BACKFILL_PAUSE_MS;
|
|
157
|
+
const statementMs = run.backfillStatementTimeoutMs ?? run.statementTimeoutMs ?? DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS;
|
|
158
|
+
for (;; ) {
|
|
159
|
+
assertTargetPolicy({ name: run.policy.target, protected: run.policy.protected }, "backfill", run.policy.allowProtected === true);
|
|
160
|
+
const committed = await oneBatch(connection, unit, item, spec, run, query, {
|
|
161
|
+
last,
|
|
162
|
+
rows,
|
|
163
|
+
batches,
|
|
164
|
+
statementMs
|
|
165
|
+
});
|
|
166
|
+
rows = committed.rows;
|
|
167
|
+
batches = committed.batches;
|
|
168
|
+
const note = {
|
|
169
|
+
migrationId: unit.migrationId,
|
|
170
|
+
stepIndex: item.index,
|
|
171
|
+
batch: batches,
|
|
172
|
+
rows,
|
|
173
|
+
lastKey: committed.lastKey
|
|
174
|
+
};
|
|
175
|
+
run.onProgress?.(`backfill ${unit.migrationId} step ${String(item.index)} batch ${String(batches)} rows ${String(rows)} key ${committed.lastKey ?? "-"}`);
|
|
176
|
+
await run.afterBatch?.(note);
|
|
177
|
+
if (committed.final)
|
|
178
|
+
return;
|
|
179
|
+
last = committed.lastKey;
|
|
180
|
+
if (pause > 0)
|
|
181
|
+
await delay(pause);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function oneBatch(connection, unit, item, spec, run, query, state) {
|
|
185
|
+
for (let attempt = 0;attempt <= run.retries; attempt += 1) {
|
|
186
|
+
let began = false;
|
|
187
|
+
try {
|
|
188
|
+
await connection.execute("begin");
|
|
189
|
+
began = true;
|
|
190
|
+
await connection.execute("select set_config('lock_timeout', $1, true)", [
|
|
191
|
+
String(run.lockTimeoutMs)
|
|
192
|
+
]);
|
|
193
|
+
await connection.execute("select set_config('statement_timeout', $1, true)", [
|
|
194
|
+
String(state.statementMs)
|
|
195
|
+
]);
|
|
196
|
+
const boundary = await connection.execute(query, [state.last, String(spec.batch - 1)]);
|
|
197
|
+
const upper = boundary.rows[0]?.[0] ?? null;
|
|
198
|
+
const final = upper === null;
|
|
199
|
+
const updated = await connection.execute(item.step.sql, [state.last, upper]);
|
|
200
|
+
const rows = state.rows + updated.count;
|
|
201
|
+
const batches = state.batches + 1;
|
|
202
|
+
const lastKey = upper ?? state.last;
|
|
203
|
+
await writeCheckpoint(connection, unit.migrationId, item.index, {
|
|
204
|
+
lastKey,
|
|
205
|
+
rows,
|
|
206
|
+
batches,
|
|
207
|
+
state: final ? "done" : "running"
|
|
208
|
+
});
|
|
209
|
+
if (final) {
|
|
210
|
+
await writeStep(connection, unit, item);
|
|
211
|
+
if (unit.finishes)
|
|
212
|
+
await writeMeta(connection, unit);
|
|
213
|
+
}
|
|
214
|
+
await connection.execute("commit");
|
|
215
|
+
return { rows, batches, lastKey, final };
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (began)
|
|
218
|
+
await connection.execute("rollback").catch(() => {
|
|
219
|
+
return;
|
|
220
|
+
});
|
|
221
|
+
if (!isLockTimeout(error) || attempt === run.retries)
|
|
222
|
+
throw failStep(unit, item.index, error);
|
|
223
|
+
await delay(BACKOFF_MS * 2 ** attempt);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
throw failStep(unit, item.index, new Error("The batch did not commit."));
|
|
227
|
+
}
|
|
228
|
+
async function keyColumns(connection, spec) {
|
|
229
|
+
const found = await connection.execute(KEY_TYPES, [spec.table]);
|
|
230
|
+
const types = new Map;
|
|
231
|
+
for (const row of found.rows) {
|
|
232
|
+
const name = row[0];
|
|
233
|
+
const dataType = row[1];
|
|
234
|
+
if (typeof name === "string" && typeof dataType === "string")
|
|
235
|
+
types.set(name, dataType);
|
|
236
|
+
}
|
|
237
|
+
return spec.key.map((quoted) => {
|
|
238
|
+
const name = unquote(quoted);
|
|
239
|
+
const dataType = types.get(name);
|
|
240
|
+
if (dataType === undefined) {
|
|
241
|
+
throw new OkmError2("OKM1546", `Backfill key ${name} is not a column of ${spec.table}.`, {
|
|
242
|
+
fix: { summary: "Name a primary key column that exists on the table." }
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return { quoted, dataType: checkedCast(dataType) };
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
async function readCheckpoint(connection, migrationId, stepIndex) {
|
|
249
|
+
const result = await connection.execute(`select last_key, rows_touched::text, batches::text, state
|
|
250
|
+
from okm_backfill
|
|
251
|
+
where migration_id = $1 and step_index = $2`, [migrationId, String(stepIndex)]);
|
|
252
|
+
const row = result.rows[0];
|
|
253
|
+
if (row === undefined)
|
|
254
|
+
return;
|
|
255
|
+
return {
|
|
256
|
+
lastKey: row[0] ?? null,
|
|
257
|
+
rows: Number(row[1] ?? "0"),
|
|
258
|
+
batches: Number(row[2] ?? "0"),
|
|
259
|
+
state: row[3] ?? "running"
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
async function writeCheckpoint(connection, migrationId, stepIndex, checkpoint) {
|
|
263
|
+
await connection.execute(`insert into okm_backfill (migration_id, step_index, last_key, rows_touched, batches, state)
|
|
264
|
+
values ($1, $2, $3, $4, $5, $6)
|
|
265
|
+
on conflict (migration_id, step_index) do update set
|
|
266
|
+
last_key = excluded.last_key,
|
|
267
|
+
rows_touched = excluded.rows_touched,
|
|
268
|
+
batches = excluded.batches,
|
|
269
|
+
state = excluded.state,
|
|
270
|
+
updated_at = now()`, [
|
|
271
|
+
migrationId,
|
|
272
|
+
String(stepIndex),
|
|
273
|
+
checkpoint.lastKey,
|
|
274
|
+
String(checkpoint.rows),
|
|
275
|
+
String(checkpoint.batches),
|
|
276
|
+
checkpoint.state
|
|
277
|
+
]);
|
|
278
|
+
}
|
|
279
|
+
async function writeStep(connection, unit, item) {
|
|
280
|
+
await connection.execute("insert into okm_history (migration_id, step_index, class, catalog_hash) values ($1, $2, $3, $4)", [unit.migrationId, String(item.index), item.step.class, unit.catalogHash]);
|
|
281
|
+
}
|
|
282
|
+
async function writeMeta(connection, unit) {
|
|
283
|
+
await connection.execute(`insert into okm_meta (id, catalog_hash, migration_id) values ('head', $1, $2)
|
|
284
|
+
on conflict (id) do update set catalog_hash = excluded.catalog_hash, migration_id = excluded.migration_id`, [unit.catalogHash, unit.migrationId]);
|
|
285
|
+
}
|
|
286
|
+
async function rebuildInvalidIndex(connection, sql) {
|
|
287
|
+
const name = concurrentIndexName(sql);
|
|
288
|
+
if (name === undefined)
|
|
289
|
+
return;
|
|
290
|
+
const found = await connection.execute(`select i.indisvalid
|
|
291
|
+
from pg_class c
|
|
292
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
293
|
+
join pg_index i on i.indexrelid = c.oid
|
|
294
|
+
where c.relname = $1 and n.nspname = current_schema()`, [name]);
|
|
295
|
+
const valid = found.rows[0]?.[0];
|
|
296
|
+
if (valid === undefined || wireTrue(valid))
|
|
297
|
+
return;
|
|
298
|
+
await connection.execute(`drop index concurrently if exists ${quoteIdent(name)}`);
|
|
299
|
+
}
|
|
300
|
+
function concurrentIndexName(sql) {
|
|
301
|
+
const match = /^create\s+(?:unique\s+)?index\s+concurrently\s+(?:if\s+not\s+exists\s+)?(?:"([^"]+)"|([A-Za-z_][\w$]*))/i.exec(sql.trim());
|
|
302
|
+
return match?.[1] ?? match?.[2];
|
|
303
|
+
}
|
|
304
|
+
function failStep(unit, index, error) {
|
|
305
|
+
if (error instanceof OkmError2)
|
|
306
|
+
return error;
|
|
307
|
+
const message = error instanceof Error ? error.message : "The step failed.";
|
|
308
|
+
return new OkmError2("invalid", `Migration ${unit.migrationId} failed at step ${String(index)}: ${message}`, {
|
|
309
|
+
cause: error,
|
|
310
|
+
fix: { summary: "Fix the step and run apply again. Finished steps are not repeated." }
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
function isLockTimeout(error) {
|
|
314
|
+
if (typeof error !== "object" || error === null)
|
|
315
|
+
return false;
|
|
316
|
+
if ("sqlstate" in error && error.sqlstate === "55P03")
|
|
317
|
+
return true;
|
|
318
|
+
const message = error instanceof Error ? error.message : "";
|
|
319
|
+
return message.includes("lock timeout");
|
|
320
|
+
}
|
|
321
|
+
function wireTrue(value) {
|
|
322
|
+
return value === "t" || value === "true";
|
|
323
|
+
}
|
|
324
|
+
function unquote(token) {
|
|
325
|
+
if (token.startsWith('"') && token.endsWith('"')) {
|
|
326
|
+
return token.slice(1, -1).replaceAll('""', '"');
|
|
327
|
+
}
|
|
328
|
+
return token;
|
|
329
|
+
}
|
|
330
|
+
function statementOutsideTransaction(sql) {
|
|
331
|
+
const text = sql.trim().toLowerCase();
|
|
332
|
+
if (text.startsWith("vacuum"))
|
|
333
|
+
return true;
|
|
334
|
+
if (/^alter\s+type\b/.test(text) && /\badd\s+value\b/.test(text))
|
|
335
|
+
return true;
|
|
336
|
+
if (/^create\s+(?:unique\s+)?index\s+concurrently\b/.test(text))
|
|
337
|
+
return true;
|
|
338
|
+
if (/^drop\s+index\s+concurrently\b/.test(text))
|
|
339
|
+
return true;
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
function assertUnitPolicy(policy, unit) {
|
|
343
|
+
let operation = "expand";
|
|
344
|
+
for (const item of unit.steps) {
|
|
345
|
+
if (item.step.action === "backfill") {
|
|
346
|
+
operation = "backfill";
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
if (item.step.class === "contract" || item.step.class === "unclassified") {
|
|
350
|
+
operation = item.step.class;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
assertTargetPolicy({ name: policy.target, protected: policy.protected }, operation, policy.allowProtected === true);
|
|
354
|
+
}
|
|
355
|
+
function delay(ms) {
|
|
356
|
+
return new Promise((resolve) => {
|
|
357
|
+
setTimeout(resolve, ms);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/tooling/migrate/files.ts
|
|
362
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
363
|
+
import { join } from "node:path";
|
|
364
|
+
function loadMigrations(directory) {
|
|
365
|
+
if (!existsSync(directory))
|
|
366
|
+
return [];
|
|
367
|
+
const names = readdirSync(directory).filter((file) => file.endsWith(".sql")).sort();
|
|
368
|
+
const migrations = [];
|
|
369
|
+
for (const file of names) {
|
|
370
|
+
const id = file.slice(0, -".sql".length);
|
|
371
|
+
const catalogPath = join(directory, `${id}.catalog.json`);
|
|
372
|
+
if (!existsSync(catalogPath)) {
|
|
373
|
+
throw new OkmError2("invalid", `Migration ${id} is missing its catalog snapshot.`, {
|
|
374
|
+
fix: {
|
|
375
|
+
summary: "Generate the migration again so the SQL and the catalog are written together."
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
const plan = parsePlan(readFileSync(join(directory, file), "utf8"));
|
|
380
|
+
const hash = catalogHash2(parseCatalog2(readFileSync(catalogPath, "utf8")));
|
|
381
|
+
migrations.push({ id, catalogHash: hash, steps: plan.steps });
|
|
382
|
+
}
|
|
383
|
+
return migrations;
|
|
384
|
+
}
|
|
385
|
+
function loadHeadSnapshot(directory) {
|
|
386
|
+
const migrations = loadMigrations(directory);
|
|
387
|
+
const last = migrations.at(-1);
|
|
388
|
+
if (last === undefined)
|
|
389
|
+
return;
|
|
390
|
+
return {
|
|
391
|
+
id: last.id,
|
|
392
|
+
catalogHash: last.catalogHash,
|
|
393
|
+
catalog: parseCatalog2(readFileSync(join(directory, `${last.id}.catalog.json`), "utf8"))
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/tooling/migrate/reference.ts
|
|
398
|
+
function readReference(tables, casing, head) {
|
|
399
|
+
const declared = [];
|
|
400
|
+
for (const item of tables) {
|
|
401
|
+
const options = item.options;
|
|
402
|
+
const reference = options?.reference;
|
|
403
|
+
if (reference === undefined)
|
|
404
|
+
continue;
|
|
405
|
+
const table = options?.sqlName ?? (casing === "snake" ? snakeCase(item.name) : item.name);
|
|
406
|
+
const key = keyNames(item, reference, casing);
|
|
407
|
+
if (!headHasTable(head, table)) {
|
|
408
|
+
throw new OkmError2("invalid", `Table ${item.name} declares reference rows and is not in the head snapshot.`, { fix: { summary: "Run okm generate so the snapshot includes the table." } });
|
|
409
|
+
}
|
|
410
|
+
if (!headHasKey(head, table, key)) {
|
|
411
|
+
throw new OkmError2("invalid", `Reference key ${key.join(", ")} on ${table} is not a primary key or a unique constraint.`, {
|
|
412
|
+
fix: {
|
|
413
|
+
summary: "Declare the key as the primary key or a unique constraint, in that column order."
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
declared.push({ table, key, rows: rowsOf(item, reference, key, casing) });
|
|
418
|
+
}
|
|
419
|
+
return declared;
|
|
420
|
+
}
|
|
421
|
+
function referenceInserts(tables, schema) {
|
|
422
|
+
const statements = [];
|
|
423
|
+
for (const item of tables) {
|
|
424
|
+
const conflict = item.key.map((name) => quoteIdent(name)).join(", ");
|
|
425
|
+
for (const row of item.rows) {
|
|
426
|
+
const columns = row.map(([name]) => quoteIdent(name)).join(", ");
|
|
427
|
+
const values = row.map(([, value]) => literal(value)).join(", ");
|
|
428
|
+
statements.push(`insert into ${qualify(schema, item.table)} (${columns}) values (${values}) on conflict (${conflict}) do nothing`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return statements;
|
|
432
|
+
}
|
|
433
|
+
function keyNames(item, reference, casing) {
|
|
434
|
+
const names = typeof reference.key === "string" ? [reference.key] : [...reference.key];
|
|
435
|
+
if (names.length === 0) {
|
|
436
|
+
throw new OkmError2("invalid", `Table ${item.name} reference key is empty.`, {
|
|
437
|
+
fix: { summary: "Name the column or columns that identify a reference row." }
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
return names.map((field) => fieldSql(item, field, casing));
|
|
441
|
+
}
|
|
442
|
+
function rowsOf(item, reference, key, casing) {
|
|
443
|
+
if (!Array.isArray(reference.rows)) {
|
|
444
|
+
throw new OkmError2("invalid", `Table ${item.name} reference rows must be a list.`, {
|
|
445
|
+
fix: { summary: "Pass reference: { key, rows } with one object per row." }
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
return reference.rows.map((row, index) => rowOf(item, row, key, casing, index));
|
|
449
|
+
}
|
|
450
|
+
function rowOf(item, row, key, casing, index) {
|
|
451
|
+
if (row === null || typeof row !== "object" || Array.isArray(row)) {
|
|
452
|
+
throw new OkmError2("invalid", `Table ${item.name} reference row ${String(index)} is not an object.`, {
|
|
453
|
+
fix: { summary: "Each reference row is an object of column names and values." }
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
const pairs = [];
|
|
457
|
+
const seen = new Set;
|
|
458
|
+
for (const name of key) {
|
|
459
|
+
const field = fieldFor(item, name, casing);
|
|
460
|
+
if (field === undefined || !Object.hasOwn(row, field)) {
|
|
461
|
+
throw new OkmError2("invalid", `Table ${item.name} reference row ${String(index)} is missing key ${name}.`, { fix: { summary: "Every reference row includes its key columns." } });
|
|
462
|
+
}
|
|
463
|
+
pairs.push([name, valueOf(item, index, field, row[field])]);
|
|
464
|
+
seen.add(field);
|
|
465
|
+
}
|
|
466
|
+
for (const field of Object.keys(row)) {
|
|
467
|
+
if (seen.has(field))
|
|
468
|
+
continue;
|
|
469
|
+
pairs.push([fieldSql(item, field, casing), valueOf(item, index, field, row[field])]);
|
|
470
|
+
}
|
|
471
|
+
return pairs;
|
|
472
|
+
}
|
|
473
|
+
function fieldFor(item, sqlName, casing) {
|
|
474
|
+
for (const field of Object.keys(item.columns)) {
|
|
475
|
+
if (fieldSql(item, field, casing) === sqlName)
|
|
476
|
+
return field;
|
|
477
|
+
}
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
function fieldSql(item, field, casing) {
|
|
481
|
+
const column = item.columns[field];
|
|
482
|
+
if (column === undefined) {
|
|
483
|
+
throw new OkmError2("invalid", `Table ${item.name} reference names ${field}, which is not a column.`, {
|
|
484
|
+
fix: { summary: "Use a column of that table." }
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const named = column.state?.sqlName;
|
|
488
|
+
if (named !== undefined && named.length > 0)
|
|
489
|
+
return named;
|
|
490
|
+
return casing === "snake" ? snakeCase(field) : field;
|
|
491
|
+
}
|
|
492
|
+
function valueOf(item, index, field, value) {
|
|
493
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
494
|
+
return value;
|
|
495
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
496
|
+
return value;
|
|
497
|
+
throw new OkmError2("invalid", `Table ${item.name} reference row ${String(index)} column ${field} is not a string, number, boolean, or null.`, {
|
|
498
|
+
fix: { summary: "Reference values are scalars. A later release can add other column types." }
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
function literal(value) {
|
|
502
|
+
if (value === null)
|
|
503
|
+
return "null";
|
|
504
|
+
if (typeof value === "boolean")
|
|
505
|
+
return value ? "true" : "false";
|
|
506
|
+
if (typeof value === "number")
|
|
507
|
+
return String(value);
|
|
508
|
+
return quoteLiteral(value);
|
|
509
|
+
}
|
|
510
|
+
function headHasTable(head, table) {
|
|
511
|
+
return head.objects.some((object) => object.kind === "table" && object.identity.name === table);
|
|
512
|
+
}
|
|
513
|
+
function headHasKey(head, table, key) {
|
|
514
|
+
const want = key.join("\x00");
|
|
515
|
+
for (const object of head.objects) {
|
|
516
|
+
if (object.kind === "constraint" && object.identity.parent.name === table) {
|
|
517
|
+
const kind = object.definition.constraintKind;
|
|
518
|
+
if ((kind === "primaryKey" || kind === "unique") && object.definition.columns.join("\x00") === want) {
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (object.kind === "index" && object.identity.parent.name === table && object.definition.unique && object.definition.predicate === undefined && object.definition.columns.join("\x00") === want) {
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/tooling/migrate/equal.ts
|
|
530
|
+
function catalogsEqual(left, right) {
|
|
531
|
+
if (!hasExtension(left) && !hasExtension(right)) {
|
|
532
|
+
return serializeCatalog2(left) === serializeCatalog2(right);
|
|
533
|
+
}
|
|
534
|
+
return serializeCatalog2(alignExtensionVersions(left, right)) === serializeCatalog2(alignExtensionVersions(right, left));
|
|
535
|
+
}
|
|
536
|
+
function hasExtension(catalog) {
|
|
537
|
+
return catalog.objects.some((object) => object.kind === "extension");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/tooling/migrate/snapshot.ts
|
|
541
|
+
var PROVISION_PREFIX = "provisioned@";
|
|
542
|
+
function provisionMark(migrationId) {
|
|
543
|
+
return `${PROVISION_PREFIX}${migrationId}`;
|
|
544
|
+
}
|
|
545
|
+
function provisionedMigrationId(migrationId) {
|
|
546
|
+
if (!migrationId.startsWith(PROVISION_PREFIX))
|
|
547
|
+
return;
|
|
548
|
+
const id = migrationId.slice(PROVISION_PREFIX.length);
|
|
549
|
+
return id.length > 0 ? id : undefined;
|
|
550
|
+
}
|
|
551
|
+
function expandProvisioned(migrations, done, historyIds) {
|
|
552
|
+
const next = new Set(done);
|
|
553
|
+
let covered = -1;
|
|
554
|
+
for (const historyId of historyIds) {
|
|
555
|
+
const id = provisionedMigrationId(historyId);
|
|
556
|
+
if (id === undefined)
|
|
557
|
+
continue;
|
|
558
|
+
const index = migrations.findIndex((migration) => migration.id === id);
|
|
559
|
+
if (index > covered)
|
|
560
|
+
covered = index;
|
|
561
|
+
}
|
|
562
|
+
for (let index = 0;index <= covered; index += 1) {
|
|
563
|
+
const migration = migrations[index];
|
|
564
|
+
if (migration === undefined)
|
|
565
|
+
continue;
|
|
566
|
+
for (let step = 0;step < migration.steps.length; step += 1) {
|
|
567
|
+
next.add(`${migration.id}:${String(step)}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return next;
|
|
571
|
+
}
|
|
572
|
+
function provisionHistoryIsKnown(migrationId, known) {
|
|
573
|
+
if (known.has(migrationId))
|
|
574
|
+
return true;
|
|
575
|
+
const id = provisionedMigrationId(migrationId);
|
|
576
|
+
return id !== undefined && known.has(id);
|
|
577
|
+
}
|
|
578
|
+
async function installSnapshot(connection, head, schema, reference, session, migrationRole) {
|
|
579
|
+
const plan = planMigration({ before: catalog([]), after: head, schema, name: "provision" });
|
|
580
|
+
let began = false;
|
|
581
|
+
const batch = [];
|
|
582
|
+
const flush = async () => {
|
|
583
|
+
if (batch.length === 0)
|
|
584
|
+
return;
|
|
585
|
+
await connection.execute("begin");
|
|
586
|
+
began = true;
|
|
587
|
+
for (const sql of batch)
|
|
588
|
+
await connection.execute(sql);
|
|
589
|
+
await connection.execute("commit");
|
|
590
|
+
began = false;
|
|
591
|
+
batch.length = 0;
|
|
592
|
+
};
|
|
593
|
+
try {
|
|
594
|
+
for (const step of plan.steps) {
|
|
595
|
+
if (step.backfill !== undefined)
|
|
596
|
+
continue;
|
|
597
|
+
const created = createdRoleName(step.sql);
|
|
598
|
+
const existed = created !== undefined && await roleExists(connection, created);
|
|
599
|
+
if (created !== undefined && existed) {
|
|
600
|
+
if (created === migrationRole)
|
|
601
|
+
await assumeRole(connection, created, session);
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (step.transactional && !statementOutsideTransaction(step.sql)) {
|
|
605
|
+
batch.push(step.sql);
|
|
606
|
+
} else {
|
|
607
|
+
await flush();
|
|
608
|
+
await connection.execute(step.sql);
|
|
609
|
+
}
|
|
610
|
+
if (created !== undefined && created === migrationRole) {
|
|
611
|
+
await flush();
|
|
612
|
+
await grantSchema(connection, schema, created);
|
|
613
|
+
await connection.execute(`grant select, insert, update on okm_meta, okm_history, okm_backfill to ${quoteIdent(created)}`);
|
|
614
|
+
await assumeRole(connection, created, session);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
await flush();
|
|
618
|
+
for (const sql of referenceInserts(reference, schema)) {
|
|
619
|
+
if (!began) {
|
|
620
|
+
await connection.execute("begin");
|
|
621
|
+
began = true;
|
|
622
|
+
}
|
|
623
|
+
await connection.execute(sql);
|
|
624
|
+
}
|
|
625
|
+
if (began) {
|
|
626
|
+
await connection.execute("commit");
|
|
627
|
+
began = false;
|
|
628
|
+
}
|
|
629
|
+
} catch (error) {
|
|
630
|
+
if (began)
|
|
631
|
+
await connection.execute("rollback").catch(() => {
|
|
632
|
+
return;
|
|
633
|
+
});
|
|
634
|
+
throw error;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function snapshotDifference(left, right) {
|
|
638
|
+
const provided = normaliseCatalog(omitManagedObjects(left));
|
|
639
|
+
const replayed = normaliseCatalog(omitManagedObjects(right));
|
|
640
|
+
if (catalogsEqual(provided, replayed))
|
|
641
|
+
return;
|
|
642
|
+
const forward = diffSteps(provided, replayed);
|
|
643
|
+
const backward = diffSteps(replayed, provided);
|
|
644
|
+
const sql = forward[0] ?? backward[0];
|
|
645
|
+
const object = firstObject(provided, replayed);
|
|
646
|
+
return sql === undefined ? object : `${object}
|
|
647
|
+
${sql}`;
|
|
648
|
+
}
|
|
649
|
+
function diffSteps(before, after) {
|
|
650
|
+
try {
|
|
651
|
+
return planMigration({ before, after, name: "snapshot" }).steps.map((step) => step.sql);
|
|
652
|
+
} catch (error) {
|
|
653
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
654
|
+
return [message];
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function firstObject(left, right) {
|
|
658
|
+
const rightBy = new Map(right.objects.map((object) => [identityKey2(object.identity), object]));
|
|
659
|
+
for (const object of left.objects) {
|
|
660
|
+
const other = rightBy.get(identityKey2(object.identity));
|
|
661
|
+
if (other === undefined)
|
|
662
|
+
return `${identityLabel2(object.identity)} is only in the snapshot`;
|
|
663
|
+
if (JSON.stringify(object) !== JSON.stringify(other))
|
|
664
|
+
return identityLabel2(object.identity);
|
|
665
|
+
}
|
|
666
|
+
for (const object of right.objects) {
|
|
667
|
+
if (!left.objects.some((item) => identityKey2(item.identity) === identityKey2(object.identity))) {
|
|
668
|
+
return `${identityLabel2(object.identity)} is only in the replayed history`;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return "the catalogs differ";
|
|
672
|
+
}
|
|
673
|
+
function normaliseCatalog(source) {
|
|
674
|
+
let changed = false;
|
|
675
|
+
const objects = source.objects.map((object) => {
|
|
676
|
+
const next = normaliseObject(object);
|
|
677
|
+
if (next !== object)
|
|
678
|
+
changed = true;
|
|
679
|
+
return next;
|
|
680
|
+
});
|
|
681
|
+
return changed ? { version: source.version, objects } : source;
|
|
682
|
+
}
|
|
683
|
+
function normaliseObject(object) {
|
|
684
|
+
if (object.kind === "column")
|
|
685
|
+
return normaliseColumn(object);
|
|
686
|
+
if (object.kind === "type" && isDomain(object.definition))
|
|
687
|
+
return normaliseDomain(object);
|
|
688
|
+
if (object.kind === "view")
|
|
689
|
+
return normaliseView(object);
|
|
690
|
+
if (object.kind === "materializedView")
|
|
691
|
+
return normaliseMatview(object);
|
|
692
|
+
if (object.kind === "function")
|
|
693
|
+
return normaliseFunction(object);
|
|
694
|
+
return object;
|
|
695
|
+
}
|
|
696
|
+
function normaliseColumn(object) {
|
|
697
|
+
const dataType = canonicalTypeName(object.definition.dataType);
|
|
698
|
+
if (dataType === object.definition.dataType)
|
|
699
|
+
return object;
|
|
700
|
+
return { ...object, definition: { ...object.definition, dataType } };
|
|
701
|
+
}
|
|
702
|
+
function normaliseDomain(object) {
|
|
703
|
+
if (!isDomain(object.definition))
|
|
704
|
+
return object;
|
|
705
|
+
const base = canonicalTypeName(object.definition.base);
|
|
706
|
+
if (base === object.definition.base)
|
|
707
|
+
return object;
|
|
708
|
+
return { ...object, definition: { ...object.definition, base } };
|
|
709
|
+
}
|
|
710
|
+
function normaliseView(object) {
|
|
711
|
+
const columns = normaliseViewColumns(object.definition.columns);
|
|
712
|
+
if (columns === object.definition.columns)
|
|
713
|
+
return object;
|
|
714
|
+
return { ...object, definition: { ...object.definition, columns } };
|
|
715
|
+
}
|
|
716
|
+
function normaliseMatview(object) {
|
|
717
|
+
const columns = normaliseViewColumns(object.definition.columns);
|
|
718
|
+
if (columns === object.definition.columns)
|
|
719
|
+
return object;
|
|
720
|
+
return { ...object, definition: { ...object.definition, columns } };
|
|
721
|
+
}
|
|
722
|
+
function normaliseViewColumns(columns) {
|
|
723
|
+
let changed = false;
|
|
724
|
+
const next = columns.map((column) => {
|
|
725
|
+
const dataType = canonicalTypeName(column.dataType);
|
|
726
|
+
if (dataType === column.dataType)
|
|
727
|
+
return column;
|
|
728
|
+
changed = true;
|
|
729
|
+
return { ...column, dataType };
|
|
730
|
+
});
|
|
731
|
+
return changed ? next : columns;
|
|
732
|
+
}
|
|
733
|
+
function normaliseFunction(object) {
|
|
734
|
+
const returns = canonicalTypeName(object.definition.returns);
|
|
735
|
+
let changed = returns !== object.definition.returns;
|
|
736
|
+
const arguments_ = object.definition.arguments.map((argument) => {
|
|
737
|
+
const type = canonicalTypeName(argument.type);
|
|
738
|
+
if (type === argument.type)
|
|
739
|
+
return argument;
|
|
740
|
+
changed = true;
|
|
741
|
+
return { ...argument, type };
|
|
742
|
+
});
|
|
743
|
+
if (!changed)
|
|
744
|
+
return object;
|
|
745
|
+
return {
|
|
746
|
+
...object,
|
|
747
|
+
definition: { ...object.definition, returns, arguments: arguments_ }
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
export { runTarget, assumeRole, statementOutsideTransaction, loadMigrations, loadHeadSnapshot, readReference, referenceInserts, catalogsEqual, provisionMark, expandProvisioned, provisionHistoryIsKnown, installSnapshot, snapshotDifference };
|