okmodel 0.4.0 → 0.5.1
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 +95 -12
- package/dist/contracts/catalog/document.d.ts +20 -0
- package/dist/contracts/driver.d.ts +7 -0
- package/dist/dialects/pg/filter-parse.d.ts +8 -2
- package/dist/dialects/pg/fn/index.js +8 -0
- package/dist/dialects/pg/index.js +8 -2
- package/dist/dialects/pg/introspect.d.ts +6 -0
- package/dist/dialects/pg/ops/or.d.ts +1 -1
- package/dist/dialects/pg/role/sql.d.ts +10 -0
- package/dist/dialects/pg/schema.d.ts +15 -6
- package/dist/dialects/pg/temporal.d.ts +5 -76
- package/dist/dialects/pg/view/index.d.ts +20 -6
- package/dist/dialects/pg/view/index.js +1 -1
- package/dist/dialects/pg/view/scratch.d.ts +2 -2
- package/dist/runtime/client.d.ts +6 -1
- package/dist/runtime/conflict.d.ts +2 -1
- package/dist/runtime/pg/bun.d.ts +12 -7
- package/dist/runtime/pg/bun.js +7 -4
- package/dist/runtime/pg/pg.d.ts +12 -6
- package/dist/runtime/pg/pg.js +7 -4
- package/dist/runtime/pg/pglite.d.ts +11 -3
- package/dist/runtime/pg/pglite.js +7 -4
- package/dist/runtime/pg/postgresjs.d.ts +12 -6
- package/dist/runtime/pg/postgresjs.js +7 -4
- package/dist/runtime/plan.d.ts +16 -1
- package/dist/runtime/tenancy/index.js +1 -1
- package/dist/runtime/topology.d.ts +191 -0
- package/dist/runtime/traits/index.js +7 -7
- package/dist/runtime/types.d.ts +57 -2
- package/dist/runtime/validate/index.js +3 -3
- package/dist/shared/{radd3z4g.js → 0t22wwh1.js} +5 -9
- package/dist/shared/{ady1ms74.js → 2pqx26h8.js} +25 -10
- package/dist/shared/{kyxb62mc.js → 31vm914h.js} +9 -1
- package/dist/shared/{jns2wx4t.js → 32zqc1dw.js} +18 -7
- package/dist/shared/{nxb42q7h.js → 47x8x9ze.js} +2 -2
- package/dist/shared/{zvbjc949.js → 4az561fn.js} +1 -1
- package/dist/shared/{s6gqfn5j.js → 51mvgj8w.js} +27 -3
- package/dist/shared/{k3ted12y.js → 54tqkxja.js} +20 -2
- package/dist/shared/{3p4b1fg0.js → 5enne4ma.js} +1 -1
- package/dist/shared/{6693j9rs.js → 5fqx9yh4.js} +6 -2
- package/dist/shared/{c5g81ngb.js → 6mnp99hx.js} +11 -8
- package/dist/shared/{hqjsxnz7.js → 6qd27mx0.js} +5 -4
- package/dist/shared/{v7tpnhnz.js → 6rk2gen5.js} +5 -3
- package/dist/shared/{2321avmn.js → 7zwwyh0v.js} +46 -16
- package/dist/shared/{fa9vt245.js → 87jvvhnt.js} +2 -2
- package/dist/shared/{8jb4ymh2.js → 8bwbwy8h.js} +12 -1
- package/dist/shared/{c8aghh05.js → 8rsy3hxz.js} +7 -1
- package/dist/shared/{41kv2xdm.js → 8xsbagad.js} +1 -1
- package/dist/shared/{6p5k20sm.js → 96qcj21c.js} +3 -3
- package/dist/shared/{jgxzjzcz.js → aj2qxyhj.js} +2 -2
- package/dist/shared/{5vdm4sgp.js → caw8b9d8.js} +4 -4
- package/dist/shared/{s4mb6a4b.js → cctw17hj.js} +74 -15
- package/dist/shared/{rrstp3kb.js → cgbamx3e.js} +2 -2
- package/dist/shared/{axhk06q1.js → cr74e96e.js} +1 -1
- package/dist/shared/{b066g80h.js → efmy2gcg.js} +2 -2
- package/dist/shared/{gdh9mrtp.js → f5cw5q5r.js} +2 -2
- package/dist/shared/{xghkt7a9.js → fancm5pt.js} +6 -233
- package/dist/shared/{n5y9zyj1.js → ga3sfy93.js} +369 -14
- package/dist/shared/{xrfnq5k8.js → gbtcc2d7.js} +55 -5
- package/dist/shared/{7nk68fj8.js → gde73es6.js} +20 -15
- package/dist/shared/{kkzwp3d2.js → hf22e0wt.js} +55 -19
- package/dist/shared/{tny2t3sm.js → janx09m5.js} +8 -7
- package/dist/shared/{4ef7mmqf.js → jwtxdj06.js} +5 -5
- package/dist/shared/{sf8m6tfy.js → k8v5v5wj.js} +3 -3
- package/dist/shared/{qn0srwwq.js → kfjp0gck.js} +5 -3
- package/dist/shared/{wqem4jed.js → krxx77g0.js} +7 -7
- package/dist/shared/{edwqqryd.js → nkaxvt9q.js} +1 -1
- package/dist/shared/{jsd07prz.js → ntneex2a.js} +1 -1
- package/dist/shared/nxdp2ze3.js +1197 -0
- package/dist/shared/{tbszx8mz.js → pf0d8f77.js} +1 -1
- package/dist/shared/{84ptn35q.js → ps7a6h14.js} +10 -9
- package/dist/shared/{kj680nxv.js → q58y17hy.js} +9 -7
- package/dist/shared/{awrsqrn7.js → qm7cdtvr.js} +22 -9
- package/dist/shared/{q6zsyh31.js → qv2ydck9.js} +156 -205
- package/dist/shared/{kafaa5zv.js → r2sp8hrc.js} +28 -8
- package/dist/shared/{fb00y8fp.js → r8x5qhd7.js} +5 -5
- package/dist/shared/{yjw02enp.js → sdc7r2ww.js} +8 -7
- package/dist/shared/sycnx8gt.js +289 -0
- package/dist/shared/{hcmdtwgh.js → szm0cksa.js} +4 -1
- package/dist/shared/{zg24x0hm.js → tm5ab8we.js} +10 -2
- package/dist/shared/{ygem90rn.js → v36c5g6p.js} +85 -21
- package/dist/shared/wq9n6pwr.js +139 -0
- package/dist/shared/{7p2wbz7f.js → x72jb7ad.js} +1 -1
- package/dist/shared/{q795afwa.js → z2rnngj4.js} +3 -3
- package/dist/shared/{sr5ms7b3.js → zqrnhwsq.js} +3 -3
- package/dist/tooling/cli.js +70 -23
- package/dist/tooling/migrate/apply.d.ts +2 -0
- package/dist/tooling/migrate/commands.d.ts +1 -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 +7 -0
- package/dist/tooling/migrate/index.js +12 -9
- package/dist/tooling/migrate/lint.d.ts +3 -3
- package/dist/tooling/migrate/plan.d.ts +7 -3
- package/dist/tooling/migrate/policy.d.ts +11 -0
- package/dist/tooling/migrate/sql-lex.d.ts +22 -0
- package/dist/tooling/testing/factories.d.ts +10 -0
- package/dist/tooling/testing/facts.d.ts +3 -0
- package/dist/tooling/testing/index.js +3 -3
- package/dist/tooling/testing/isolation.d.ts +4 -3
- package/package.json +6 -5
- package/dist/shared/0v1g424p.js +0 -26
|
@@ -8,21 +8,27 @@ import {
|
|
|
8
8
|
defaultPrivilegeObject,
|
|
9
9
|
serializeCatalog2,
|
|
10
10
|
catalogHash2,
|
|
11
|
+
startupCatalog2,
|
|
11
12
|
parseCatalog2
|
|
12
|
-
} from "./
|
|
13
|
+
} from "./8bwbwy8h.js";
|
|
13
14
|
import {
|
|
14
15
|
open2
|
|
15
|
-
} from "./
|
|
16
|
+
} from "./krxx77g0.js";
|
|
16
17
|
import {
|
|
17
18
|
canonicalTypeName,
|
|
18
19
|
batchSizeField,
|
|
19
20
|
aboutBatchCount,
|
|
21
|
+
sqlStatements,
|
|
20
22
|
parseReplace,
|
|
21
23
|
planMigration,
|
|
22
24
|
formatPlan,
|
|
23
25
|
parsePlan,
|
|
24
|
-
staleRenames
|
|
25
|
-
|
|
26
|
+
staleRenames,
|
|
27
|
+
assertTargetPolicy,
|
|
28
|
+
assertTargetAlias,
|
|
29
|
+
listTargets,
|
|
30
|
+
selectTarget
|
|
31
|
+
} from "./ga3sfy93.js";
|
|
26
32
|
import {
|
|
27
33
|
contributedFields
|
|
28
34
|
} from "./n0dgs0pb.js";
|
|
@@ -37,7 +43,7 @@ import {
|
|
|
37
43
|
import {
|
|
38
44
|
snakeCase,
|
|
39
45
|
emittedTypeName
|
|
40
|
-
} from "./
|
|
46
|
+
} from "./ntneex2a.js";
|
|
41
47
|
import {
|
|
42
48
|
ColumnBuilder2
|
|
43
49
|
} from "./c9z5g0fm.js";
|
|
@@ -149,8 +155,8 @@ var ERROR_DOCS = [
|
|
|
149
155
|
{
|
|
150
156
|
code: "OKM1102",
|
|
151
157
|
title: "Unfiltered write",
|
|
152
|
-
summary: "
|
|
153
|
-
fix: "Pass a
|
|
158
|
+
summary: "A write with no effective predicate, including undefined values, is OKM1102.",
|
|
159
|
+
fix: "Pass a predicate, or .all(reason). An undefined-only where is the same as {}."
|
|
154
160
|
},
|
|
155
161
|
{
|
|
156
162
|
code: "OKM1104",
|
|
@@ -179,14 +185,14 @@ var ERROR_DOCS = [
|
|
|
179
185
|
{
|
|
180
186
|
code: "OKM1120",
|
|
181
187
|
title: "Unknown field",
|
|
182
|
-
summary: "
|
|
183
|
-
fix: "Use a field from the catalog. The error includes a nearest-name hint when one is close."
|
|
188
|
+
summary: "The name is not a field or an option. timeout and signal go in the second argument of update. Options go first: tx(options, fn).",
|
|
189
|
+
fix: "Use a field from the catalog, or move the option. The error includes a nearest-name hint when one is close."
|
|
184
190
|
},
|
|
185
191
|
{
|
|
186
192
|
code: "OKM1121",
|
|
187
193
|
title: "Object as value",
|
|
188
|
-
summary: "A plain object was passed where a value is required.
|
|
189
|
-
fix: "
|
|
194
|
+
summary: "An empty or() branch is OKM1121. or() takes one array: or([a, b]). A plain object was passed where a value is required.",
|
|
195
|
+
fix: "Give every or() branch a predicate. Pass an array: or([a, b]). Wrap a json value with eq."
|
|
190
196
|
},
|
|
191
197
|
{
|
|
192
198
|
code: "OKM1122",
|
|
@@ -251,8 +257,8 @@ var ERROR_DOCS = [
|
|
|
251
257
|
{
|
|
252
258
|
code: "OKM1510",
|
|
253
259
|
title: "Unsafe migration",
|
|
254
|
-
summary: "A migration has an unresolved lint error, or an override with an empty reason or a code
|
|
255
|
-
fix: "Put `-- okm-allow OKM15xx: reason` on the
|
|
260
|
+
summary: "A migration has an unresolved lint error, or an override with an empty reason or a code no statement in the step triggered.",
|
|
261
|
+
fix: "Put `-- okm-allow OKM15xx: reason` on the step. The code must be one a statement in that step triggered, and the reason must not be empty. The line silences that code on every statement of the step."
|
|
256
262
|
},
|
|
257
263
|
{
|
|
258
264
|
code: "OKM1511",
|
|
@@ -443,8 +449,8 @@ var ERROR_DOCS = [
|
|
|
443
449
|
{
|
|
444
450
|
code: "OKM1542",
|
|
445
451
|
title: "Data statement",
|
|
446
|
-
summary: "A migration contains a data statement outside backfill().",
|
|
447
|
-
fix: "Move
|
|
452
|
+
summary: "A migration contains a data statement outside backfill(), or a DO or CALL whose body is not analysed.",
|
|
453
|
+
fix: "Move insert, update, delete, merge, and truncate into backfill(). DO and CALL are refused because the body is not analysed. Allow OKM1542 with a reason to keep one."
|
|
448
454
|
},
|
|
449
455
|
{
|
|
450
456
|
code: "OKM1543",
|
|
@@ -623,7 +629,7 @@ var ERROR_DOCS = [
|
|
|
623
629
|
{
|
|
624
630
|
code: "OKM1840",
|
|
625
631
|
title: "Replica not allowed",
|
|
626
|
-
summary: "
|
|
632
|
+
summary: "A replica route was used on an operation that requires the primary, or inside tx().",
|
|
627
633
|
fix: "Run the operation on the primary. Writes, batch, locks, and tx() never go to a replica."
|
|
628
634
|
},
|
|
629
635
|
{
|
|
@@ -642,7 +648,7 @@ var ERROR_DOCS = [
|
|
|
642
648
|
code: "OKM1843",
|
|
643
649
|
title: "No eligible replica",
|
|
644
650
|
summary: "A replica was required and none is eligible: unhealthy, behind the session, or none configured.",
|
|
645
|
-
fix: "Configure a replica or drop .
|
|
651
|
+
fix: "Configure a replica or drop the route. This call does not read the primary."
|
|
646
652
|
},
|
|
647
653
|
{
|
|
648
654
|
code: "OKM1844",
|
|
@@ -685,6 +691,12 @@ var ERROR_DOCS = [
|
|
|
685
691
|
title: "Target not named",
|
|
686
692
|
summary: "Several targets are configured and the command was not given --target.",
|
|
687
693
|
fix: "Pass --target <name>. The command does not guess."
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
code: "OKM1854",
|
|
697
|
+
title: "Pooler session",
|
|
698
|
+
summary: "Two statements on the same connection saw different backend pids. A transaction-mode pooler is in the way.",
|
|
699
|
+
fix: "Use a direct connection, or pass --allow-pooler."
|
|
688
700
|
}
|
|
689
701
|
];
|
|
690
702
|
var byCode;
|
|
@@ -739,7 +751,7 @@ var STEP_RULES = [
|
|
|
739
751
|
rule("OKM1536", "locking", "error", "Adding a foreign key without NOT VALID locks the table while it scans.", (input) => hit(foreignKeyWithoutNotValid(input), "adds a foreign key without NOT VALID")),
|
|
740
752
|
rule("OKM1537", "locking", "error", "SET NOT NULL locks the table while it scans for nulls.", (input) => hit(unsafeSetNotNull(input), "sets NOT NULL")),
|
|
741
753
|
rule("OKM1538", "locking", "warning", "A type change that rewrites the table locks it for the rewrite.", (input) => typeChange(input, "rewrite")),
|
|
742
|
-
rule("OKM1542", "data-dependent", "error", "A data statement outside backfill() is not a migration step.", (input) =>
|
|
754
|
+
rule("OKM1542", "data-dependent", "error", "A data statement outside backfill(), or DO or CALL, is not a migration step.", (input) => dataStatement(input.step))
|
|
743
755
|
];
|
|
744
756
|
var COLUMN_RULES = [
|
|
745
757
|
{
|
|
@@ -799,12 +811,18 @@ function normalized(sql) {
|
|
|
799
811
|
return out;
|
|
800
812
|
}
|
|
801
813
|
function dataStatement(step) {
|
|
802
|
-
if (step.backfill !== undefined)
|
|
803
|
-
return false;
|
|
804
814
|
const text = normalized(step.sql);
|
|
805
|
-
if (/^(?:
|
|
806
|
-
return
|
|
807
|
-
|
|
815
|
+
if (/^(?:do|call)\b/.test(text))
|
|
816
|
+
return ["the body is not analysed"];
|
|
817
|
+
if (step.backfill !== undefined)
|
|
818
|
+
return [];
|
|
819
|
+
if (/^(?:insert|update|delete|merge|truncate)\b/.test(text)) {
|
|
820
|
+
return ["changes rows outside backfill()"];
|
|
821
|
+
}
|
|
822
|
+
if (/^with\b/.test(text) && /\b(?:insert|update|delete|merge)\b/.test(text)) {
|
|
823
|
+
return ["changes rows outside backfill()"];
|
|
824
|
+
}
|
|
825
|
+
return [];
|
|
808
826
|
}
|
|
809
827
|
function matches(step, kind, pattern) {
|
|
810
828
|
return pattern.test(normalized(step.sql)) || step.kind === kind;
|
|
@@ -979,9 +997,10 @@ function validates(input, kind) {
|
|
|
979
997
|
return true;
|
|
980
998
|
return false;
|
|
981
999
|
}
|
|
1000
|
+
var TYPE_CHANGE = /^alter table\s+(\S+)\s+alter column\s+(\S+)\s+(?:set data type|type)\b/;
|
|
982
1001
|
function typeChange(input, mode) {
|
|
983
1002
|
const text = normalized(input.step.sql);
|
|
984
|
-
const changing =
|
|
1003
|
+
const changing = TYPE_CHANGE.test(text) || input.step.kind === "set-column-type";
|
|
985
1004
|
if (!changing)
|
|
986
1005
|
return [];
|
|
987
1006
|
const compared = columnTypes(input);
|
|
@@ -1008,7 +1027,7 @@ function typeChange(input, mode) {
|
|
|
1008
1027
|
}
|
|
1009
1028
|
function columnTypes(input) {
|
|
1010
1029
|
const text = normalized(input.step.sql);
|
|
1011
|
-
const match =
|
|
1030
|
+
const match = TYPE_CHANGE.exec(text);
|
|
1012
1031
|
const table = lastName(match?.[1]);
|
|
1013
1032
|
const column = lastName(match?.[2]);
|
|
1014
1033
|
if (table === undefined || column === undefined)
|
|
@@ -1177,34 +1196,76 @@ function lintRefusal(findings) {
|
|
|
1177
1196
|
});
|
|
1178
1197
|
}
|
|
1179
1198
|
function lintStep(step, number, before, after, existingTables, earlier, later) {
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1199
|
+
const texts = statementTexts(step);
|
|
1200
|
+
const multi = texts.length > 1;
|
|
1201
|
+
const earlierStatements = earlier.flatMap(contextSteps);
|
|
1202
|
+
const laterStatements = later.flatMap(contextSteps);
|
|
1203
|
+
const perStatement = [];
|
|
1204
|
+
const triggered = new Set;
|
|
1205
|
+
texts.forEach((sql, index) => {
|
|
1206
|
+
const statement = multi ? withoutKind(step, sql) : withSql(step, sql);
|
|
1207
|
+
const hits = [];
|
|
1208
|
+
for (const rule of STEP_RULES) {
|
|
1209
|
+
for (const reason of rule.check({
|
|
1210
|
+
step: statement,
|
|
1211
|
+
before,
|
|
1212
|
+
after,
|
|
1213
|
+
existingTables,
|
|
1214
|
+
earlier: earlierStatements,
|
|
1215
|
+
later: laterStatements
|
|
1216
|
+
})) {
|
|
1217
|
+
const hit = { code: rule.code, severity: rule.severity, reason };
|
|
1218
|
+
hits.push(hit);
|
|
1219
|
+
triggered.add(hit.code);
|
|
1220
|
+
}
|
|
1184
1221
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1222
|
+
const place = multi ? `step ${String(number)} statement ${String(index + 1)}` : `step ${String(number)}`;
|
|
1223
|
+
perStatement.push({ place, hits });
|
|
1224
|
+
});
|
|
1187
1225
|
const silenced = new Set;
|
|
1188
1226
|
const findings = [];
|
|
1189
|
-
const
|
|
1227
|
+
const stepPlace = `step ${String(number)}`;
|
|
1190
1228
|
for (const allow of step.allows ?? []) {
|
|
1191
1229
|
if (allow.reason.trim().length === 0 || allow.code.length === 0) {
|
|
1192
|
-
findings.push(finding("error", "OKM1510",
|
|
1230
|
+
findings.push(finding("error", "OKM1510", stepPlace, "override needs a reason"));
|
|
1193
1231
|
continue;
|
|
1194
1232
|
}
|
|
1195
1233
|
if (!triggered.has(allow.code)) {
|
|
1196
|
-
findings.push(finding("error", "OKM1510",
|
|
1234
|
+
findings.push(finding("error", "OKM1510", stepPlace, `override ${allow.code} does not match this statement`));
|
|
1197
1235
|
continue;
|
|
1198
1236
|
}
|
|
1199
1237
|
silenced.add(allow.code);
|
|
1200
1238
|
}
|
|
1201
|
-
for (const
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1239
|
+
for (const item of perStatement) {
|
|
1240
|
+
for (const hit of item.hits) {
|
|
1241
|
+
if (silenced.has(hit.code))
|
|
1242
|
+
continue;
|
|
1243
|
+
findings.push(finding(hit.severity, hit.code, item.place, hit.reason));
|
|
1244
|
+
}
|
|
1205
1245
|
}
|
|
1206
1246
|
return findings;
|
|
1207
1247
|
}
|
|
1248
|
+
function statementTexts(step) {
|
|
1249
|
+
const statements = sqlStatements(step.sql);
|
|
1250
|
+
return statements.length === 0 ? [step.sql] : statements;
|
|
1251
|
+
}
|
|
1252
|
+
function contextSteps(step) {
|
|
1253
|
+
const statements = sqlStatements(step.sql);
|
|
1254
|
+
if (statements.length === 0)
|
|
1255
|
+
return [step];
|
|
1256
|
+
if (statements.length === 1 && statements[0] === step.sql)
|
|
1257
|
+
return [step];
|
|
1258
|
+
return statements.map((sql) => withSql(step, sql));
|
|
1259
|
+
}
|
|
1260
|
+
function withSql(step, sql) {
|
|
1261
|
+
if (sql === step.sql)
|
|
1262
|
+
return step;
|
|
1263
|
+
return { ...step, sql };
|
|
1264
|
+
}
|
|
1265
|
+
function withoutKind(step, sql) {
|
|
1266
|
+
const { kind: _kind, ...rest } = step;
|
|
1267
|
+
return { ...rest, sql };
|
|
1268
|
+
}
|
|
1208
1269
|
function finding(severity, code, place, reason) {
|
|
1209
1270
|
const doc = errorDoc(code);
|
|
1210
1271
|
if (doc === undefined)
|
|
@@ -1226,148 +1287,6 @@ function columnPlace(column) {
|
|
|
1226
1287
|
return `${column.identity.parent.name}.${column.identity.name}`;
|
|
1227
1288
|
}
|
|
1228
1289
|
|
|
1229
|
-
// src/tooling/migrate/policy.ts
|
|
1230
|
-
var READ = new Set([
|
|
1231
|
-
"plan",
|
|
1232
|
-
"status",
|
|
1233
|
-
"check",
|
|
1234
|
-
"drift",
|
|
1235
|
-
"verify",
|
|
1236
|
-
"pull",
|
|
1237
|
-
"catalog-export",
|
|
1238
|
-
"inspect"
|
|
1239
|
-
]);
|
|
1240
|
-
var ALLOWED = new Set(["expand", "reference", "provision"]);
|
|
1241
|
-
var FORWARD_ONLY = new Set(["drop", "rollback"]);
|
|
1242
|
-
function assertTargetPolicy(target, operation, allowProtected = false) {
|
|
1243
|
-
if (FORWARD_ONLY.has(operation)) {
|
|
1244
|
-
throw new OkmError2("invalid", "There is no down migration.", {
|
|
1245
|
-
fix: { summary: "Recovery is a new forward migration." }
|
|
1246
|
-
});
|
|
1247
|
-
}
|
|
1248
|
-
if (!target.protected || allowProtected)
|
|
1249
|
-
return;
|
|
1250
|
-
if (READ.has(operation) || ALLOWED.has(operation))
|
|
1251
|
-
return;
|
|
1252
|
-
throw new OkmError2("OKM1850", `${operation} is blocked on protected target ${target.name}.`, {
|
|
1253
|
-
kind: "forbidden",
|
|
1254
|
-
fix: {
|
|
1255
|
-
summary: "Run a read-only command or an expand migration, or pass --allow-protected for this invocation."
|
|
1256
|
-
}
|
|
1257
|
-
});
|
|
1258
|
-
}
|
|
1259
|
-
function assertTargetAlias(targets) {
|
|
1260
|
-
const groups = new Map;
|
|
1261
|
-
for (const target of targets) {
|
|
1262
|
-
const key = `${target.host}
|
|
1263
|
-
${target.port}
|
|
1264
|
-
${target.database}`;
|
|
1265
|
-
const group = groups.get(key);
|
|
1266
|
-
if (group === undefined)
|
|
1267
|
-
groups.set(key, [target]);
|
|
1268
|
-
else
|
|
1269
|
-
group.push(target);
|
|
1270
|
-
}
|
|
1271
|
-
for (const group of groups.values()) {
|
|
1272
|
-
const protectedTarget = group.find((target) => target.protected);
|
|
1273
|
-
const open = group.find((target) => !target.protected);
|
|
1274
|
-
if (protectedTarget !== undefined && open !== undefined) {
|
|
1275
|
-
throw new OkmError2("OKM1852", `${open.name} and ${protectedTarget.name} resolve to the same database and disagree about protection.`, {
|
|
1276
|
-
fix: {
|
|
1277
|
-
summary: "Give those targets the same protection flag. okm check and okm doctor both report this."
|
|
1278
|
-
}
|
|
1279
|
-
});
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
function assertDirectConnection(url, allowPooler) {
|
|
1284
|
-
if (allowPooler)
|
|
1285
|
-
return;
|
|
1286
|
-
const endpoint = endpointOf(url);
|
|
1287
|
-
const parsed = new URL(url);
|
|
1288
|
-
const host = endpoint.host;
|
|
1289
|
-
if (host.includes("pooler") || host.includes("pgbouncer") || host.includes("-pooler") || endpoint.port === "6543" || parsed.searchParams.get("pgbouncer") === "true") {
|
|
1290
|
-
throw new OkmError2("invalid", `Refusing pooler host ${host}. Apply needs a direct connection.`, { fix: { summary: "Use a direct connection, or pass --allow-pooler." } });
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
function listTargets(config) {
|
|
1294
|
-
if (config.database !== undefined && config.targets !== undefined) {
|
|
1295
|
-
throw new OkmError2("invalid", "Set database or targets, not both.", {
|
|
1296
|
-
fix: { summary: "database is the one target named default. Use targets for more than one." }
|
|
1297
|
-
});
|
|
1298
|
-
}
|
|
1299
|
-
const entries = [];
|
|
1300
|
-
if (config.database !== undefined)
|
|
1301
|
-
entries.push(["default", config.database]);
|
|
1302
|
-
if (config.targets !== undefined) {
|
|
1303
|
-
for (const name of Object.keys(config.targets).sort()) {
|
|
1304
|
-
const input = config.targets[name];
|
|
1305
|
-
if (input !== undefined)
|
|
1306
|
-
entries.push([name, input]);
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
return entries.map(([name, input]) => resolveTarget(name, input));
|
|
1310
|
-
}
|
|
1311
|
-
function selectTarget(config, name) {
|
|
1312
|
-
const targets = listTargets(config);
|
|
1313
|
-
if (targets.length === 0) {
|
|
1314
|
-
throw new OkmError2("OKM1845", "No target is configured.", {
|
|
1315
|
-
fix: { summary: "Set database or targets in okmodel.config.ts." }
|
|
1316
|
-
});
|
|
1317
|
-
}
|
|
1318
|
-
if (name === undefined) {
|
|
1319
|
-
if (targets.length === 1) {
|
|
1320
|
-
const only = targets[0];
|
|
1321
|
-
if (only !== undefined)
|
|
1322
|
-
return only;
|
|
1323
|
-
}
|
|
1324
|
-
throw new OkmError2("OKM1853", "Several targets are configured and --target was not passed.", {
|
|
1325
|
-
fix: { summary: "Pass --target <name>. The command does not guess." }
|
|
1326
|
-
});
|
|
1327
|
-
}
|
|
1328
|
-
const found = targets.find((target) => target.name === name);
|
|
1329
|
-
if (found === undefined) {
|
|
1330
|
-
throw new OkmError2("OKM1845", `Target ${name} is not configured.`, {
|
|
1331
|
-
fix: { summary: "Pass a name from targets, or database for the target named default." }
|
|
1332
|
-
});
|
|
1333
|
-
}
|
|
1334
|
-
return found;
|
|
1335
|
-
}
|
|
1336
|
-
function resolveTarget(name, input) {
|
|
1337
|
-
const url = typeof input === "string" ? input : input.url;
|
|
1338
|
-
const protectedTarget = typeof input === "string" ? false : input.protected === true;
|
|
1339
|
-
if (url === undefined || url.length === 0) {
|
|
1340
|
-
throw new OkmError2("OKM1845", `Target ${name} has no url.`, {
|
|
1341
|
-
fix: { summary: "Set the target url. A target is not inferred from the environment." }
|
|
1342
|
-
});
|
|
1343
|
-
}
|
|
1344
|
-
const endpoint = endpointOf(url);
|
|
1345
|
-
return {
|
|
1346
|
-
name,
|
|
1347
|
-
url,
|
|
1348
|
-
protected: protectedTarget,
|
|
1349
|
-
host: endpoint.host,
|
|
1350
|
-
port: endpoint.port,
|
|
1351
|
-
database: endpoint.database
|
|
1352
|
-
};
|
|
1353
|
-
}
|
|
1354
|
-
function endpointOf(url) {
|
|
1355
|
-
let parsed;
|
|
1356
|
-
try {
|
|
1357
|
-
parsed = new URL(url);
|
|
1358
|
-
} catch {
|
|
1359
|
-
throw new OkmError2("OKM1845", "The target URL is not a Postgres URL.", {
|
|
1360
|
-
fix: { summary: "Pass a postgres:// URL for the target." }
|
|
1361
|
-
});
|
|
1362
|
-
}
|
|
1363
|
-
const database = decodeURIComponent(parsed.pathname.replace(/^\//, ""));
|
|
1364
|
-
return {
|
|
1365
|
-
host: parsed.hostname.toLowerCase(),
|
|
1366
|
-
port: parsed.port.length > 0 ? parsed.port : "5432",
|
|
1367
|
-
database
|
|
1368
|
-
};
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
1290
|
// src/tooling/migrate/project.ts
|
|
1372
1291
|
import { existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
1373
1292
|
import { basename, join as join2 } from "node:path";
|
|
@@ -1451,9 +1370,20 @@ function emitRowTypes(source) {
|
|
|
1451
1370
|
lines.push(updates.join(`
|
|
1452
1371
|
`));
|
|
1453
1372
|
lines.push("}");
|
|
1454
|
-
|
|
1373
|
+
const body = `${lines.join(`
|
|
1455
1374
|
`)}
|
|
1456
1375
|
`;
|
|
1376
|
+
const aliases = [];
|
|
1377
|
+
if (body.includes("jsonb<"))
|
|
1378
|
+
aliases.push("export type jsonb<T> = T;");
|
|
1379
|
+
if (body.includes("json<"))
|
|
1380
|
+
aliases.push("export type json<T> = T;");
|
|
1381
|
+
if (aliases.length === 0)
|
|
1382
|
+
return body;
|
|
1383
|
+
return `${aliases.join(`
|
|
1384
|
+
`)}
|
|
1385
|
+
|
|
1386
|
+
${body}`;
|
|
1457
1387
|
}
|
|
1458
1388
|
function propertyName(name) {
|
|
1459
1389
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : JSON.stringify(name);
|
|
@@ -1476,7 +1406,7 @@ function fields(item, kind, source) {
|
|
|
1476
1406
|
const state = builder.state;
|
|
1477
1407
|
if (kind === "row") {
|
|
1478
1408
|
if (!state.hidden) {
|
|
1479
|
-
printed.push(` readonly ${field}: ${valueType(state
|
|
1409
|
+
printed.push(` readonly ${field}: ${valueType(state)};`);
|
|
1480
1410
|
}
|
|
1481
1411
|
continue;
|
|
1482
1412
|
}
|
|
@@ -1485,7 +1415,8 @@ function fields(item, kind, source) {
|
|
|
1485
1415
|
continue;
|
|
1486
1416
|
}
|
|
1487
1417
|
const mode = kind === "update" ? "optional" : written;
|
|
1488
|
-
|
|
1418
|
+
const label = valueType(state);
|
|
1419
|
+
printed.push(mode === "optional" ? ` readonly ${field}?: ${label};` : ` readonly ${field}: ${label};`);
|
|
1489
1420
|
}
|
|
1490
1421
|
return printed.join(`
|
|
1491
1422
|
`);
|
|
@@ -1504,7 +1435,7 @@ function updateMode(state) {
|
|
|
1504
1435
|
return "omit";
|
|
1505
1436
|
return "optional";
|
|
1506
1437
|
}
|
|
1507
|
-
function valueType(state
|
|
1438
|
+
function valueType(state) {
|
|
1508
1439
|
let label = scalarLabel(state);
|
|
1509
1440
|
if (state.dims > 0) {
|
|
1510
1441
|
for (let rank = 0;rank < state.dims; rank += 1) {
|
|
@@ -1514,9 +1445,6 @@ function valueType(state, mode) {
|
|
|
1514
1445
|
if (state.nullable) {
|
|
1515
1446
|
label += " | null";
|
|
1516
1447
|
}
|
|
1517
|
-
if (mode === "optional") {
|
|
1518
|
-
label += " | undefined";
|
|
1519
|
-
}
|
|
1520
1448
|
return label;
|
|
1521
1449
|
}
|
|
1522
1450
|
function scalarLabel(state) {
|
|
@@ -1552,8 +1480,9 @@ function scalarLabel(state) {
|
|
|
1552
1480
|
case "bytea":
|
|
1553
1481
|
return "Uint8Array";
|
|
1554
1482
|
case "json":
|
|
1483
|
+
return "json<unknown>";
|
|
1555
1484
|
case "jsonb":
|
|
1556
|
-
return "unknown";
|
|
1485
|
+
return "jsonb<unknown>";
|
|
1557
1486
|
case "timestamptz":
|
|
1558
1487
|
return "Temporal.Instant";
|
|
1559
1488
|
case "timestamp":
|
|
@@ -1599,7 +1528,7 @@ function baseName(dataType) {
|
|
|
1599
1528
|
// src/dialects/pg/role/index.ts
|
|
1600
1529
|
var PROVENANCE = { origin: "file", name: "roles" };
|
|
1601
1530
|
var RELATION = ["SELECT", "INSERT", "UPDATE", "DELETE"];
|
|
1602
|
-
var
|
|
1531
|
+
var READ = ["SELECT"];
|
|
1603
1532
|
var ROUTINE = ["EXECUTE"];
|
|
1604
1533
|
var SEQUENCE = ["USAGE", "SELECT"];
|
|
1605
1534
|
function attachRoles(source, input) {
|
|
@@ -1701,7 +1630,7 @@ function privilegesFor(kind) {
|
|
|
1701
1630
|
if (kind === "sequence")
|
|
1702
1631
|
return SEQUENCE;
|
|
1703
1632
|
if (kind === "materializedView")
|
|
1704
|
-
return
|
|
1633
|
+
return READ;
|
|
1705
1634
|
if (kind === "namespace")
|
|
1706
1635
|
return [];
|
|
1707
1636
|
return RELATION;
|
|
@@ -1759,20 +1688,40 @@ async function readRowEstimates(url, schema, tables) {
|
|
|
1759
1688
|
await pool.close();
|
|
1760
1689
|
}
|
|
1761
1690
|
}
|
|
1762
|
-
function annotateLock(step, estimates) {
|
|
1691
|
+
function annotateLock(step, estimates, aliases = new Map) {
|
|
1763
1692
|
const pieces = lockPieces(step);
|
|
1764
1693
|
const text = withBatches(step, pieces.map((piece) => {
|
|
1765
1694
|
if (piece.table === undefined)
|
|
1766
1695
|
return piece.mode;
|
|
1767
|
-
return `${piece.mode} on ${piece.table}, ${estimatePhrase(
|
|
1768
|
-
}).join("; "), estimates);
|
|
1696
|
+
return `${piece.mode} on ${piece.table}, ${estimatePhrase(lookupEstimate(piece.table, estimates, aliases))}`;
|
|
1697
|
+
}).join("; "), estimates, aliases);
|
|
1769
1698
|
if (step.safeRewrite === true)
|
|
1770
1699
|
return `${text}; safe rewrite applied`;
|
|
1771
|
-
if (exclusiveOverLarge(pieces, estimates)) {
|
|
1700
|
+
if (exclusiveOverLarge(pieces, estimates, aliases)) {
|
|
1772
1701
|
return `${text}; note: more than ${String(LARGE_TABLE_ROWS)} estimated rows`;
|
|
1773
1702
|
}
|
|
1774
1703
|
return text;
|
|
1775
1704
|
}
|
|
1705
|
+
function renameAliases(steps) {
|
|
1706
|
+
const map = new Map;
|
|
1707
|
+
for (const step of steps) {
|
|
1708
|
+
if (step.kind !== "rename-table")
|
|
1709
|
+
continue;
|
|
1710
|
+
const match = /^alter table "(?:(?:[^"]|"")*)"\."((?:[^"]|"")*)" rename to "((?:[^"]|"")*)"/.exec(step.sql);
|
|
1711
|
+
const from = match?.[1]?.replaceAll('""', '"');
|
|
1712
|
+
const to = match?.[2]?.replaceAll('""', '"');
|
|
1713
|
+
if (from !== undefined && to !== undefined)
|
|
1714
|
+
map.set(to, from);
|
|
1715
|
+
}
|
|
1716
|
+
return map;
|
|
1717
|
+
}
|
|
1718
|
+
function lookupEstimate(table, estimates, aliases) {
|
|
1719
|
+
const direct = estimates.get(table);
|
|
1720
|
+
if (direct !== undefined)
|
|
1721
|
+
return direct;
|
|
1722
|
+
const previous = aliases.get(table);
|
|
1723
|
+
return previous === undefined ? undefined : estimates.get(previous);
|
|
1724
|
+
}
|
|
1776
1725
|
function aboutRows(count) {
|
|
1777
1726
|
const rounded = Math.max(0, Math.round(count));
|
|
1778
1727
|
if (rounded < 1000)
|
|
@@ -1792,12 +1741,12 @@ function aboutRows(count) {
|
|
|
1792
1741
|
}
|
|
1793
1742
|
return `about ${String(rounded)} rows`;
|
|
1794
1743
|
}
|
|
1795
|
-
function withBatches(step, text, estimates) {
|
|
1744
|
+
function withBatches(step, text, estimates, aliases = new Map) {
|
|
1796
1745
|
const batch = step.backfill?.batch;
|
|
1797
1746
|
if (batch === undefined)
|
|
1798
1747
|
return text;
|
|
1799
1748
|
const name = step.tables?.[0];
|
|
1800
|
-
const estimate = name === undefined ? undefined :
|
|
1749
|
+
const estimate = name === undefined ? undefined : lookupEstimate(name, estimates, aliases);
|
|
1801
1750
|
if (estimate === undefined || estimate.kind !== "rows")
|
|
1802
1751
|
return text;
|
|
1803
1752
|
return `${text}, about ${String(aboutBatchCount(estimate.reltuples, batch))} batches`;
|
|
@@ -1859,11 +1808,11 @@ function lockPieces(step) {
|
|
|
1859
1808
|
}
|
|
1860
1809
|
return pieces;
|
|
1861
1810
|
}
|
|
1862
|
-
function exclusiveOverLarge(pieces, estimates) {
|
|
1811
|
+
function exclusiveOverLarge(pieces, estimates, aliases = new Map) {
|
|
1863
1812
|
for (const piece of pieces) {
|
|
1864
1813
|
if (piece.mode !== "ACCESS EXCLUSIVE" || piece.table === undefined)
|
|
1865
1814
|
continue;
|
|
1866
|
-
const estimate =
|
|
1815
|
+
const estimate = lookupEstimate(piece.table, estimates, aliases);
|
|
1867
1816
|
if (estimate === undefined || estimate.kind !== "rows")
|
|
1868
1817
|
continue;
|
|
1869
1818
|
if (estimate.reltuples > LARGE_TABLE_ROWS)
|
|
@@ -1945,8 +1894,9 @@ async function checkProject(cwd, flags) {
|
|
|
1945
1894
|
const target = selectTarget(opened.config, named);
|
|
1946
1895
|
assertTargetPolicy(target, "check", flags?.allowProtected ?? false);
|
|
1947
1896
|
assertTargetPolicy(target, "drift", flags?.allowProtected ?? false);
|
|
1948
|
-
const { assertAuthorDrift } = await import("./
|
|
1949
|
-
|
|
1897
|
+
const { assertAuthorDrift } = await import("./6qd27mx0.js");
|
|
1898
|
+
const allowPooler = flags?.allowPooler === true || opened.config.allowPooler === true;
|
|
1899
|
+
await assertAuthorDrift(target.url, opened.built.catalog, opened.config.roles, allowPooler);
|
|
1950
1900
|
return findings;
|
|
1951
1901
|
}
|
|
1952
1902
|
function unlistedTableFiles(schemaSource, fileNames) {
|
|
@@ -2005,7 +1955,8 @@ async function displayPlan(config, plan, invoke) {
|
|
|
2005
1955
|
} catch {
|
|
2006
1956
|
return offline;
|
|
2007
1957
|
}
|
|
2008
|
-
|
|
1958
|
+
const aliases = renameAliases(plan.steps);
|
|
1959
|
+
return formatPlan(plan, (step) => annotateLock(step, estimates, aliases));
|
|
2009
1960
|
}
|
|
2010
1961
|
function lintOpened(opened) {
|
|
2011
1962
|
const plan = planMigration({
|
|
@@ -2046,9 +1997,9 @@ function withRoles(built, config) {
|
|
|
2046
1997
|
function writeArtifact(cwd, config, built) {
|
|
2047
1998
|
const directory = join2(cwd, config.out ?? ".okm");
|
|
2048
1999
|
mkdirSync(directory, { recursive: true });
|
|
2049
|
-
const
|
|
2050
|
-
writeFileSync(join2(directory, "catalog.json"),
|
|
2051
|
-
writeFileSync(join2(directory, "catalog.hash"), `${catalogHash2(
|
|
2000
|
+
const startup = startupCatalog2(built.catalog);
|
|
2001
|
+
writeFileSync(join2(directory, "catalog.json"), serializeCatalog2(startup));
|
|
2002
|
+
writeFileSync(join2(directory, "catalog.hash"), `${catalogHash2(startup)}
|
|
2052
2003
|
`);
|
|
2053
2004
|
writeFileSync(join2(directory, "types.d.ts"), `${emitRowTypes(built)}${referenceAugmentation(built)}`);
|
|
2054
2005
|
return directory;
|
|
@@ -2189,4 +2140,4 @@ function runtimeImportsValidate(source) {
|
|
|
2189
2140
|
return false;
|
|
2190
2141
|
}
|
|
2191
2142
|
|
|
2192
|
-
export { errorDoc, lintMigrationDirectory, formatFindings, hasError, lintRefusal,
|
|
2143
|
+
export { errorDoc, lintMigrationDirectory, formatFindings, hasError, lintRefusal, buildProject, generateProject, planProject, checkProject, projectHead, openProject, loadBuiltSchema, loadConfig, loadSchema };
|