okmodel 0.5.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 +9 -6
- 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/ops/or.d.ts +1 -1
- package/dist/dialects/pg/temporal.d.ts +5 -76
- package/dist/dialects/pg/view/index.js +1 -1
- package/dist/runtime/client.d.ts +6 -1
- package/dist/runtime/conflict.d.ts +2 -1
- package/dist/runtime/pg/bun.js +5 -5
- package/dist/runtime/pg/pg.js +5 -5
- package/dist/runtime/pg/pglite.js +5 -5
- package/dist/runtime/pg/postgresjs.js +5 -5
- package/dist/runtime/plan.d.ts +13 -0
- package/dist/runtime/tenancy/index.js +1 -1
- package/dist/runtime/traits/index.js +4 -4
- package/dist/runtime/validate/index.js +3 -3
- package/dist/shared/{ckjbyrt9.js → 0t22wwh1.js} +5 -9
- package/dist/shared/{jdgqqx31.js → 2pqx26h8.js} +23 -8
- package/dist/shared/{kyxb62mc.js → 31vm914h.js} +9 -1
- package/dist/shared/{9tgmr1gh.js → 32zqc1dw.js} +18 -7
- package/dist/shared/{47xzb9fc.js → 47x8x9ze.js} +1 -1
- 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/{bzsjtd7c.js → 6mnp99hx.js} +7 -6
- package/dist/shared/{ykv7wh5b.js → 6qd27mx0.js} +2 -2
- package/dist/shared/{h4nqvjkp.js → 6rk2gen5.js} +2 -2
- package/dist/shared/{r73pt594.js → 7zwwyh0v.js} +32 -10
- package/dist/shared/{fa9vt245.js → 87jvvhnt.js} +2 -2
- package/dist/shared/{c8aghh05.js → 8rsy3hxz.js} +7 -1
- package/dist/shared/{6p5k20sm.js → 96qcj21c.js} +3 -3
- package/dist/shared/{xsq7v6wp.js → aj2qxyhj.js} +2 -2
- package/dist/shared/{5vdm4sgp.js → caw8b9d8.js} +4 -4
- package/dist/shared/{xhh1srw3.js → cctw17hj.js} +23 -8
- package/dist/shared/{rrstp3kb.js → cgbamx3e.js} +2 -2
- package/dist/shared/{tq9afp8d.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/{ayz64vzj.js → ga3sfy93.js} +357 -3
- package/dist/shared/{qgvt8nx1.js → gbtcc2d7.js} +44 -3
- package/dist/shared/{kdkxh3kb.js → gde73es6.js} +5 -7
- package/dist/shared/{bt9sfq0z.js → hf22e0wt.js} +51 -17
- package/dist/shared/{990a291j.js → janx09m5.js} +6 -5
- package/dist/shared/{4ef7mmqf.js → jwtxdj06.js} +5 -5
- package/dist/shared/{sf8m6tfy.js → k8v5v5wj.js} +3 -3
- package/dist/shared/{r6pj4fq2.js → kfjp0gck.js} +2 -2
- package/dist/shared/{wqem4jed.js → krxx77g0.js} +7 -7
- package/dist/shared/{h8ne16fq.js → nkaxvt9q.js} +1 -1
- package/dist/shared/{jsd07prz.js → ntneex2a.js} +1 -1
- package/dist/shared/{nkc9sfqb.js → nxdp2ze3.js} +40 -4
- package/dist/shared/{tbszx8mz.js → pf0d8f77.js} +1 -1
- package/dist/shared/{qy9rdq0g.js → ps7a6h14.js} +6 -6
- package/dist/shared/{rasvvzgz.js → q58y17hy.js} +5 -5
- package/dist/shared/{p7pfx624.js → qm7cdtvr.js} +22 -9
- package/dist/shared/{xdpj5kkm.js → qv2ydck9.js} +149 -209
- package/dist/shared/{k7cc9nzk.js → r2sp8hrc.js} +6 -3
- package/dist/shared/{fb00y8fp.js → r8x5qhd7.js} +5 -5
- package/dist/shared/{fyqkk1m0.js → sdc7r2ww.js} +6 -5
- package/dist/shared/{wxn4b7pp.js → sycnx8gt.js} +42 -5
- package/dist/shared/{dxjp14rk.js → tm5ab8we.js} +2 -2
- package/dist/shared/{g516zdsb.js → v36c5g6p.js} +78 -19
- package/dist/shared/wq9n6pwr.js +139 -0
- package/dist/shared/{7p2wbz7f.js → x72jb7ad.js} +1 -1
- package/dist/shared/{922qbgnw.js → zqrnhwsq.js} +3 -3
- package/dist/tooling/cli.js +68 -21
- 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/index.js +8 -7
- 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/index.js +1 -1
- package/dist/tooling/testing/isolation.d.ts +4 -3
- package/package.json +6 -5
- package/dist/shared/0v1g424p.js +0 -26
|
@@ -13,17 +13,22 @@ import {
|
|
|
13
13
|
} from "./8bwbwy8h.js";
|
|
14
14
|
import {
|
|
15
15
|
open2
|
|
16
|
-
} from "./
|
|
16
|
+
} from "./krxx77g0.js";
|
|
17
17
|
import {
|
|
18
18
|
canonicalTypeName,
|
|
19
19
|
batchSizeField,
|
|
20
20
|
aboutBatchCount,
|
|
21
|
+
sqlStatements,
|
|
21
22
|
parseReplace,
|
|
22
23
|
planMigration,
|
|
23
24
|
formatPlan,
|
|
24
25
|
parsePlan,
|
|
25
|
-
staleRenames
|
|
26
|
-
|
|
26
|
+
staleRenames,
|
|
27
|
+
assertTargetPolicy,
|
|
28
|
+
assertTargetAlias,
|
|
29
|
+
listTargets,
|
|
30
|
+
selectTarget
|
|
31
|
+
} from "./ga3sfy93.js";
|
|
27
32
|
import {
|
|
28
33
|
contributedFields
|
|
29
34
|
} from "./n0dgs0pb.js";
|
|
@@ -38,7 +43,7 @@ import {
|
|
|
38
43
|
import {
|
|
39
44
|
snakeCase,
|
|
40
45
|
emittedTypeName
|
|
41
|
-
} from "./
|
|
46
|
+
} from "./ntneex2a.js";
|
|
42
47
|
import {
|
|
43
48
|
ColumnBuilder2
|
|
44
49
|
} from "./c9z5g0fm.js";
|
|
@@ -150,8 +155,8 @@ var ERROR_DOCS = [
|
|
|
150
155
|
{
|
|
151
156
|
code: "OKM1102",
|
|
152
157
|
title: "Unfiltered write",
|
|
153
|
-
summary: "
|
|
154
|
-
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 {}."
|
|
155
160
|
},
|
|
156
161
|
{
|
|
157
162
|
code: "OKM1104",
|
|
@@ -180,14 +185,14 @@ var ERROR_DOCS = [
|
|
|
180
185
|
{
|
|
181
186
|
code: "OKM1120",
|
|
182
187
|
title: "Unknown field",
|
|
183
|
-
summary: "
|
|
184
|
-
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."
|
|
185
190
|
},
|
|
186
191
|
{
|
|
187
192
|
code: "OKM1121",
|
|
188
193
|
title: "Object as value",
|
|
189
|
-
summary: "A plain object was passed where a value is required.
|
|
190
|
-
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."
|
|
191
196
|
},
|
|
192
197
|
{
|
|
193
198
|
code: "OKM1122",
|
|
@@ -252,8 +257,8 @@ var ERROR_DOCS = [
|
|
|
252
257
|
{
|
|
253
258
|
code: "OKM1510",
|
|
254
259
|
title: "Unsafe migration",
|
|
255
|
-
summary: "A migration has an unresolved lint error, or an override with an empty reason or a code
|
|
256
|
-
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."
|
|
257
262
|
},
|
|
258
263
|
{
|
|
259
264
|
code: "OKM1511",
|
|
@@ -444,8 +449,8 @@ var ERROR_DOCS = [
|
|
|
444
449
|
{
|
|
445
450
|
code: "OKM1542",
|
|
446
451
|
title: "Data statement",
|
|
447
|
-
summary: "A migration contains a data statement outside backfill().",
|
|
448
|
-
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."
|
|
449
454
|
},
|
|
450
455
|
{
|
|
451
456
|
code: "OKM1543",
|
|
@@ -686,6 +691,12 @@ var ERROR_DOCS = [
|
|
|
686
691
|
title: "Target not named",
|
|
687
692
|
summary: "Several targets are configured and the command was not given --target.",
|
|
688
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."
|
|
689
700
|
}
|
|
690
701
|
];
|
|
691
702
|
var byCode;
|
|
@@ -740,7 +751,7 @@ var STEP_RULES = [
|
|
|
740
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")),
|
|
741
752
|
rule("OKM1537", "locking", "error", "SET NOT NULL locks the table while it scans for nulls.", (input) => hit(unsafeSetNotNull(input), "sets NOT NULL")),
|
|
742
753
|
rule("OKM1538", "locking", "warning", "A type change that rewrites the table locks it for the rewrite.", (input) => typeChange(input, "rewrite")),
|
|
743
|
-
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))
|
|
744
755
|
];
|
|
745
756
|
var COLUMN_RULES = [
|
|
746
757
|
{
|
|
@@ -800,12 +811,18 @@ function normalized(sql) {
|
|
|
800
811
|
return out;
|
|
801
812
|
}
|
|
802
813
|
function dataStatement(step) {
|
|
803
|
-
if (step.backfill !== undefined)
|
|
804
|
-
return false;
|
|
805
814
|
const text = normalized(step.sql);
|
|
806
|
-
if (/^(?:
|
|
807
|
-
return
|
|
808
|
-
|
|
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 [];
|
|
809
826
|
}
|
|
810
827
|
function matches(step, kind, pattern) {
|
|
811
828
|
return pattern.test(normalized(step.sql)) || step.kind === kind;
|
|
@@ -980,9 +997,10 @@ function validates(input, kind) {
|
|
|
980
997
|
return true;
|
|
981
998
|
return false;
|
|
982
999
|
}
|
|
1000
|
+
var TYPE_CHANGE = /^alter table\s+(\S+)\s+alter column\s+(\S+)\s+(?:set data type|type)\b/;
|
|
983
1001
|
function typeChange(input, mode) {
|
|
984
1002
|
const text = normalized(input.step.sql);
|
|
985
|
-
const changing =
|
|
1003
|
+
const changing = TYPE_CHANGE.test(text) || input.step.kind === "set-column-type";
|
|
986
1004
|
if (!changing)
|
|
987
1005
|
return [];
|
|
988
1006
|
const compared = columnTypes(input);
|
|
@@ -1009,7 +1027,7 @@ function typeChange(input, mode) {
|
|
|
1009
1027
|
}
|
|
1010
1028
|
function columnTypes(input) {
|
|
1011
1029
|
const text = normalized(input.step.sql);
|
|
1012
|
-
const match =
|
|
1030
|
+
const match = TYPE_CHANGE.exec(text);
|
|
1013
1031
|
const table = lastName(match?.[1]);
|
|
1014
1032
|
const column = lastName(match?.[2]);
|
|
1015
1033
|
if (table === undefined || column === undefined)
|
|
@@ -1178,34 +1196,76 @@ function lintRefusal(findings) {
|
|
|
1178
1196
|
});
|
|
1179
1197
|
}
|
|
1180
1198
|
function lintStep(step, number, before, after, existingTables, earlier, later) {
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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
|
+
}
|
|
1185
1221
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1222
|
+
const place = multi ? `step ${String(number)} statement ${String(index + 1)}` : `step ${String(number)}`;
|
|
1223
|
+
perStatement.push({ place, hits });
|
|
1224
|
+
});
|
|
1188
1225
|
const silenced = new Set;
|
|
1189
1226
|
const findings = [];
|
|
1190
|
-
const
|
|
1227
|
+
const stepPlace = `step ${String(number)}`;
|
|
1191
1228
|
for (const allow of step.allows ?? []) {
|
|
1192
1229
|
if (allow.reason.trim().length === 0 || allow.code.length === 0) {
|
|
1193
|
-
findings.push(finding("error", "OKM1510",
|
|
1230
|
+
findings.push(finding("error", "OKM1510", stepPlace, "override needs a reason"));
|
|
1194
1231
|
continue;
|
|
1195
1232
|
}
|
|
1196
1233
|
if (!triggered.has(allow.code)) {
|
|
1197
|
-
findings.push(finding("error", "OKM1510",
|
|
1234
|
+
findings.push(finding("error", "OKM1510", stepPlace, `override ${allow.code} does not match this statement`));
|
|
1198
1235
|
continue;
|
|
1199
1236
|
}
|
|
1200
1237
|
silenced.add(allow.code);
|
|
1201
1238
|
}
|
|
1202
|
-
for (const
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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
|
+
}
|
|
1206
1245
|
}
|
|
1207
1246
|
return findings;
|
|
1208
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
|
+
}
|
|
1209
1269
|
function finding(severity, code, place, reason) {
|
|
1210
1270
|
const doc = errorDoc(code);
|
|
1211
1271
|
if (doc === undefined)
|
|
@@ -1227,158 +1287,6 @@ function columnPlace(column) {
|
|
|
1227
1287
|
return `${column.identity.parent.name}.${column.identity.name}`;
|
|
1228
1288
|
}
|
|
1229
1289
|
|
|
1230
|
-
// src/tooling/migrate/policy.ts
|
|
1231
|
-
var READ = new Set([
|
|
1232
|
-
"plan",
|
|
1233
|
-
"status",
|
|
1234
|
-
"check",
|
|
1235
|
-
"drift",
|
|
1236
|
-
"verify",
|
|
1237
|
-
"pull",
|
|
1238
|
-
"catalog-export",
|
|
1239
|
-
"inspect"
|
|
1240
|
-
]);
|
|
1241
|
-
var ALLOWED = new Set(["expand", "reference", "provision"]);
|
|
1242
|
-
var FORWARD_ONLY = new Set(["drop", "rollback"]);
|
|
1243
|
-
function assertTargetPolicy(target, operation, allowProtected = false) {
|
|
1244
|
-
if (FORWARD_ONLY.has(operation)) {
|
|
1245
|
-
throw new OkmError2("invalid", "There is no down migration.", {
|
|
1246
|
-
fix: { summary: "Recovery is a new forward migration." }
|
|
1247
|
-
});
|
|
1248
|
-
}
|
|
1249
|
-
if (!target.protected || allowProtected)
|
|
1250
|
-
return;
|
|
1251
|
-
if (READ.has(operation) || ALLOWED.has(operation))
|
|
1252
|
-
return;
|
|
1253
|
-
throw new OkmError2("OKM1850", `${operation} is blocked on protected target ${target.name}.`, {
|
|
1254
|
-
kind: "forbidden",
|
|
1255
|
-
fix: {
|
|
1256
|
-
summary: "Run a read-only command or an expand migration, or pass --allow-protected for this invocation."
|
|
1257
|
-
}
|
|
1258
|
-
});
|
|
1259
|
-
}
|
|
1260
|
-
function assertTargetAlias(targets) {
|
|
1261
|
-
const groups = new Map;
|
|
1262
|
-
for (const target of targets) {
|
|
1263
|
-
const key = `${target.host}
|
|
1264
|
-
${target.port}
|
|
1265
|
-
${target.database}`;
|
|
1266
|
-
const group = groups.get(key);
|
|
1267
|
-
if (group === undefined)
|
|
1268
|
-
groups.set(key, [target]);
|
|
1269
|
-
else
|
|
1270
|
-
group.push(target);
|
|
1271
|
-
}
|
|
1272
|
-
for (const group of groups.values()) {
|
|
1273
|
-
const protectedTarget = group.find((target) => target.protected);
|
|
1274
|
-
const open = group.find((target) => !target.protected);
|
|
1275
|
-
if (protectedTarget !== undefined && open !== undefined) {
|
|
1276
|
-
throw new OkmError2("OKM1852", `${open.name} and ${protectedTarget.name} resolve to the same database and disagree about protection.`, {
|
|
1277
|
-
fix: {
|
|
1278
|
-
summary: "Give those targets the same protection flag. okm check and okm doctor both report this."
|
|
1279
|
-
}
|
|
1280
|
-
});
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
function assertDirectConnection(url, allowPooler) {
|
|
1285
|
-
if (allowPooler)
|
|
1286
|
-
return;
|
|
1287
|
-
const endpoint = endpointOf(url);
|
|
1288
|
-
const parsed = new URL(url);
|
|
1289
|
-
const host = endpoint.host;
|
|
1290
|
-
if (host.includes("pooler") || host.includes("pgbouncer") || host.includes("-pooler") || endpoint.port === "6543" || parsed.searchParams.get("pgbouncer") === "true") {
|
|
1291
|
-
throw new OkmError2("invalid", `Refusing pooler host ${host}. Apply needs a direct connection.`, { fix: { summary: "Use a direct connection, or pass --allow-pooler." } });
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
function listTargets(config) {
|
|
1295
|
-
if (config.database !== undefined && config.targets !== undefined) {
|
|
1296
|
-
throw new OkmError2("invalid", "Set database or targets, not both.", {
|
|
1297
|
-
fix: { summary: "database is the one target named default. Use targets for more than one." }
|
|
1298
|
-
});
|
|
1299
|
-
}
|
|
1300
|
-
const entries = [];
|
|
1301
|
-
if (config.database !== undefined)
|
|
1302
|
-
entries.push(["default", config.database]);
|
|
1303
|
-
if (config.targets !== undefined) {
|
|
1304
|
-
for (const name of Object.keys(config.targets).sort()) {
|
|
1305
|
-
const input = config.targets[name];
|
|
1306
|
-
if (input !== undefined)
|
|
1307
|
-
entries.push([name, input]);
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
return entries.map(([name, input]) => resolveTarget(name, input));
|
|
1311
|
-
}
|
|
1312
|
-
function selectTarget(config, name) {
|
|
1313
|
-
const targets = listTargets(config);
|
|
1314
|
-
if (targets.length === 0) {
|
|
1315
|
-
throw new OkmError2("OKM1845", "No target is configured.", {
|
|
1316
|
-
fix: { summary: "Set database or targets in okmodel.config.ts." }
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
if (name === undefined) {
|
|
1320
|
-
if (targets.length === 1) {
|
|
1321
|
-
const only = targets[0];
|
|
1322
|
-
if (only !== undefined)
|
|
1323
|
-
return only;
|
|
1324
|
-
}
|
|
1325
|
-
throw new OkmError2("OKM1853", "Several targets are configured and --target was not passed.", {
|
|
1326
|
-
fix: { summary: "Pass --target <name>. The command does not guess." }
|
|
1327
|
-
});
|
|
1328
|
-
}
|
|
1329
|
-
const found = targets.find((target) => target.name === name);
|
|
1330
|
-
if (found === undefined) {
|
|
1331
|
-
throw new OkmError2("OKM1845", `Target ${name} is not configured.`, {
|
|
1332
|
-
fix: { summary: "Pass a name from targets, or database for the target named default." }
|
|
1333
|
-
});
|
|
1334
|
-
}
|
|
1335
|
-
return found;
|
|
1336
|
-
}
|
|
1337
|
-
function resolveTarget(name, input) {
|
|
1338
|
-
if (typeof input !== "string" && isReplicaTarget(input)) {
|
|
1339
|
-
throw new OkmError2("OKM1845", `Target ${name} is a replica. Migrations run on the primary only.`, {
|
|
1340
|
-
fix: {
|
|
1341
|
-
summary: "Set database or targets to the primary URL. A replica is not a migration target."
|
|
1342
|
-
}
|
|
1343
|
-
});
|
|
1344
|
-
}
|
|
1345
|
-
const url = typeof input === "string" ? input : input.url;
|
|
1346
|
-
const protectedTarget = typeof input === "string" ? false : input.protected === true;
|
|
1347
|
-
if (url === undefined || url.length === 0) {
|
|
1348
|
-
throw new OkmError2("OKM1845", `Target ${name} has no url.`, {
|
|
1349
|
-
fix: { summary: "Set the target url. A target is not inferred from the environment." }
|
|
1350
|
-
});
|
|
1351
|
-
}
|
|
1352
|
-
const endpoint = endpointOf(url);
|
|
1353
|
-
return {
|
|
1354
|
-
name,
|
|
1355
|
-
url,
|
|
1356
|
-
protected: protectedTarget,
|
|
1357
|
-
host: endpoint.host,
|
|
1358
|
-
port: endpoint.port,
|
|
1359
|
-
database: endpoint.database
|
|
1360
|
-
};
|
|
1361
|
-
}
|
|
1362
|
-
function isReplicaTarget(input) {
|
|
1363
|
-
return "primary" in input || "replicas" in input || "weight" in input || "pool" in input;
|
|
1364
|
-
}
|
|
1365
|
-
function endpointOf(url) {
|
|
1366
|
-
let parsed;
|
|
1367
|
-
try {
|
|
1368
|
-
parsed = new URL(url);
|
|
1369
|
-
} catch {
|
|
1370
|
-
throw new OkmError2("OKM1845", "The target URL is not a Postgres URL.", {
|
|
1371
|
-
fix: { summary: "Pass a postgres:// URL for the target." }
|
|
1372
|
-
});
|
|
1373
|
-
}
|
|
1374
|
-
const database = decodeURIComponent(parsed.pathname.replace(/^\//, ""));
|
|
1375
|
-
return {
|
|
1376
|
-
host: parsed.hostname.toLowerCase(),
|
|
1377
|
-
port: parsed.port.length > 0 ? parsed.port : "5432",
|
|
1378
|
-
database
|
|
1379
|
-
};
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
1290
|
// src/tooling/migrate/project.ts
|
|
1383
1291
|
import { existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
1384
1292
|
import { basename, join as join2 } from "node:path";
|
|
@@ -1462,9 +1370,20 @@ function emitRowTypes(source) {
|
|
|
1462
1370
|
lines.push(updates.join(`
|
|
1463
1371
|
`));
|
|
1464
1372
|
lines.push("}");
|
|
1465
|
-
|
|
1373
|
+
const body = `${lines.join(`
|
|
1466
1374
|
`)}
|
|
1467
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}`;
|
|
1468
1387
|
}
|
|
1469
1388
|
function propertyName(name) {
|
|
1470
1389
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : JSON.stringify(name);
|
|
@@ -1487,7 +1406,7 @@ function fields(item, kind, source) {
|
|
|
1487
1406
|
const state = builder.state;
|
|
1488
1407
|
if (kind === "row") {
|
|
1489
1408
|
if (!state.hidden) {
|
|
1490
|
-
printed.push(` readonly ${field}: ${valueType(state
|
|
1409
|
+
printed.push(` readonly ${field}: ${valueType(state)};`);
|
|
1491
1410
|
}
|
|
1492
1411
|
continue;
|
|
1493
1412
|
}
|
|
@@ -1496,7 +1415,8 @@ function fields(item, kind, source) {
|
|
|
1496
1415
|
continue;
|
|
1497
1416
|
}
|
|
1498
1417
|
const mode = kind === "update" ? "optional" : written;
|
|
1499
|
-
|
|
1418
|
+
const label = valueType(state);
|
|
1419
|
+
printed.push(mode === "optional" ? ` readonly ${field}?: ${label};` : ` readonly ${field}: ${label};`);
|
|
1500
1420
|
}
|
|
1501
1421
|
return printed.join(`
|
|
1502
1422
|
`);
|
|
@@ -1515,7 +1435,7 @@ function updateMode(state) {
|
|
|
1515
1435
|
return "omit";
|
|
1516
1436
|
return "optional";
|
|
1517
1437
|
}
|
|
1518
|
-
function valueType(state
|
|
1438
|
+
function valueType(state) {
|
|
1519
1439
|
let label = scalarLabel(state);
|
|
1520
1440
|
if (state.dims > 0) {
|
|
1521
1441
|
for (let rank = 0;rank < state.dims; rank += 1) {
|
|
@@ -1525,9 +1445,6 @@ function valueType(state, mode) {
|
|
|
1525
1445
|
if (state.nullable) {
|
|
1526
1446
|
label += " | null";
|
|
1527
1447
|
}
|
|
1528
|
-
if (mode === "optional") {
|
|
1529
|
-
label += " | undefined";
|
|
1530
|
-
}
|
|
1531
1448
|
return label;
|
|
1532
1449
|
}
|
|
1533
1450
|
function scalarLabel(state) {
|
|
@@ -1563,8 +1480,9 @@ function scalarLabel(state) {
|
|
|
1563
1480
|
case "bytea":
|
|
1564
1481
|
return "Uint8Array";
|
|
1565
1482
|
case "json":
|
|
1483
|
+
return "json<unknown>";
|
|
1566
1484
|
case "jsonb":
|
|
1567
|
-
return "unknown";
|
|
1485
|
+
return "jsonb<unknown>";
|
|
1568
1486
|
case "timestamptz":
|
|
1569
1487
|
return "Temporal.Instant";
|
|
1570
1488
|
case "timestamp":
|
|
@@ -1610,7 +1528,7 @@ function baseName(dataType) {
|
|
|
1610
1528
|
// src/dialects/pg/role/index.ts
|
|
1611
1529
|
var PROVENANCE = { origin: "file", name: "roles" };
|
|
1612
1530
|
var RELATION = ["SELECT", "INSERT", "UPDATE", "DELETE"];
|
|
1613
|
-
var
|
|
1531
|
+
var READ = ["SELECT"];
|
|
1614
1532
|
var ROUTINE = ["EXECUTE"];
|
|
1615
1533
|
var SEQUENCE = ["USAGE", "SELECT"];
|
|
1616
1534
|
function attachRoles(source, input) {
|
|
@@ -1712,7 +1630,7 @@ function privilegesFor(kind) {
|
|
|
1712
1630
|
if (kind === "sequence")
|
|
1713
1631
|
return SEQUENCE;
|
|
1714
1632
|
if (kind === "materializedView")
|
|
1715
|
-
return
|
|
1633
|
+
return READ;
|
|
1716
1634
|
if (kind === "namespace")
|
|
1717
1635
|
return [];
|
|
1718
1636
|
return RELATION;
|
|
@@ -1770,20 +1688,40 @@ async function readRowEstimates(url, schema, tables) {
|
|
|
1770
1688
|
await pool.close();
|
|
1771
1689
|
}
|
|
1772
1690
|
}
|
|
1773
|
-
function annotateLock(step, estimates) {
|
|
1691
|
+
function annotateLock(step, estimates, aliases = new Map) {
|
|
1774
1692
|
const pieces = lockPieces(step);
|
|
1775
1693
|
const text = withBatches(step, pieces.map((piece) => {
|
|
1776
1694
|
if (piece.table === undefined)
|
|
1777
1695
|
return piece.mode;
|
|
1778
|
-
return `${piece.mode} on ${piece.table}, ${estimatePhrase(
|
|
1779
|
-
}).join("; "), estimates);
|
|
1696
|
+
return `${piece.mode} on ${piece.table}, ${estimatePhrase(lookupEstimate(piece.table, estimates, aliases))}`;
|
|
1697
|
+
}).join("; "), estimates, aliases);
|
|
1780
1698
|
if (step.safeRewrite === true)
|
|
1781
1699
|
return `${text}; safe rewrite applied`;
|
|
1782
|
-
if (exclusiveOverLarge(pieces, estimates)) {
|
|
1700
|
+
if (exclusiveOverLarge(pieces, estimates, aliases)) {
|
|
1783
1701
|
return `${text}; note: more than ${String(LARGE_TABLE_ROWS)} estimated rows`;
|
|
1784
1702
|
}
|
|
1785
1703
|
return text;
|
|
1786
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
|
+
}
|
|
1787
1725
|
function aboutRows(count) {
|
|
1788
1726
|
const rounded = Math.max(0, Math.round(count));
|
|
1789
1727
|
if (rounded < 1000)
|
|
@@ -1803,12 +1741,12 @@ function aboutRows(count) {
|
|
|
1803
1741
|
}
|
|
1804
1742
|
return `about ${String(rounded)} rows`;
|
|
1805
1743
|
}
|
|
1806
|
-
function withBatches(step, text, estimates) {
|
|
1744
|
+
function withBatches(step, text, estimates, aliases = new Map) {
|
|
1807
1745
|
const batch = step.backfill?.batch;
|
|
1808
1746
|
if (batch === undefined)
|
|
1809
1747
|
return text;
|
|
1810
1748
|
const name = step.tables?.[0];
|
|
1811
|
-
const estimate = name === undefined ? undefined :
|
|
1749
|
+
const estimate = name === undefined ? undefined : lookupEstimate(name, estimates, aliases);
|
|
1812
1750
|
if (estimate === undefined || estimate.kind !== "rows")
|
|
1813
1751
|
return text;
|
|
1814
1752
|
return `${text}, about ${String(aboutBatchCount(estimate.reltuples, batch))} batches`;
|
|
@@ -1870,11 +1808,11 @@ function lockPieces(step) {
|
|
|
1870
1808
|
}
|
|
1871
1809
|
return pieces;
|
|
1872
1810
|
}
|
|
1873
|
-
function exclusiveOverLarge(pieces, estimates) {
|
|
1811
|
+
function exclusiveOverLarge(pieces, estimates, aliases = new Map) {
|
|
1874
1812
|
for (const piece of pieces) {
|
|
1875
1813
|
if (piece.mode !== "ACCESS EXCLUSIVE" || piece.table === undefined)
|
|
1876
1814
|
continue;
|
|
1877
|
-
const estimate =
|
|
1815
|
+
const estimate = lookupEstimate(piece.table, estimates, aliases);
|
|
1878
1816
|
if (estimate === undefined || estimate.kind !== "rows")
|
|
1879
1817
|
continue;
|
|
1880
1818
|
if (estimate.reltuples > LARGE_TABLE_ROWS)
|
|
@@ -1956,8 +1894,9 @@ async function checkProject(cwd, flags) {
|
|
|
1956
1894
|
const target = selectTarget(opened.config, named);
|
|
1957
1895
|
assertTargetPolicy(target, "check", flags?.allowProtected ?? false);
|
|
1958
1896
|
assertTargetPolicy(target, "drift", flags?.allowProtected ?? false);
|
|
1959
|
-
const { assertAuthorDrift } = await import("./
|
|
1960
|
-
|
|
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);
|
|
1961
1900
|
return findings;
|
|
1962
1901
|
}
|
|
1963
1902
|
function unlistedTableFiles(schemaSource, fileNames) {
|
|
@@ -2016,7 +1955,8 @@ async function displayPlan(config, plan, invoke) {
|
|
|
2016
1955
|
} catch {
|
|
2017
1956
|
return offline;
|
|
2018
1957
|
}
|
|
2019
|
-
|
|
1958
|
+
const aliases = renameAliases(plan.steps);
|
|
1959
|
+
return formatPlan(plan, (step) => annotateLock(step, estimates, aliases));
|
|
2020
1960
|
}
|
|
2021
1961
|
function lintOpened(opened) {
|
|
2022
1962
|
const plan = planMigration({
|
|
@@ -2200,4 +2140,4 @@ function runtimeImportsValidate(source) {
|
|
|
2200
2140
|
return false;
|
|
2201
2141
|
}
|
|
2202
2142
|
|
|
2203
|
-
export { errorDoc, lintMigrationDirectory, formatFindings, hasError, lintRefusal,
|
|
2143
|
+
export { errorDoc, lintMigrationDirectory, formatFindings, hasError, lintRefusal, buildProject, generateProject, planProject, checkProject, projectHead, openProject, loadBuiltSchema, loadConfig, loadSchema };
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isConnectionLost
|
|
3
|
-
} from "./
|
|
3
|
+
} from "./pf0d8f77.js";
|
|
4
4
|
import {
|
|
5
5
|
openClient,
|
|
6
6
|
withHttp,
|
|
7
7
|
settleCall
|
|
8
|
-
} from "./
|
|
8
|
+
} from "./hf22e0wt.js";
|
|
9
9
|
import {
|
|
10
10
|
rejectKeys,
|
|
11
11
|
isRecord,
|
|
12
12
|
fail
|
|
13
|
-
} from "./
|
|
13
|
+
} from "./gbtcc2d7.js";
|
|
14
14
|
import {
|
|
15
15
|
OkmError2
|
|
16
16
|
} from "./52szzakm.js";
|
|
@@ -92,6 +92,9 @@ class Frame {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
async function tx(session, args) {
|
|
95
|
+
if (typeof args[0] === "function" && args.length > 1) {
|
|
96
|
+
fail("OKM1120", "options go first: tx(options, fn).");
|
|
97
|
+
}
|
|
95
98
|
const fn = typeof args[0] === "function" ? args[0] : args[1];
|
|
96
99
|
const given = typeof args[0] === "function" ? {} : args[0] ?? {};
|
|
97
100
|
try {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
send,
|
|
3
3
|
connectClient
|
|
4
|
-
} from "./
|
|
4
|
+
} from "./96qcj21c.js";
|
|
5
5
|
import {
|
|
6
6
|
isConnectionLost
|
|
7
|
-
} from "./
|
|
7
|
+
} from "./pf0d8f77.js";
|
|
8
8
|
import {
|
|
9
9
|
WireSession
|
|
10
|
-
} from "./
|
|
10
|
+
} from "./4az561fn.js";
|
|
11
11
|
import {
|
|
12
12
|
rejectClosed,
|
|
13
13
|
runCall
|
|
14
|
-
} from "./
|
|
14
|
+
} from "./cgbamx3e.js";
|
|
15
15
|
|
|
16
16
|
// src/adapters/pg/nodepostgres-extra.ts
|
|
17
17
|
var RESET_ALL = "RESET ALL";
|
|
@@ -43,7 +43,7 @@ async function checkout(held) {
|
|
|
43
43
|
return {
|
|
44
44
|
execute: (text, params, options) => guard(() => runCall(isClosed(), options, (watch) => session.query(text, params, watch))),
|
|
45
45
|
batch: (statements, options) => guard(() => runCall(isClosed(), options, async (watch) => {
|
|
46
|
-
const { runAtomicBatch } = await import("./
|
|
46
|
+
const { runAtomicBatch } = await import("./efmy2gcg.js");
|
|
47
47
|
return runAtomicBatch(session, statements, watch);
|
|
48
48
|
})),
|
|
49
49
|
cancel() {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import"./8bwbwy8h.js";
|
|
2
2
|
import"./n442h54c.js";
|
|
3
|
-
import"./
|
|
3
|
+
import"./krxx77g0.js";
|
|
4
4
|
import {
|
|
5
|
-
listTargets,
|
|
6
5
|
loadConfig
|
|
7
|
-
} from "./
|
|
8
|
-
import
|
|
6
|
+
} from "./qv2ydck9.js";
|
|
7
|
+
import {
|
|
8
|
+
listTargets
|
|
9
|
+
} from "./ga3sfy93.js";
|
|
9
10
|
import"./8xsbagad.js";
|
|
10
11
|
|
|
11
12
|
// src/tooling/migrate/dev.ts
|
|
@@ -19,7 +20,7 @@ async function devProject(cwd) {
|
|
|
19
20
|
`;
|
|
20
21
|
const directory = join(cwd, config.out ?? ".okm", "dev-db");
|
|
21
22
|
mkdirSync(directory, { recursive: true });
|
|
22
|
-
const { open } = await import("./
|
|
23
|
+
const { open } = await import("./f5cw5q5r.js");
|
|
23
24
|
const pool = await open({ dataDir: directory });
|
|
24
25
|
try {
|
|
25
26
|
await pool.execute("select 1");
|