okmodel 0.5.0 → 0.6.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 +36 -18
- package/dist/adapters/pg/no-inflight-timeout.d.ts +17 -0
- package/dist/adapters/pg/postgres-extra.d.ts +22 -0
- package/dist/contracts/catalog/document.d.ts +19 -5
- package/dist/contracts/catalog/object.d.ts +2 -1
- package/dist/contracts/catalog/policy.d.ts +48 -0
- package/dist/contracts/catalog/types.d.ts +25 -1
- package/dist/contracts/catalog/view.d.ts +2 -0
- package/dist/contracts/internal.js +1 -1
- package/dist/contracts/location.d.ts +10 -0
- package/dist/dialects/pg/column.d.ts +13 -2
- package/dist/dialects/pg/ddl.d.ts +37 -1
- package/dist/dialects/pg/emit.d.ts +19 -1
- package/dist/dialects/pg/errors.d.ts +32 -1
- 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/filter-parse.d.ts +10 -3
- package/dist/dialects/pg/fn/index.js +10 -2
- package/dist/dialects/pg/index.d.ts +2 -1
- package/dist/dialects/pg/index.js +29 -17
- package/dist/dialects/pg/introspect.d.ts +7 -0
- package/dist/dialects/pg/model.d.ts +7 -2
- package/dist/dialects/pg/ops/or.d.ts +1 -1
- package/dist/dialects/pg/owner.d.ts +42 -0
- package/dist/dialects/pg/path-endpoint.d.ts +20 -0
- package/dist/dialects/pg/role/check.d.ts +3 -0
- package/dist/dialects/pg/role/index.d.ts +71 -6
- package/dist/dialects/pg/role/sql.d.ts +11 -0
- package/dist/dialects/pg/schema.d.ts +2 -0
- package/dist/dialects/pg/table.d.ts +25 -3
- package/dist/dialects/pg/temporal.d.ts +5 -76
- package/dist/dialects/pg/tenancy.d.ts +45 -3
- package/dist/dialects/pg/view/client.d.ts +19 -1
- package/dist/dialects/pg/view/index.d.ts +4 -0
- package/dist/dialects/pg/view/index.js +21 -178
- package/dist/dialects/pg/view/refresh.d.ts +24 -0
- package/dist/runtime/batch.d.ts +1 -1
- package/dist/runtime/client.d.ts +6 -1
- package/dist/runtime/conflict.d.ts +2 -1
- package/dist/runtime/drift.d.ts +9 -0
- package/dist/runtime/inflight.d.ts +14 -0
- package/dist/runtime/pg/bun.js +6 -5
- package/dist/runtime/pg/pg.js +5 -5
- package/dist/runtime/pg/pglite.js +6 -5
- package/dist/runtime/pg/postgresjs.js +5 -5
- package/dist/runtime/plan.d.ts +22 -1
- package/dist/runtime/predicate.d.ts +19 -0
- package/dist/runtime/safety/index.js +1 -1
- package/dist/runtime/tenancy/composite.d.ts +26 -0
- package/dist/runtime/tenancy/context.d.ts +16 -0
- package/dist/runtime/tenancy/index.d.ts +9 -5
- package/dist/runtime/tenancy/index.js +1508 -37
- package/dist/runtime/tenancy/path.d.ts +87 -0
- package/dist/runtime/tenancy/rls.d.ts +67 -0
- package/dist/runtime/tenancy/via.d.ts +22 -0
- package/dist/runtime/traits/index.js +17 -11
- package/dist/runtime/types.d.ts +13 -1
- package/dist/runtime/validate/index.js +4 -4
- package/dist/runtime/write.d.ts +1 -1
- package/dist/shared/09p049xa.js +702 -0
- package/dist/shared/{ykv7wh5b.js → 1f0xa3cb.js} +5 -5
- package/dist/shared/1nw1f3vs.js +23 -0
- package/dist/shared/{6p5k20sm.js → 1z04fa85.js} +21 -11
- package/dist/shared/{p7pfx624.js → 2cg3wnxc.js} +314 -65
- package/dist/shared/{dyj86cmj.js → 2jd1sn0e.js} +2 -2
- package/dist/shared/{qgvt8nx1.js → 2z7wv5tj.js} +21 -5
- package/dist/shared/{kyxb62mc.js → 31vm914h.js} +9 -1
- package/dist/shared/{sf8m6tfy.js → 32cnm7kn.js} +8 -3
- package/dist/shared/{t4tbsy9h.js → 358zsv4r.js} +1 -1
- package/dist/shared/{jdgqqx31.js → 3fqwmr09.js} +38 -14
- package/dist/shared/{922qbgnw.js → 3qnhc7w5.js} +16 -9
- package/dist/shared/{zvbjc949.js → 4az561fn.js} +1 -1
- package/dist/shared/{k3ted12y.js → 54tqkxja.js} +20 -2
- package/dist/shared/{k7cc9nzk.js → 5d8t84hg.js} +15 -3
- package/dist/shared/{fa9vt245.js → 5j36z8dn.js} +17 -6
- package/dist/shared/{1g0drakv.js → 5yv4k6a6.js} +1 -1
- package/dist/shared/8dqhth8c.js +12 -0
- package/dist/shared/8etn7d8w.js +62 -0
- package/dist/shared/{g516zdsb.js → 8x72s86k.js} +476 -30
- package/dist/shared/{nkc9sfqb.js → 929h19xp.js} +145 -7
- package/dist/shared/{gjthds9j.js → 9628a9xt.js} +1 -1
- package/dist/shared/{8xsbagad.js → 9s0ypp87.js} +88 -7
- package/dist/shared/{b066g80h.js → ag4c7516.js} +2 -2
- package/dist/shared/b3awxn7h.js +45 -0
- package/dist/shared/b4v57v3x.js +9 -0
- package/dist/shared/bdk4sty6.js +58 -0
- package/dist/shared/{s6gqfn5j.js → c58secgj.js} +125 -8
- package/dist/shared/{rrstp3kb.js → cgbamx3e.js} +2 -2
- package/dist/shared/d5azex6s.js +6 -0
- package/dist/shared/{5fqx9yh4.js → dk8v79p3.js} +3 -1
- package/dist/shared/{4m9fn52z.js → ed0b8k4s.js} +1 -1
- package/dist/shared/{z2rnngj4.js → ek2wt2dz.js} +8 -5
- package/dist/shared/{wxn4b7pp.js → f0f1n281.js} +59 -12
- package/dist/shared/{gdh9mrtp.js → g90m9b0g.js} +3 -2
- package/dist/shared/{tbszx8mz.js → gwn6v667.js} +2 -2
- package/dist/shared/{c8aghh05.js → h15jx9bc.js} +8 -2
- package/dist/shared/{3p4b1fg0.js → hazqqecq.js} +17 -93
- package/dist/shared/{4ef7mmqf.js → j3rkk835.js} +34 -11
- package/dist/shared/{bt9sfq0z.js → j60w0dvr.js} +56 -21
- package/dist/shared/{ycatkxmq.js → jgqj11w4.js} +1 -1
- package/dist/shared/k68eedmy.js +85 -0
- package/dist/shared/{szm0cksa.js → knmes2kh.js} +34 -6
- package/dist/shared/{ckjbyrt9.js → mb2ycs1p.js} +7 -9
- package/dist/shared/{tq9afp8d.js → n076ydsc.js} +1 -1
- package/dist/shared/{xhh1srw3.js → nx9hh281.js} +50 -32
- package/dist/shared/{kdkxh3kb.js → p248q79y.js} +58 -59
- package/dist/shared/{990a291j.js → p6bjzq9q.js} +14 -9
- package/dist/shared/{h4nqvjkp.js → pjr6xrm5.js} +2 -2
- package/dist/shared/pz6rj8ay.js +212 -0
- package/dist/shared/{5vdm4sgp.js → q3j1tq7a.js} +36 -23
- package/dist/shared/{8bwbwy8h.js → qq3899ht.js} +128 -18
- package/dist/shared/{dxjp14rk.js → r11n8grd.js} +2 -2
- package/dist/shared/r1fdd0f6.js +19 -0
- package/dist/shared/{8ktwd3rh.js → r3j8s5y0.js} +1 -1
- package/dist/shared/rhs3xe4k.js +7 -0
- package/dist/shared/{jsd07prz.js → rpycarcr.js} +6 -6
- package/dist/shared/{xsq7v6wp.js → sbdme3vy.js} +14 -2
- package/dist/shared/{xdpj5kkm.js → t3g0k88x.js} +775 -264
- package/dist/shared/{ayz64vzj.js → ttmg09vd.js} +708 -19
- package/dist/shared/{r6pj4fq2.js → venqrjna.js} +8 -4
- package/dist/shared/{fancm5pt.js → vjpnzwjz.js} +138 -15
- package/dist/shared/{9tgmr1gh.js → vmfkhszw.js} +44 -9
- package/dist/shared/{wqem4jed.js → wkme6tep.js} +11 -9
- package/dist/shared/{qy9rdq0g.js → wt5rzv1h.js} +15 -11
- package/dist/shared/wxr8cwns.js +6 -0
- package/dist/shared/{bzsjtd7c.js → wynzeggs.js} +19 -14
- package/dist/shared/x3jc53kg.js +220 -0
- package/dist/shared/{7p2wbz7f.js → x72jb7ad.js} +1 -1
- package/dist/shared/{fyqkk1m0.js → xq0mvhts.js} +10 -9
- package/dist/shared/xqve9598.js +53 -0
- package/dist/shared/{47xzb9fc.js → y2t3jktd.js} +2 -2
- package/dist/shared/{9x25fxhc.js → y34a8714.js} +1 -1
- package/dist/shared/{k7dwkhvq.js → y6jdds7g.js} +4 -1
- package/dist/shared/{h8ne16fq.js → ys0xzyaj.js} +1 -1
- package/dist/shared/{c9z5g0fm.js → ywr8hmey.js} +5 -2
- package/dist/shared/{g7ez901n.js → ywspezzx.js} +1 -1
- package/dist/shared/{9m80mkac.js → ze4hxsmy.js} +1 -1
- package/dist/shared/{fb00y8fp.js → zm0mafk4.js} +12 -6
- package/dist/shared/{y005b761.js → zy8r7qvz.js} +6 -2
- package/dist/tooling/cli.js +97 -24
- package/dist/tooling/migrate/apply.d.ts +14 -0
- package/dist/tooling/migrate/check.d.ts +3 -4
- package/dist/tooling/migrate/classify.d.ts +4 -0
- package/dist/tooling/migrate/commands.d.ts +1 -2
- package/dist/tooling/migrate/config.d.ts +3 -2
- package/dist/tooling/migrate/drift.d.ts +1 -1
- package/dist/tooling/migrate/estimate.d.ts +14 -1
- package/dist/tooling/migrate/files.d.ts +11 -1
- package/dist/tooling/migrate/index.js +17 -16
- package/dist/tooling/migrate/lint.d.ts +4 -4
- package/dist/tooling/migrate/lost.d.ts +28 -0
- package/dist/tooling/migrate/paths.d.ts +14 -0
- package/dist/tooling/migrate/plan.d.ts +10 -3
- package/dist/tooling/migrate/policy.d.ts +11 -0
- package/dist/tooling/migrate/runner.d.ts +25 -0
- package/dist/tooling/migrate/sandbox.d.ts +57 -0
- package/dist/tooling/migrate/sql-lex.d.ts +39 -0
- package/dist/tooling/migrate/text.d.ts +42 -0
- package/dist/tooling/node-version.d.ts +21 -0
- package/dist/tooling/testing/factories.d.ts +10 -0
- package/dist/tooling/testing/facts.d.ts +33 -5
- package/dist/tooling/testing/index.d.ts +1 -1
- package/dist/tooling/testing/index.js +3 -3
- package/dist/tooling/testing/isolation.d.ts +15 -8
- package/package.json +10 -6
- package/dist/shared/0v1g424p.js +0 -26
- package/dist/shared/r73pt594.js +0 -426
- package/dist/shared/rasvvzgz.js +0 -21
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseCatalog2
|
|
3
|
+
} from "./qq3899ht.js";
|
|
4
|
+
import"./n442h54c.js";
|
|
5
|
+
import"./wkme6tep.js";
|
|
6
|
+
import {
|
|
7
|
+
watchReserved2
|
|
8
|
+
} from "./q3j1tq7a.js";
|
|
9
|
+
import {
|
|
10
|
+
ensureMigrationTables2
|
|
11
|
+
} from "./nx9hh281.js";
|
|
12
|
+
import {
|
|
13
|
+
sealViews,
|
|
14
|
+
catalogQuery2,
|
|
15
|
+
managedRoleOptions2
|
|
16
|
+
} from "./f0f1n281.js";
|
|
17
|
+
import {
|
|
18
|
+
assumeRole,
|
|
19
|
+
grantSchema,
|
|
20
|
+
assertSchemaUsageLanded,
|
|
21
|
+
catalogsEqual,
|
|
22
|
+
snapshotDifference
|
|
23
|
+
} from "./p248q79y.js";
|
|
24
|
+
import {
|
|
25
|
+
roleExists
|
|
26
|
+
} from "./knmes2kh.js";
|
|
27
|
+
import {
|
|
28
|
+
introspectSchema
|
|
29
|
+
} from "./vjpnzwjz.js";
|
|
30
|
+
import {
|
|
31
|
+
projectPath,
|
|
32
|
+
loadMigrations,
|
|
33
|
+
readMigrationPlan,
|
|
34
|
+
lintMigrationDirectory,
|
|
35
|
+
hasError,
|
|
36
|
+
lintRefusal,
|
|
37
|
+
openProject
|
|
38
|
+
} from "./t3g0k88x.js";
|
|
39
|
+
import {
|
|
40
|
+
sqlStatements,
|
|
41
|
+
transactionControl,
|
|
42
|
+
canonicalTypeName,
|
|
43
|
+
createdRoleName,
|
|
44
|
+
createRoleOnce,
|
|
45
|
+
classOf,
|
|
46
|
+
strictestClass,
|
|
47
|
+
planMigration,
|
|
48
|
+
sessionFailure,
|
|
49
|
+
finishQuiet,
|
|
50
|
+
assertDirectConnection,
|
|
51
|
+
assertStableBackend,
|
|
52
|
+
selectTarget
|
|
53
|
+
} from "./ttmg09vd.js";
|
|
54
|
+
import {
|
|
55
|
+
isConnectionLost
|
|
56
|
+
} from "./gwn6v667.js";
|
|
57
|
+
import {
|
|
58
|
+
quoteIdent
|
|
59
|
+
} from "./9s0ypp87.js";
|
|
60
|
+
import {
|
|
61
|
+
catalog,
|
|
62
|
+
isDomain
|
|
63
|
+
} from "./ywspezzx.js";
|
|
64
|
+
import {
|
|
65
|
+
identityKey2
|
|
66
|
+
} from "./358zsv4r.js";
|
|
67
|
+
import {
|
|
68
|
+
OkmError2
|
|
69
|
+
} from "./52szzakm.js";
|
|
70
|
+
|
|
71
|
+
// src/tooling/migrate/check.ts
|
|
72
|
+
import { readFileSync } from "node:fs";
|
|
73
|
+
import { join } from "node:path";
|
|
74
|
+
|
|
75
|
+
// src/tooling/migrate/sandbox.ts
|
|
76
|
+
import postgres from "postgres";
|
|
77
|
+
async function replayCheckHistory(url, scratch, config, allowPooler, history) {
|
|
78
|
+
const skips = [];
|
|
79
|
+
let stop;
|
|
80
|
+
await inCheckTransaction(url, scratch, config, allowPooler, async (sql) => {
|
|
81
|
+
const connection = driverOf(sql);
|
|
82
|
+
const session = {};
|
|
83
|
+
for (let index = 0;index < history.length; index += 1) {
|
|
84
|
+
const migration = history[index];
|
|
85
|
+
if (migration === undefined)
|
|
86
|
+
continue;
|
|
87
|
+
await replayMigration(sql, connection, scratch, config, migration, session, skips);
|
|
88
|
+
await restoreSession(sql, session);
|
|
89
|
+
const runner = catalogQuery2(sql);
|
|
90
|
+
const live = await introspectSchema(runner, scratch, "public", managedRoleOptions2(config.roles));
|
|
91
|
+
const sealed = await sealViews(runner, migration.catalog);
|
|
92
|
+
const back = planMigration({ before: live, after: sealed, name: migration.id });
|
|
93
|
+
if (back.steps.length === 0)
|
|
94
|
+
continue;
|
|
95
|
+
stop = { index, back };
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
return stop === undefined ? { skips } : { skips, stop };
|
|
100
|
+
}
|
|
101
|
+
async function proveCheckSnapshot(url, provided, replayed, config, allowPooler, history) {
|
|
102
|
+
const head = history.at(-1);
|
|
103
|
+
if (head === undefined)
|
|
104
|
+
return;
|
|
105
|
+
const installed = await inCheckTransaction(url, provided, config, allowPooler, async (sql) => {
|
|
106
|
+
const connection = driverOf(sql);
|
|
107
|
+
const session = {};
|
|
108
|
+
const planned = planMigration({
|
|
109
|
+
before: catalog([]),
|
|
110
|
+
after: head.catalog,
|
|
111
|
+
schema: provided,
|
|
112
|
+
name: "provision"
|
|
113
|
+
});
|
|
114
|
+
await runSteps(sql, connection, provided, config.roles?.migration, {
|
|
115
|
+
id: "snapshot",
|
|
116
|
+
catalog: head.catalog,
|
|
117
|
+
steps: retargetSteps(planned.steps, provided)
|
|
118
|
+
}, session, []);
|
|
119
|
+
await restoreSession(sql, session);
|
|
120
|
+
return introspectSchema(catalogQuery2(sql), provided, "public", managedRoleOptions2(config.roles));
|
|
121
|
+
});
|
|
122
|
+
const again = await inCheckTransaction(url, replayed, config, allowPooler, async (sql) => {
|
|
123
|
+
const connection = driverOf(sql);
|
|
124
|
+
const session = {};
|
|
125
|
+
for (const migration of history) {
|
|
126
|
+
await replayMigration(sql, connection, replayed, config, migration, session, []);
|
|
127
|
+
}
|
|
128
|
+
await restoreSession(sql, session);
|
|
129
|
+
return introspectSchema(catalogQuery2(sql), replayed, "public", managedRoleOptions2(config.roles));
|
|
130
|
+
});
|
|
131
|
+
return snapshotDifference(installed, again);
|
|
132
|
+
}
|
|
133
|
+
function retargetSteps(steps, schema) {
|
|
134
|
+
const from = `${quoteIdent("public")}.`;
|
|
135
|
+
const to = `${quoteIdent(schema)}.`;
|
|
136
|
+
const fromDefaults = `in schema ${quoteIdent("public")} `;
|
|
137
|
+
const toDefaults = `in schema ${quoteIdent(schema)} `;
|
|
138
|
+
const fromSchema = `on schema ${quoteIdent("public")}`;
|
|
139
|
+
const toSchema = `on schema ${quoteIdent(schema)}`;
|
|
140
|
+
return steps.map((step) => ({
|
|
141
|
+
...step,
|
|
142
|
+
sql: step.sql.replaceAll(from, to).replaceAll(fromDefaults, toDefaults).replaceAll(fromSchema, toSchema),
|
|
143
|
+
...step.backfill === undefined ? {} : { backfill: { ...step.backfill, table: step.backfill.table.replaceAll(from, to) } }
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
async function inCheckTransaction(url, scratch, config, allowPooler, run) {
|
|
147
|
+
const sql = postgres(url, {
|
|
148
|
+
max: 1,
|
|
149
|
+
connect_timeout: 5,
|
|
150
|
+
idle_timeout: 1,
|
|
151
|
+
onnotice: () => {}
|
|
152
|
+
});
|
|
153
|
+
const reserved = await sql.reserve();
|
|
154
|
+
const lost = { on: false };
|
|
155
|
+
watchReserved2(reserved, lost);
|
|
156
|
+
refuseWhenLost(reserved, lost);
|
|
157
|
+
let open = false;
|
|
158
|
+
let failure;
|
|
159
|
+
let value;
|
|
160
|
+
try {
|
|
161
|
+
await assertStableBackend(async () => {
|
|
162
|
+
const rows = await reserved.unsafe("select pg_backend_pid()::text as pid");
|
|
163
|
+
const pid = cell(rows[0], "pid");
|
|
164
|
+
return pid ?? "";
|
|
165
|
+
}, allowPooler);
|
|
166
|
+
await reserved.unsafe("begin");
|
|
167
|
+
open = true;
|
|
168
|
+
const xid = await readXid(reserved);
|
|
169
|
+
await reserved.unsafe(`create schema ${quoteIdent(scratch)}`);
|
|
170
|
+
await reserved.unsafe(`set local search_path to ${quoteIdent(scratch)}`);
|
|
171
|
+
await setLocalTimeout(reserved, "lock_timeout", config.timeouts?.lock);
|
|
172
|
+
await setLocalTimeout(reserved, "statement_timeout", config.timeouts?.statement);
|
|
173
|
+
await ownScratch(reserved, scratch, config.roles?.migration);
|
|
174
|
+
await ensureMigrationTables2((text) => reserved.unsafe(text));
|
|
175
|
+
value = await run(reserved);
|
|
176
|
+
const after = await readXid(reserved).catch((error) => {
|
|
177
|
+
if (isConnectionLost(error))
|
|
178
|
+
lost.on = true;
|
|
179
|
+
return;
|
|
180
|
+
});
|
|
181
|
+
if (!lost.on && after !== xid)
|
|
182
|
+
throw xidChanged();
|
|
183
|
+
if (!lost.on)
|
|
184
|
+
await reserved.unsafe("rollback");
|
|
185
|
+
open = false;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (isConnectionLost(error))
|
|
188
|
+
lost.on = true;
|
|
189
|
+
failure = error;
|
|
190
|
+
} finally {
|
|
191
|
+
if (open && !lost.on) {
|
|
192
|
+
await reserved.unsafe("rollback").catch((error) => {
|
|
193
|
+
if (isConnectionLost(error))
|
|
194
|
+
lost.on = true;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (!lost.on)
|
|
198
|
+
await reserved.unsafe("reset role").catch(() => {
|
|
199
|
+
return;
|
|
200
|
+
});
|
|
201
|
+
if (!lost.on) {
|
|
202
|
+
try {
|
|
203
|
+
reserved.release();
|
|
204
|
+
} catch {
|
|
205
|
+
lost.on = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
await finishQuiet(() => sql.end({ timeout: 5 }));
|
|
209
|
+
}
|
|
210
|
+
const left = await dropScratch(url, scratch);
|
|
211
|
+
if (left !== undefined) {
|
|
212
|
+
const note = `Scratch schema ${left} is still present.`;
|
|
213
|
+
failure = failure === undefined ? new OkmError2("unavailable", note, { kind: "unavailable", fix: UNAVAILABLE_FIX }) : noteOn(failure, note);
|
|
214
|
+
}
|
|
215
|
+
if (failure !== undefined)
|
|
216
|
+
throw sessionFailure(failure) ?? failure;
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
var UNAVAILABLE_FIX = {
|
|
220
|
+
summary: "The connection to Postgres closed. Run the command again when the database is reachable."
|
|
221
|
+
};
|
|
222
|
+
function refuseWhenLost(sql, lost) {
|
|
223
|
+
const unsafe = sql.unsafe.bind(sql);
|
|
224
|
+
sql.unsafe = (text, params, options) => {
|
|
225
|
+
if (lost.on)
|
|
226
|
+
return Promise.reject(closedError());
|
|
227
|
+
const pending = options !== undefined ? unsafe(text, params, options) : params !== undefined ? unsafe(text, params) : unsafe(text);
|
|
228
|
+
return pending.catch((error) => {
|
|
229
|
+
if (isConnectionLost(error))
|
|
230
|
+
lost.on = true;
|
|
231
|
+
throw error;
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function closedError() {
|
|
236
|
+
return Object.assign(new Error("CONNECTION_CLOSED"), { code: "CONNECTION_CLOSED" });
|
|
237
|
+
}
|
|
238
|
+
async function dropScratch(url, scratch) {
|
|
239
|
+
const sql = postgres(url, {
|
|
240
|
+
max: 1,
|
|
241
|
+
connect_timeout: 5,
|
|
242
|
+
idle_timeout: 1,
|
|
243
|
+
onnotice: () => {}
|
|
244
|
+
});
|
|
245
|
+
try {
|
|
246
|
+
await sql.unsafe(`drop schema if exists ${quoteIdent(scratch)} cascade`);
|
|
247
|
+
const rows = await sql`
|
|
248
|
+
select nspname from pg_namespace where nspname = ${scratch}
|
|
249
|
+
`;
|
|
250
|
+
return rows.length > 0 ? scratch : undefined;
|
|
251
|
+
} catch {
|
|
252
|
+
return scratch;
|
|
253
|
+
} finally {
|
|
254
|
+
await finishQuiet(() => sql.end({ timeout: 5 }));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function noteOn(error, note) {
|
|
258
|
+
if (error instanceof Error) {
|
|
259
|
+
return new OkmError2("unavailable", `${error.message} ${note}`, {
|
|
260
|
+
kind: "unavailable",
|
|
261
|
+
cause: error,
|
|
262
|
+
fix: UNAVAILABLE_FIX
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return new OkmError2("unavailable", note, { kind: "unavailable", fix: UNAVAILABLE_FIX });
|
|
266
|
+
}
|
|
267
|
+
async function restoreSession(sql, session) {
|
|
268
|
+
if (session.setRole === undefined)
|
|
269
|
+
return;
|
|
270
|
+
await sql.unsafe("reset role");
|
|
271
|
+
delete session.setRole;
|
|
272
|
+
}
|
|
273
|
+
async function replayMigration(sql, connection, scratch, config, migration, session, skips) {
|
|
274
|
+
await runSteps(sql, connection, scratch, config.roles?.migration, { ...migration, steps: retargetSteps(migration.steps, scratch) }, session, skips);
|
|
275
|
+
}
|
|
276
|
+
async function runSteps(sql, connection, scratch, migrationRole, migration, session, skips) {
|
|
277
|
+
for (let index = 0;index < migration.steps.length; index += 1) {
|
|
278
|
+
const step = migration.steps[index];
|
|
279
|
+
if (step === undefined)
|
|
280
|
+
continue;
|
|
281
|
+
const prepared = prepareCheckSql(step.sql);
|
|
282
|
+
if (prepared.skip !== undefined) {
|
|
283
|
+
skips.push(`${migration.id} step ${String(index + 1)}: ${prepared.skip}`);
|
|
284
|
+
}
|
|
285
|
+
if (prepared.sql.length === 0)
|
|
286
|
+
continue;
|
|
287
|
+
try {
|
|
288
|
+
const created = createdRoleName(step.sql);
|
|
289
|
+
const existed = created !== undefined && await roleExists(connection, created);
|
|
290
|
+
if (step.backfill !== undefined) {
|
|
291
|
+
await sql.unsafe(prepared.sql, [null, null]);
|
|
292
|
+
} else if (created === undefined) {
|
|
293
|
+
await sql.unsafe(prepared.sql);
|
|
294
|
+
await assertSchemaUsageLanded(connection, prepared.sql);
|
|
295
|
+
} else if (!existed) {
|
|
296
|
+
await sql.unsafe(createRoleOnce(prepared.sql.replace(/;\s*$/, "")));
|
|
297
|
+
}
|
|
298
|
+
if (created !== undefined && created === migrationRole) {
|
|
299
|
+
if (!existed) {
|
|
300
|
+
await grantSchema(connection, scratch, created);
|
|
301
|
+
await connection.execute(`grant select, insert, update on okm_meta, okm_history, okm_backfill to ${quoteIdent(created)}`);
|
|
302
|
+
}
|
|
303
|
+
await assumeRole(connection, created, session);
|
|
304
|
+
}
|
|
305
|
+
} catch (error) {
|
|
306
|
+
throw failStep(migration.id, index, error);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function prepareCheckSql(sql) {
|
|
311
|
+
const parts = sqlStatements(sql);
|
|
312
|
+
const source = parts.length === 0 ? [sql] : parts;
|
|
313
|
+
const kept = [];
|
|
314
|
+
const skipped = [];
|
|
315
|
+
for (const statement of source) {
|
|
316
|
+
const kind = skipKind(statement);
|
|
317
|
+
if (kind !== undefined) {
|
|
318
|
+
skipped.push(kind);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
kept.push(rewriteConcurrent(statement));
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
sql: kept.length === 0 ? "" : `${kept.join(`;
|
|
325
|
+
`)};`,
|
|
326
|
+
...skipped.length > 0 ? { skip: skipped.join(", ") } : {}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function skipKind(sql) {
|
|
330
|
+
const text = sql.trim().replace(/\s+/g, " ").toLowerCase();
|
|
331
|
+
if (text.startsWith("vacuum"))
|
|
332
|
+
return "VACUUM is not run inside the check transaction";
|
|
333
|
+
if (/^(?:create|drop)\s+database\b/.test(text)) {
|
|
334
|
+
return "CREATE or DROP DATABASE is not run inside the check transaction";
|
|
335
|
+
}
|
|
336
|
+
if (text.startsWith("alter system"))
|
|
337
|
+
return "ALTER SYSTEM is not run inside the check transaction";
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
function rewriteConcurrent(sql) {
|
|
341
|
+
const text = sql.trim().replace(/\s+/g, " ").toLowerCase();
|
|
342
|
+
if (/^(?:create\s+(?:unique\s+)?index|drop\s+index|reindex|refresh\s+materialized\s+view)\b/.test(text)) {
|
|
343
|
+
return sql.replace(/\s+concurrently\b/gi, "");
|
|
344
|
+
}
|
|
345
|
+
return sql;
|
|
346
|
+
}
|
|
347
|
+
async function ownScratch(sql, scratch, role) {
|
|
348
|
+
if (role === undefined)
|
|
349
|
+
return;
|
|
350
|
+
const exists = await sql.unsafe("select exists (select 1 from pg_roles where rolname = $1) as ok", [role]);
|
|
351
|
+
const present = cell(exists[0], "ok");
|
|
352
|
+
if (present === "t" || present === "true") {
|
|
353
|
+
await sql.unsafe(`alter schema ${quoteIdent(scratch)} owner to ${quoteIdent(role)}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async function setLocalTimeout(sql, name, ms) {
|
|
357
|
+
if (ms === undefined)
|
|
358
|
+
return;
|
|
359
|
+
await sql.unsafe("select set_config($1, $2, true)", [name, String(ms)]);
|
|
360
|
+
}
|
|
361
|
+
async function readXid(sql) {
|
|
362
|
+
const rows = await sql.unsafe("select pg_current_xact_id()::text as xid");
|
|
363
|
+
return cell(rows[0], "xid") ?? "";
|
|
364
|
+
}
|
|
365
|
+
function cell(row, key) {
|
|
366
|
+
if (typeof row !== "object" || row === null || !(key in row))
|
|
367
|
+
return;
|
|
368
|
+
const value = Reflect.get(row, key);
|
|
369
|
+
if (typeof value === "string")
|
|
370
|
+
return value;
|
|
371
|
+
if (typeof value === "boolean")
|
|
372
|
+
return value ? "true" : "false";
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
function xidChanged() {
|
|
376
|
+
return new OkmError2("invalid", "okm migrate check left its transaction. A step committed, so the replay did not stay in one transaction.", {
|
|
377
|
+
kind: "invalid",
|
|
378
|
+
fix: {
|
|
379
|
+
summary: "Remove COMMIT, ROLLBACK, and other transaction control from the step. The check always rolls the replay back."
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
function failStep(id, index, error) {
|
|
384
|
+
const lost = sessionFailure(error);
|
|
385
|
+
if (lost !== undefined) {
|
|
386
|
+
return new OkmError2(lost.code, `Migration ${id} failed at step ${String(index + 1)}: ${lost.message}`, { kind: lost.kind, cause: error, fix: lost.fix });
|
|
387
|
+
}
|
|
388
|
+
if (error instanceof OkmError2)
|
|
389
|
+
return error;
|
|
390
|
+
const message = error instanceof Error ? error.message : "The step failed.";
|
|
391
|
+
return new OkmError2("invalid", `Migration ${id} failed at step ${String(index + 1)}: ${message}`, {
|
|
392
|
+
cause: error,
|
|
393
|
+
fix: { summary: "Fix the step and run apply again. Finished steps are not repeated." }
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
function driverOf(sql) {
|
|
397
|
+
return {
|
|
398
|
+
async execute(text, params) {
|
|
399
|
+
const query = params === undefined || params.length === 0 ? sql.unsafe(text) : sql.unsafe(text, [...params]);
|
|
400
|
+
const rows = await query;
|
|
401
|
+
const wire = Array.from(rows, (row) => cells(row));
|
|
402
|
+
return { rows: wire, count: rows.count ?? wire.length, notices: [] };
|
|
403
|
+
},
|
|
404
|
+
batch() {
|
|
405
|
+
return Promise.reject(new Error("okm migrate check does not batch."));
|
|
406
|
+
},
|
|
407
|
+
release() {
|
|
408
|
+
return Promise.resolve();
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function cells(row) {
|
|
413
|
+
const values = Array.isArray(row) ? row : Object.values(row);
|
|
414
|
+
return values.map((value) => {
|
|
415
|
+
if (value === null || value === undefined)
|
|
416
|
+
return null;
|
|
417
|
+
if (value === true)
|
|
418
|
+
return "t";
|
|
419
|
+
if (value === false)
|
|
420
|
+
return "f";
|
|
421
|
+
if (typeof value === "string")
|
|
422
|
+
return value;
|
|
423
|
+
if (typeof value === "number" || typeof value === "bigint")
|
|
424
|
+
return String(value);
|
|
425
|
+
return null;
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/tooling/migrate/check.ts
|
|
430
|
+
var CHECK_SCHEMA_PREFIX = "okm_check_";
|
|
431
|
+
var HISTORY_CODE = "OKM1547";
|
|
432
|
+
var PREVIOUS_CODE = "OKM1548";
|
|
433
|
+
var STALE_CODE = "OKM1549";
|
|
434
|
+
var HISTORY_FIX = "Regenerate the migration so the stored catalog matches the schema the SQL applies.";
|
|
435
|
+
var FORK_FIX = "Generate the later migration from the previous migration's catalog, not from an older parent.";
|
|
436
|
+
var PREVIOUS_FIX = "Keep every table, column, constraint, and type the previous catalog relies on, or classify the migration as contract.";
|
|
437
|
+
var STALE_FIX = "Run okm generate.";
|
|
438
|
+
async function checkMigrations(cwd, flags, options) {
|
|
439
|
+
const opened = await openProject(cwd);
|
|
440
|
+
const target = selectTarget(opened.config, flags.target);
|
|
441
|
+
const allowPooler = flags.allowPooler || opened.config.allowPooler === true;
|
|
442
|
+
assertDirectConnection(target.url, allowPooler);
|
|
443
|
+
if (target.protected) {
|
|
444
|
+
throw new OkmError2("OKM1850", `okm migrate check writes a scratch schema and refuses protected target ${target.name}. Point it at a throwaway Postgres.`, {
|
|
445
|
+
kind: "forbidden",
|
|
446
|
+
fix: {
|
|
447
|
+
summary: "Point okm migrate check at a throwaway Postgres. --allow-protected does not apply."
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
const directory = projectPath(cwd, opened.config.migrations ?? "migrations");
|
|
452
|
+
const history = readHistory(directory);
|
|
453
|
+
const previous = previousFailures(history);
|
|
454
|
+
const stale = staleFailure(history, opened.built.catalog);
|
|
455
|
+
const lint = lintFailure(directory, history, opened.config.lintFrom);
|
|
456
|
+
if (lint.length > 0)
|
|
457
|
+
throwFailure([...previous, ...stale, ...lint]);
|
|
458
|
+
refuseTransactionControl(history);
|
|
459
|
+
if (options?.provision === true) {
|
|
460
|
+
const equivalence = await proveSnapshot(opened.config, target.url, flags, history);
|
|
461
|
+
if (equivalence !== undefined)
|
|
462
|
+
throwFailure([equivalence]);
|
|
463
|
+
}
|
|
464
|
+
const replayed = await replayHistory(opened.config, target.url, flags, history);
|
|
465
|
+
const failures = [...replayed.failures, ...previous, ...stale, ...lint];
|
|
466
|
+
if (failures.length > 0)
|
|
467
|
+
throwFailure(failures);
|
|
468
|
+
const summary = `ok ${String(history.length)} migrations
|
|
469
|
+
`;
|
|
470
|
+
if (replayed.skips.length === 0)
|
|
471
|
+
return summary;
|
|
472
|
+
return `${summary}${replayed.skips.join(`
|
|
473
|
+
`)}
|
|
474
|
+
`;
|
|
475
|
+
}
|
|
476
|
+
function previousCatalogGaps(before, after) {
|
|
477
|
+
const afterBy = new Map(after.objects.map((object) => [identityKey2(object.identity), object]));
|
|
478
|
+
const gaps = [];
|
|
479
|
+
for (const object of before.objects) {
|
|
480
|
+
if (!checkedKind(object.kind))
|
|
481
|
+
continue;
|
|
482
|
+
const next = afterBy.get(identityKey2(object.identity));
|
|
483
|
+
if (next === undefined || next.kind !== object.kind) {
|
|
484
|
+
gaps.push(`${objectLabel(object)} is missing`);
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (object.kind === "column" && next.kind === "column") {
|
|
488
|
+
gaps.push(...columnGaps(object, next));
|
|
489
|
+
}
|
|
490
|
+
if (object.kind === "type" && next.kind === "type" && !sameType(object.definition, next.definition)) {
|
|
491
|
+
gaps.push(`${objectLabel(object)} changed`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return gaps;
|
|
495
|
+
}
|
|
496
|
+
function recomputedMigrationClass(plan, before, after) {
|
|
497
|
+
if (plan.steps.length === 0)
|
|
498
|
+
return "expand";
|
|
499
|
+
if (plan.steps.some((step) => step.kind === undefined)) {
|
|
500
|
+
return planMigration({ before, after, name: plan.name }).class;
|
|
501
|
+
}
|
|
502
|
+
return strictestClass(plan.steps.map((step) => classOf(step.kind ?? "raw-sql")));
|
|
503
|
+
}
|
|
504
|
+
function migrationAllowsPreviousGaps(plan, before, after) {
|
|
505
|
+
return recomputedMigrationClass(plan, before, after) === "contract" && plan.class !== "expand";
|
|
506
|
+
}
|
|
507
|
+
function previousFailures(history) {
|
|
508
|
+
const failures = [];
|
|
509
|
+
let before = catalog([]);
|
|
510
|
+
for (const migration of history) {
|
|
511
|
+
const gaps = previousCatalogGaps(before, migration.catalog);
|
|
512
|
+
if (gaps.length > 0 && !migrationAllowsPreviousGaps(migration.plan, before, migration.catalog)) {
|
|
513
|
+
const stated = migration.plan.class === "expand" ? "expand" : recomputedMigrationClass(migration.plan, before, migration.catalog);
|
|
514
|
+
failures.push({
|
|
515
|
+
code: PREVIOUS_CODE,
|
|
516
|
+
message: `${migration.id}: previous catalog is not satisfied (${gaps.join("; ")}). The migration is classified ${stated}.`,
|
|
517
|
+
fix: PREVIOUS_FIX
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
before = migration.catalog;
|
|
521
|
+
}
|
|
522
|
+
return failures;
|
|
523
|
+
}
|
|
524
|
+
function staleFailure(history, head) {
|
|
525
|
+
const last = history.at(-1)?.catalog ?? catalog([]);
|
|
526
|
+
if (catalogsEqual(last, head))
|
|
527
|
+
return [];
|
|
528
|
+
return [
|
|
529
|
+
{
|
|
530
|
+
code: STALE_CODE,
|
|
531
|
+
message: "The last migration's catalog does not match the schema.",
|
|
532
|
+
fix: STALE_FIX
|
|
533
|
+
}
|
|
534
|
+
];
|
|
535
|
+
}
|
|
536
|
+
function lintFailure(directory, history, lintFrom) {
|
|
537
|
+
const findings = lintMigrationDirectory(directory, lintIds(history, lintFrom));
|
|
538
|
+
if (!hasError(findings))
|
|
539
|
+
return [];
|
|
540
|
+
const refusal = lintRefusal(findings);
|
|
541
|
+
return [{ code: refusal.code, message: refusal.message, fix: refusal.fix.summary }];
|
|
542
|
+
}
|
|
543
|
+
function lintIds(history, lintFrom) {
|
|
544
|
+
if (lintFrom === undefined)
|
|
545
|
+
return;
|
|
546
|
+
const ids = history.map((migration) => migration.id);
|
|
547
|
+
const start = ids.indexOf(lintFrom);
|
|
548
|
+
if (start < 0) {
|
|
549
|
+
throw new OkmError2("invalid", `lintFrom ${lintFrom} is not a migration id.`, {
|
|
550
|
+
fix: {
|
|
551
|
+
summary: "Set lintFrom to a migration id. Files before that id are the adoption baseline and are not linted."
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
return new Set(ids.slice(start));
|
|
556
|
+
}
|
|
557
|
+
async function proveSnapshot(config, url, flags, history) {
|
|
558
|
+
const provided = `${CHECK_SCHEMA_PREFIX}p${crypto.randomUUID().replaceAll("-", "")}`;
|
|
559
|
+
const replayed = `${CHECK_SCHEMA_PREFIX}r${crypto.randomUUID().replaceAll("-", "")}`;
|
|
560
|
+
const difference = await proveCheckSnapshot(url, provided, replayed, config, flags.allowPooler || config.allowPooler === true, history);
|
|
561
|
+
if (difference === undefined)
|
|
562
|
+
return;
|
|
563
|
+
return {
|
|
564
|
+
code: "OKM1521",
|
|
565
|
+
message: `Provisioning from the snapshot does not match the replayed history.
|
|
566
|
+
${difference}`,
|
|
567
|
+
fix: "Regenerate the snapshot from the history. okm migrate check --provision is the command that reports this."
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
async function replayHistory(config, url, flags, history) {
|
|
571
|
+
if (history.length === 0)
|
|
572
|
+
return { failures: [], skips: [] };
|
|
573
|
+
const scratch = `${CHECK_SCHEMA_PREFIX}${crypto.randomUUID().replaceAll("-", "")}`;
|
|
574
|
+
const outcome = await replayCheckHistory(url, scratch, config, flags.allowPooler || config.allowPooler === true, history);
|
|
575
|
+
if (outcome.stop === undefined)
|
|
576
|
+
return { failures: [], skips: outcome.skips };
|
|
577
|
+
return {
|
|
578
|
+
failures: [historyFailure(history, outcome.stop.index, outcome.stop.back)],
|
|
579
|
+
skips: outcome.skips
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function refuseTransactionControl(history) {
|
|
583
|
+
for (const migration of history) {
|
|
584
|
+
migration.steps.forEach((step, index) => {
|
|
585
|
+
const word = transactionControl(step.sql);
|
|
586
|
+
if (word === undefined)
|
|
587
|
+
return;
|
|
588
|
+
throw new OkmError2("OKM1551", `Migration ${migration.id} step ${String(index + 1)} contains ${word}.`, {
|
|
589
|
+
fix: {
|
|
590
|
+
summary: "Remove transaction control from the step. okm migrate check replays one transaction and rolls it back."
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
function historyFailure(history, index, back) {
|
|
597
|
+
const migration = history[index];
|
|
598
|
+
const id = migration?.id ?? "migration";
|
|
599
|
+
const fork = migration === undefined ? undefined : forkMessage(history, index);
|
|
600
|
+
if (fork !== undefined)
|
|
601
|
+
return { code: HISTORY_CODE, message: fork, fix: FORK_FIX };
|
|
602
|
+
const steps = back.steps.map((step) => step.sql).join(`
|
|
603
|
+
`);
|
|
604
|
+
return {
|
|
605
|
+
code: HISTORY_CODE,
|
|
606
|
+
message: `${id}: history differs from the stored catalog
|
|
607
|
+
${steps}`,
|
|
608
|
+
fix: HISTORY_FIX
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function forkMessage(history, index) {
|
|
612
|
+
const current = history[index];
|
|
613
|
+
const previous = history[index - 1];
|
|
614
|
+
if (current === undefined || previous === undefined)
|
|
615
|
+
return;
|
|
616
|
+
if (samePlan(previous.catalog, current.catalog, current.plan))
|
|
617
|
+
return;
|
|
618
|
+
for (let parentIndex = 0;parentIndex < index; parentIndex += 1) {
|
|
619
|
+
const parent = parentIndex === 0 ? catalog([]) : history[parentIndex - 1]?.catalog;
|
|
620
|
+
if (parent === undefined)
|
|
621
|
+
continue;
|
|
622
|
+
if (!samePlan(parent, current.catalog, current.plan))
|
|
623
|
+
continue;
|
|
624
|
+
return `migration ${current.id} was generated from a different parent than ${previous.id}`;
|
|
625
|
+
}
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
function samePlan(before, after, file) {
|
|
629
|
+
try {
|
|
630
|
+
return sameSql(planMigration({ before, after, name: "parent" }), file);
|
|
631
|
+
} catch (error) {
|
|
632
|
+
if (error instanceof OkmError2 && error.code === "OKM1530")
|
|
633
|
+
return false;
|
|
634
|
+
throw error;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function sameSql(planned, file) {
|
|
638
|
+
if (planned.steps.length !== file.steps.length)
|
|
639
|
+
return false;
|
|
640
|
+
return planned.steps.every((step, index) => step.sql === file.steps[index]?.sql);
|
|
641
|
+
}
|
|
642
|
+
function readHistory(directory) {
|
|
643
|
+
return loadMigrations(directory).map((migration) => ({
|
|
644
|
+
id: migration.id,
|
|
645
|
+
catalogHash: migration.catalogHash,
|
|
646
|
+
steps: migration.steps,
|
|
647
|
+
plan: readMigrationPlan(join(directory, `${migration.id}.sql`)),
|
|
648
|
+
catalog: parseCatalog2(readFileSync(join(directory, `${migration.id}.catalog.json`), "utf8"))
|
|
649
|
+
}));
|
|
650
|
+
}
|
|
651
|
+
function throwFailure(failures) {
|
|
652
|
+
const first = failures[0];
|
|
653
|
+
if (first === undefined)
|
|
654
|
+
throw new Error("okm migrate check has no failure to report.");
|
|
655
|
+
const message = failures.length === 1 ? first.message : failures.map((failure) => `${failure.code} ${failure.message}`).join(`
|
|
656
|
+
`);
|
|
657
|
+
throw new OkmError2(first.code, message, { fix: { summary: first.fix } });
|
|
658
|
+
}
|
|
659
|
+
function checkedKind(kind) {
|
|
660
|
+
return kind === "table" || kind === "column" || kind === "constraint" || kind === "type";
|
|
661
|
+
}
|
|
662
|
+
function columnGaps(before, after) {
|
|
663
|
+
const gaps = [];
|
|
664
|
+
const place = objectLabel(before);
|
|
665
|
+
if (canonicalTypeName(before.definition.dataType) !== canonicalTypeName(after.definition.dataType)) {
|
|
666
|
+
gaps.push(`${place} changed type from ${before.definition.dataType} to ${after.definition.dataType}`);
|
|
667
|
+
}
|
|
668
|
+
if (before.definition.nullable && !after.definition.nullable && !columnHasDefault(after)) {
|
|
669
|
+
gaps.push(`${place} nullability tightened without a default`);
|
|
670
|
+
}
|
|
671
|
+
return gaps;
|
|
672
|
+
}
|
|
673
|
+
function columnHasDefault(column) {
|
|
674
|
+
return column.definition.defaultExpression !== undefined || column.definition.identity !== undefined || column.definition.generated !== undefined;
|
|
675
|
+
}
|
|
676
|
+
function sameType(before, after) {
|
|
677
|
+
const beforeDomain = isDomain(before);
|
|
678
|
+
const afterDomain = isDomain(after);
|
|
679
|
+
if (beforeDomain !== afterDomain)
|
|
680
|
+
return false;
|
|
681
|
+
if (beforeDomain && afterDomain) {
|
|
682
|
+
return canonicalTypeName(before.base) === canonicalTypeName(after.base) && before.check === after.check;
|
|
683
|
+
}
|
|
684
|
+
if (!beforeDomain && !afterDomain) {
|
|
685
|
+
if (before.labels.length !== after.labels.length)
|
|
686
|
+
return false;
|
|
687
|
+
return before.labels.every((label, index) => label === after.labels[index]);
|
|
688
|
+
}
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
function objectLabel(object) {
|
|
692
|
+
if (object.kind === "column" || object.kind === "constraint") {
|
|
693
|
+
return `${object.kind} ${object.identity.parent.name}.${object.identity.name}`;
|
|
694
|
+
}
|
|
695
|
+
if (object.kind === "table" || object.kind === "type") {
|
|
696
|
+
return `${object.kind} ${object.identity.name}`;
|
|
697
|
+
}
|
|
698
|
+
return object.kind;
|
|
699
|
+
}
|
|
700
|
+
export {
|
|
701
|
+
checkMigrations
|
|
702
|
+
};
|