okmodel 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -6
- package/dist/contracts/catalog/document.d.ts +20 -0
- package/dist/contracts/driver.d.ts +7 -0
- package/dist/dialects/pg/introspect.d.ts +6 -0
- package/dist/dialects/pg/role/sql.d.ts +10 -0
- package/dist/dialects/pg/schema.d.ts +15 -6
- package/dist/dialects/pg/view/index.d.ts +20 -6
- package/dist/dialects/pg/view/scratch.d.ts +2 -2
- package/dist/runtime/pg/bun.d.ts +12 -7
- package/dist/runtime/pg/bun.js +4 -1
- package/dist/runtime/pg/pg.d.ts +12 -6
- package/dist/runtime/pg/pg.js +4 -1
- package/dist/runtime/pg/pglite.d.ts +11 -3
- package/dist/runtime/pg/pglite.js +4 -1
- package/dist/runtime/pg/postgresjs.d.ts +12 -6
- package/dist/runtime/pg/postgresjs.js +4 -1
- package/dist/runtime/plan.d.ts +3 -1
- package/dist/runtime/tenancy/index.js +1 -1
- package/dist/runtime/topology.d.ts +191 -0
- package/dist/runtime/traits/index.js +6 -6
- package/dist/runtime/types.d.ts +57 -2
- package/dist/runtime/validate/index.js +1 -1
- package/dist/shared/{nxb42q7h.js → 47xzb9fc.js} +2 -2
- package/dist/shared/{6693j9rs.js → 5fqx9yh4.js} +6 -2
- package/dist/shared/{8jb4ymh2.js → 8bwbwy8h.js} +12 -1
- package/dist/shared/{41kv2xdm.js → 8xsbagad.js} +1 -1
- package/dist/shared/{sr5ms7b3.js → 922qbgnw.js} +3 -3
- package/dist/shared/{tny2t3sm.js → 990a291j.js} +4 -4
- package/dist/shared/{jns2wx4t.js → 9tgmr1gh.js} +1 -1
- package/dist/shared/{n5y9zyj1.js → ayz64vzj.js} +13 -12
- package/dist/shared/{kkzwp3d2.js → bt9sfq0z.js} +16 -14
- package/dist/shared/{c5g81ngb.js → bzsjtd7c.js} +7 -5
- package/dist/shared/{radd3z4g.js → ckjbyrt9.js} +1 -1
- package/dist/shared/{zg24x0hm.js → dxjp14rk.js} +10 -2
- package/dist/shared/{xghkt7a9.js → fancm5pt.js} +6 -233
- package/dist/shared/{yjw02enp.js → fyqkk1m0.js} +4 -4
- package/dist/shared/{ygem90rn.js → g516zdsb.js} +8 -3
- package/dist/shared/{v7tpnhnz.js → h4nqvjkp.js} +5 -3
- package/dist/shared/{edwqqryd.js → h8ne16fq.js} +1 -1
- package/dist/shared/{ady1ms74.js → jdgqqx31.js} +5 -5
- package/dist/shared/{kafaa5zv.js → k7cc9nzk.js} +24 -7
- package/dist/shared/{7nk68fj8.js → kdkxh3kb.js} +17 -10
- package/dist/shared/nkc9sfqb.js +1161 -0
- package/dist/shared/{awrsqrn7.js → p7pfx624.js} +2 -2
- package/dist/shared/{xrfnq5k8.js → qgvt8nx1.js} +11 -2
- package/dist/shared/{84ptn35q.js → qy9rdq0g.js} +6 -5
- package/dist/shared/{qn0srwwq.js → r6pj4fq2.js} +5 -3
- package/dist/shared/{2321avmn.js → r73pt594.js} +21 -13
- package/dist/shared/{kj680nxv.js → rasvvzgz.js} +8 -6
- package/dist/shared/{hcmdtwgh.js → szm0cksa.js} +4 -1
- package/dist/shared/{axhk06q1.js → tq9afp8d.js} +1 -1
- package/dist/shared/wxn4b7pp.js +252 -0
- package/dist/shared/{q6zsyh31.js → xdpj5kkm.js} +19 -8
- package/dist/shared/{s4mb6a4b.js → xhh1srw3.js} +54 -10
- package/dist/shared/{jgxzjzcz.js → xsq7v6wp.js} +2 -2
- package/dist/shared/{hqjsxnz7.js → ykv7wh5b.js} +5 -4
- package/dist/shared/{q795afwa.js → z2rnngj4.js} +3 -3
- package/dist/tooling/cli.js +12 -12
- package/dist/tooling/migrate/apply.d.ts +2 -0
- package/dist/tooling/migrate/files.d.ts +7 -0
- package/dist/tooling/migrate/index.js +8 -6
- package/dist/tooling/testing/facts.d.ts +3 -0
- package/dist/tooling/testing/index.js +3 -3
- package/package.json +1 -1
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
isRecord,
|
|
35
35
|
registerFailFix,
|
|
36
36
|
fail
|
|
37
|
-
} from "./
|
|
37
|
+
} from "./qgvt8nx1.js";
|
|
38
38
|
import {
|
|
39
39
|
safetyInstalled,
|
|
40
40
|
runSafety
|
|
@@ -179,7 +179,7 @@ async function planInsert(table, input, options, mods, generators, scope, tenanc
|
|
|
179
179
|
keyFields: undefined
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
|
-
const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./
|
|
182
|
+
const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./9tgmr1gh.js");
|
|
183
183
|
const conflict = conflictMod?.readConflict(table, options.onConflict, allow);
|
|
184
184
|
const chunks = chunkRows(rows.map((row) => cellsFor(columns, row)));
|
|
185
185
|
const statements = [];
|
|
@@ -42,12 +42,21 @@ var FIND_OPTIONS = [
|
|
|
42
42
|
"limit",
|
|
43
43
|
"orderBy",
|
|
44
44
|
"select",
|
|
45
|
+
"route",
|
|
45
46
|
"signal",
|
|
46
47
|
"timeout",
|
|
47
48
|
"where"
|
|
48
49
|
];
|
|
49
|
-
var ONE_OPTIONS = [
|
|
50
|
-
|
|
50
|
+
var ONE_OPTIONS = [
|
|
51
|
+
"include",
|
|
52
|
+
"orderBy",
|
|
53
|
+
"route",
|
|
54
|
+
"select",
|
|
55
|
+
"signal",
|
|
56
|
+
"timeout",
|
|
57
|
+
"where"
|
|
58
|
+
];
|
|
59
|
+
var FILTER_OPTIONS = ["route", "signal", "timeout", "where"];
|
|
51
60
|
function readOptionNames(op) {
|
|
52
61
|
if (op === "find")
|
|
53
62
|
return FIND_OPTIONS;
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
open2
|
|
3
3
|
} from "./wqem4jed.js";
|
|
4
|
+
import"./8bwbwy8h.js";
|
|
4
5
|
import"./n442h54c.js";
|
|
5
|
-
import"./8jb4ymh2.js";
|
|
6
6
|
import {
|
|
7
7
|
assertRoleHealth
|
|
8
|
-
} from "./
|
|
8
|
+
} from "./szm0cksa.js";
|
|
9
|
+
import"./fancm5pt.js";
|
|
9
10
|
import {
|
|
10
11
|
errorDoc,
|
|
11
12
|
assertTargetPolicy,
|
|
12
13
|
selectTarget,
|
|
13
14
|
openProject
|
|
14
|
-
} from "./
|
|
15
|
+
} from "./xdpj5kkm.js";
|
|
15
16
|
import {
|
|
16
17
|
omitManagedObjects
|
|
17
|
-
} from "./
|
|
18
|
-
import"./
|
|
18
|
+
} from "./ayz64vzj.js";
|
|
19
|
+
import"./8xsbagad.js";
|
|
19
20
|
import {
|
|
20
21
|
OkmError2
|
|
21
22
|
} from "./52szzakm.js";
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
readHandle,
|
|
3
3
|
readCall,
|
|
4
4
|
attachHttp
|
|
5
|
-
} from "./
|
|
5
|
+
} from "./bt9sfq0z.js";
|
|
6
6
|
import {
|
|
7
7
|
emitWhere,
|
|
8
8
|
emitOrder,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
list,
|
|
16
16
|
isRecord,
|
|
17
17
|
fail
|
|
18
|
-
} from "./
|
|
18
|
+
} from "./qgvt8nx1.js";
|
|
19
19
|
import {
|
|
20
20
|
OkmError2,
|
|
21
21
|
throwNamed2
|
|
@@ -32,6 +32,7 @@ var OPTIONS = [
|
|
|
32
32
|
"max",
|
|
33
33
|
"orderBy",
|
|
34
34
|
"limit",
|
|
35
|
+
"route",
|
|
35
36
|
"signal",
|
|
36
37
|
"timeout"
|
|
37
38
|
];
|
|
@@ -79,7 +80,8 @@ function plan(session, table, options, mods, view) {
|
|
|
79
80
|
orderBy,
|
|
80
81
|
limit: record.limit,
|
|
81
82
|
signal: record.signal,
|
|
82
|
-
timeout: record.timeout
|
|
83
|
+
timeout: record.timeout,
|
|
84
|
+
route: record.route
|
|
83
85
|
}, mods.all, session.scope, session.schema.model[table], session.schema, view);
|
|
84
86
|
const call = { ...base, op: "aggregate", build: statement(spec) };
|
|
85
87
|
return readHandle(session, table, call, mods, (compiled, rows) => decode(compiled, rows));
|
|
@@ -1,40 +1,43 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseCatalog2
|
|
3
|
-
} from "./
|
|
3
|
+
} from "./8bwbwy8h.js";
|
|
4
4
|
import"./wqem4jed.js";
|
|
5
5
|
import"./n442h54c.js";
|
|
6
6
|
import {
|
|
7
7
|
applyTarget2,
|
|
8
8
|
backfillTiming2
|
|
9
|
-
} from "./
|
|
9
|
+
} from "./xhh1srw3.js";
|
|
10
10
|
import {
|
|
11
|
-
introspectSchema,
|
|
12
11
|
sealViews,
|
|
13
12
|
catalogQuery2,
|
|
14
13
|
managedRoleOptions2
|
|
15
|
-
} from "./
|
|
14
|
+
} from "./wxn4b7pp.js";
|
|
16
15
|
import {
|
|
17
16
|
loadMigrations,
|
|
18
17
|
catalogsEqual,
|
|
19
18
|
snapshotDifference
|
|
20
|
-
} from "./
|
|
19
|
+
} from "./kdkxh3kb.js";
|
|
20
|
+
import"./szm0cksa.js";
|
|
21
|
+
import {
|
|
22
|
+
introspectSchema
|
|
23
|
+
} from "./fancm5pt.js";
|
|
21
24
|
import {
|
|
22
25
|
lintMigrationDirectory,
|
|
23
26
|
hasError,
|
|
24
27
|
lintRefusal,
|
|
25
28
|
selectTarget,
|
|
26
29
|
openProject
|
|
27
|
-
} from "./
|
|
30
|
+
} from "./xdpj5kkm.js";
|
|
28
31
|
import {
|
|
29
32
|
canonicalTypeName,
|
|
30
33
|
classOf,
|
|
31
34
|
strictestClass,
|
|
32
35
|
planMigration,
|
|
33
36
|
parsePlan
|
|
34
|
-
} from "./
|
|
37
|
+
} from "./ayz64vzj.js";
|
|
35
38
|
import {
|
|
36
39
|
quoteIdent
|
|
37
|
-
} from "./
|
|
40
|
+
} from "./8xsbagad.js";
|
|
38
41
|
import {
|
|
39
42
|
catalog,
|
|
40
43
|
isDomain
|
|
@@ -225,9 +228,8 @@ async function proveSnapshot(config, url, target, flags, history) {
|
|
|
225
228
|
})),
|
|
226
229
|
...config.roles !== undefined ? { migrationRole: config.roles.migration, schema: replayed } : {}
|
|
227
230
|
});
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
const liveReplayed = await introspectSchema(runner, replayed, "public", managedRoleOptions2(config.roles));
|
|
231
|
+
const liveProvided = await introspectScratch(sql, provided, config);
|
|
232
|
+
const liveReplayed = await introspectScratch(sql, replayed, config);
|
|
231
233
|
const difference = snapshotDifference(liveProvided, liveReplayed);
|
|
232
234
|
if (difference === undefined)
|
|
233
235
|
return;
|
|
@@ -284,7 +286,7 @@ async function replayHistory(config, url, target, flags, history) {
|
|
|
284
286
|
],
|
|
285
287
|
...config.roles !== undefined ? { migrationRole: config.roles.migration, schema: scratch } : {}
|
|
286
288
|
});
|
|
287
|
-
const live = await
|
|
289
|
+
const live = await introspectScratch(sql, scratch, config);
|
|
288
290
|
const sealed = await sealViews(runner, migration.catalog);
|
|
289
291
|
const back = planMigration({ before: live, after: sealed, name: migration.id });
|
|
290
292
|
if (back.steps.length === 0)
|
|
@@ -300,6 +302,10 @@ async function replayHistory(config, url, target, flags, history) {
|
|
|
300
302
|
}
|
|
301
303
|
return failures;
|
|
302
304
|
}
|
|
305
|
+
async function introspectScratch(sql, schema, config) {
|
|
306
|
+
await sql.unsafe(`set search_path to ${quoteIdent(schema)}`);
|
|
307
|
+
return introspectSchema(catalogQuery2(sql), schema, "public", managedRoleOptions2(config.roles));
|
|
308
|
+
}
|
|
303
309
|
function historyFailure(history, index, back) {
|
|
304
310
|
const migration = history[index];
|
|
305
311
|
const id = migration?.id ?? "migration";
|
|
@@ -349,9 +355,11 @@ function sameSql(planned, file) {
|
|
|
349
355
|
function retargetSteps(steps, schema) {
|
|
350
356
|
const from = `${quoteIdent("public")}.`;
|
|
351
357
|
const to = `${quoteIdent(schema)}.`;
|
|
358
|
+
const fromDefaults = `in schema ${quoteIdent("public")} `;
|
|
359
|
+
const toDefaults = `in schema ${quoteIdent(schema)} `;
|
|
352
360
|
return steps.map((step) => ({
|
|
353
361
|
...step,
|
|
354
|
-
sql: step.sql.replaceAll(from, to),
|
|
362
|
+
sql: step.sql.replaceAll(from, to).replaceAll(fromDefaults, toDefaults),
|
|
355
363
|
...step.backfill === undefined ? {} : { backfill: { ...step.backfill, table: step.backfill.table.replaceAll(from, to) } }
|
|
356
364
|
}));
|
|
357
365
|
}
|
|
@@ -3,13 +3,15 @@ import {
|
|
|
3
3
|
applyProject2,
|
|
4
4
|
pushProject2,
|
|
5
5
|
backfillTiming2
|
|
6
|
-
} from "./
|
|
7
|
-
import"./
|
|
8
|
-
import"./
|
|
9
|
-
import"./
|
|
6
|
+
} from "./xhh1srw3.js";
|
|
7
|
+
import"./kdkxh3kb.js";
|
|
8
|
+
import"./szm0cksa.js";
|
|
9
|
+
import"./fancm5pt.js";
|
|
10
|
+
import"./xdpj5kkm.js";
|
|
11
|
+
import"./ayz64vzj.js";
|
|
10
12
|
import"./n442h54c.js";
|
|
11
|
-
import"./
|
|
12
|
-
import"./
|
|
13
|
+
import"./8xsbagad.js";
|
|
14
|
+
import"./8bwbwy8h.js";
|
|
13
15
|
import"./wqem4jed.js";
|
|
14
16
|
export {
|
|
15
17
|
applyProject2 as applyProject,
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FUNCTION_ARG_TYPES
|
|
3
|
+
} from "./fancm5pt.js";
|
|
1
4
|
import {
|
|
2
5
|
OkmError2
|
|
3
6
|
} from "./52szzakm.js";
|
|
@@ -92,7 +95,7 @@ async function functionExists(runner, schema, name, args) {
|
|
|
92
95
|
const result = await runner.execute(`select 1 from pg_proc p
|
|
93
96
|
join pg_namespace n on n.oid = p.pronamespace
|
|
94
97
|
where n.nspname = $1 and p.proname = $2
|
|
95
|
-
and
|
|
98
|
+
and ${FUNCTION_ARG_TYPES} = $3`, [schema, name, args]);
|
|
96
99
|
return result.rows.length > 0;
|
|
97
100
|
}
|
|
98
101
|
function truthy(value) {
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cellText,
|
|
3
|
+
VIEW_DEPENDENCIES,
|
|
4
|
+
viewDependencyEdges,
|
|
5
|
+
PUBLIC,
|
|
6
|
+
introspectSchema
|
|
7
|
+
} from "./fancm5pt.js";
|
|
8
|
+
import {
|
|
9
|
+
planMigration
|
|
10
|
+
} from "./ayz64vzj.js";
|
|
11
|
+
import {
|
|
12
|
+
quoteIdent,
|
|
13
|
+
createObjectSql
|
|
14
|
+
} from "./8xsbagad.js";
|
|
15
|
+
import {
|
|
16
|
+
normaliseViewQuery,
|
|
17
|
+
viewObject,
|
|
18
|
+
materializedViewObject
|
|
19
|
+
} from "./y005b761.js";
|
|
20
|
+
import {
|
|
21
|
+
catalog
|
|
22
|
+
} from "./g7ez901n.js";
|
|
23
|
+
import {
|
|
24
|
+
OkmError2
|
|
25
|
+
} from "./52szzakm.js";
|
|
26
|
+
|
|
27
|
+
// src/tooling/migrate/drift.ts
|
|
28
|
+
import postgres from "postgres";
|
|
29
|
+
|
|
30
|
+
// src/dialects/pg/view/scratch.ts
|
|
31
|
+
var SCRATCH = "okm_view_seal";
|
|
32
|
+
var VIEW_SHAPE = `
|
|
33
|
+
select c.relname as name, c.relkind as kind, pg_get_viewdef(c.oid, true) as query,
|
|
34
|
+
(
|
|
35
|
+
select coalesce(string_agg(
|
|
36
|
+
a.attname || '|' || format_type(a.atttypid, a.atttypmod),
|
|
37
|
+
E'\\n' order by a.attnum
|
|
38
|
+
), '')
|
|
39
|
+
from pg_attribute a
|
|
40
|
+
where a.attrelid = c.oid and a.attnum > 0 and not a.attisdropped
|
|
41
|
+
) as columns
|
|
42
|
+
from pg_class c
|
|
43
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
44
|
+
where n.nspname = $1 and c.relkind in ('v', 'm')
|
|
45
|
+
`;
|
|
46
|
+
async function sealViews(runner, source) {
|
|
47
|
+
const views = source.objects.filter((object) => object.kind === "view" || object.kind === "materializedView");
|
|
48
|
+
if (views.length === 0)
|
|
49
|
+
return source;
|
|
50
|
+
await dropScratch(runner);
|
|
51
|
+
await runner.query(`create schema ${quoteIdent(SCRATCH)}`);
|
|
52
|
+
try {
|
|
53
|
+
await runner.query(`set search_path to ${quoteIdent(SCRATCH)}`);
|
|
54
|
+
await createObjects(runner, source, "type");
|
|
55
|
+
for (const object of source.objects) {
|
|
56
|
+
if (object.kind !== "table")
|
|
57
|
+
continue;
|
|
58
|
+
await runner.query(stubTable(object, source));
|
|
59
|
+
}
|
|
60
|
+
await createObjects(runner, source, "function");
|
|
61
|
+
await createViews(runner, views);
|
|
62
|
+
const shapes = await runner.query(VIEW_SHAPE, [SCRATCH]);
|
|
63
|
+
const deps = await runner.query(VIEW_DEPENDENCIES, [SCRATCH]);
|
|
64
|
+
const edges = viewDependencyEdges(deps, PUBLIC, source.objects);
|
|
65
|
+
const byName = new Map(shapes.map((row) => [cellText(row.name), row]));
|
|
66
|
+
const next = source.objects.map((object) => {
|
|
67
|
+
if (object.kind !== "view" && object.kind !== "materializedView")
|
|
68
|
+
return object;
|
|
69
|
+
const row = byName.get(object.identity.name);
|
|
70
|
+
if (row === undefined) {
|
|
71
|
+
throw new OkmError2("OKM1020", `View ${object.identity.name} was not created in the scratch schema.`, {
|
|
72
|
+
fix: { summary: "The query has to run against the tables in this schema." }
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const columns = readColumns(object, cellText(row.columns));
|
|
76
|
+
const query = normaliseViewQuery(cellText(row.query));
|
|
77
|
+
const dependencies = edges.get(object.identity.name) ?? [];
|
|
78
|
+
if (object.kind === "view") {
|
|
79
|
+
return viewObject({
|
|
80
|
+
namespace: object.identity.namespace,
|
|
81
|
+
name: object.identity.name,
|
|
82
|
+
columns,
|
|
83
|
+
query,
|
|
84
|
+
owner: object.owner,
|
|
85
|
+
provenance: object.provenance,
|
|
86
|
+
dependencies
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return materializedViewObject({
|
|
90
|
+
namespace: object.identity.namespace,
|
|
91
|
+
name: object.identity.name,
|
|
92
|
+
columns,
|
|
93
|
+
query,
|
|
94
|
+
owner: object.owner,
|
|
95
|
+
provenance: object.provenance,
|
|
96
|
+
dependencies,
|
|
97
|
+
...object.definition.refresh !== undefined ? { refresh: object.definition.refresh } : {}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
return catalog(next);
|
|
101
|
+
} finally {
|
|
102
|
+
await runner.query("set search_path to public");
|
|
103
|
+
await dropScratch(runner);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function createObjects(runner, source, kind) {
|
|
107
|
+
for (const object of source.objects) {
|
|
108
|
+
if (object.kind !== kind)
|
|
109
|
+
continue;
|
|
110
|
+
const statement = createObjectSql(object, SCRATCH);
|
|
111
|
+
if (statement !== undefined)
|
|
112
|
+
await runner.query(statement);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function createViews(runner, views) {
|
|
116
|
+
const pending = [...views];
|
|
117
|
+
let last = "";
|
|
118
|
+
for (let pass = 0;pass < views.length && pending.length > 0; pass += 1) {
|
|
119
|
+
const failed = [];
|
|
120
|
+
let created = 0;
|
|
121
|
+
for (const object of pending) {
|
|
122
|
+
const sql = object.kind === "materializedView" ? `create materialized view ${quoteIdent(object.identity.name)} as ${object.definition.query} with no data` : `create view ${quoteIdent(object.identity.name)} as ${object.definition.query}`;
|
|
123
|
+
try {
|
|
124
|
+
await runner.query(sql);
|
|
125
|
+
created += 1;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
last = error instanceof Error ? error.message : String(error);
|
|
128
|
+
failed.push(object);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (created === 0) {
|
|
132
|
+
throw new OkmError2("OKM1020", `View ${failed[0]?.identity.name ?? ""} could not be created. ${last}`, {
|
|
133
|
+
fix: { summary: "The query has to run against the tables in this schema." }
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
pending.length = 0;
|
|
137
|
+
pending.push(...failed);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function stubTable(object, source) {
|
|
141
|
+
const columns = source.objects.filter((item) => item.kind === "column" && item.identity.parent.name === object.identity.name);
|
|
142
|
+
const lines = columns.map((column) => `${quoteIdent(column.identity.name)} ${column.definition.dataType}`);
|
|
143
|
+
return `create table ${quoteIdent(object.identity.name)} (${lines.join(", ")})`;
|
|
144
|
+
}
|
|
145
|
+
function readColumns(object, printed) {
|
|
146
|
+
const live = printed.length === 0 ? [] : printed.split(`
|
|
147
|
+
`);
|
|
148
|
+
const declared = object.definition.columns;
|
|
149
|
+
if (live.length !== declared.length) {
|
|
150
|
+
throw mismatch(object.identity.name, declared.map((column) => column.name).join(", "), printed);
|
|
151
|
+
}
|
|
152
|
+
return declared.map((column, index) => {
|
|
153
|
+
const line = live[index] ?? "";
|
|
154
|
+
const splitAt = line.indexOf("|");
|
|
155
|
+
const name = splitAt < 0 ? line : line.slice(0, splitAt);
|
|
156
|
+
const type = splitAt < 0 ? "" : line.slice(splitAt + 1);
|
|
157
|
+
if (name !== column.name || type !== column.dataType) {
|
|
158
|
+
throw mismatch(object.identity.name, `${column.name}:${column.dataType}`, live[index] ?? "");
|
|
159
|
+
}
|
|
160
|
+
return column;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function mismatch(view, declared, live) {
|
|
164
|
+
return new OkmError2("OKM1020", `View ${view} declares ${declared} and the database has ${live}.`, { fix: { summary: "Declare the columns the query returns, in that order, with those types." } });
|
|
165
|
+
}
|
|
166
|
+
async function dropScratch(runner) {
|
|
167
|
+
const present = await runner.query(`select 1 as ok from pg_namespace where nspname = $1`, [
|
|
168
|
+
SCRATCH
|
|
169
|
+
]);
|
|
170
|
+
if (present.length === 0)
|
|
171
|
+
return;
|
|
172
|
+
const rows = await runner.query(`
|
|
173
|
+
select c.relname as name, c.relkind as kind
|
|
174
|
+
from pg_class c
|
|
175
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
176
|
+
where n.nspname = $1 and c.relkind in ('v', 'm', 'r')
|
|
177
|
+
`, [SCRATCH]);
|
|
178
|
+
const views = rows.filter((row) => cellText(row.kind) === "v" || cellText(row.kind) === "m");
|
|
179
|
+
for (let pass = 0;pass < Math.max(views.length, 1); pass += 1) {
|
|
180
|
+
for (const row of views) {
|
|
181
|
+
const kind = cellText(row.kind) === "m" ? "materialized view" : "view";
|
|
182
|
+
try {
|
|
183
|
+
await runner.query(`drop ${kind} if exists ${quoteIdent(SCRATCH)}.${quoteIdent(cellText(row.name))}`);
|
|
184
|
+
} catch {}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (const row of rows) {
|
|
188
|
+
if (cellText(row.kind) !== "r")
|
|
189
|
+
continue;
|
|
190
|
+
await runner.query(`drop table if exists ${quoteIdent(SCRATCH)}.${quoteIdent(cellText(row.name))}`);
|
|
191
|
+
}
|
|
192
|
+
await runner.query(`drop schema if exists ${quoteIdent(SCRATCH)} cascade`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/tooling/migrate/drift.ts
|
|
196
|
+
async function assertAuthorDrift2(url, author, roles) {
|
|
197
|
+
const sql = postgres(url, {
|
|
198
|
+
max: 1,
|
|
199
|
+
connect_timeout: 5,
|
|
200
|
+
idle_timeout: 1,
|
|
201
|
+
onnotice: () => {}
|
|
202
|
+
});
|
|
203
|
+
try {
|
|
204
|
+
const present = await sql`
|
|
205
|
+
select to_regclass('public.okm_meta')::text as reg
|
|
206
|
+
`;
|
|
207
|
+
if (present[0]?.reg == null)
|
|
208
|
+
return;
|
|
209
|
+
const runner = catalogQuery2(sql);
|
|
210
|
+
const authorOnServer = await sealViews(runner, author);
|
|
211
|
+
const live = await introspectSchema(runner, "public", "public", managedRoleOptions2(roles));
|
|
212
|
+
const plan = planMigration({ before: live, after: authorOnServer, name: "check" });
|
|
213
|
+
if (plan.steps.length === 0)
|
|
214
|
+
return;
|
|
215
|
+
throw new OkmError2("OKM1520", `The database differs from the schema.
|
|
216
|
+
${plan.steps.map((step) => step.sql).join(`
|
|
217
|
+
`)}`, {
|
|
218
|
+
fix: {
|
|
219
|
+
summary: "Change the schema or the database so okm check reports no difference."
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
} finally {
|
|
223
|
+
await sql.end({ timeout: 5 });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function catalogQuery2(sql) {
|
|
227
|
+
return {
|
|
228
|
+
async query(text, params) {
|
|
229
|
+
const rows = await sql.unsafe(text, params === undefined ? undefined : [...params]);
|
|
230
|
+
return rows.map((row) => {
|
|
231
|
+
const copy = {};
|
|
232
|
+
for (const [key, value] of Object.entries(row))
|
|
233
|
+
copy[key] = value;
|
|
234
|
+
return copy;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function managedRoleOptions2(roles) {
|
|
240
|
+
const names = managedRoleNames(roles);
|
|
241
|
+
return names === undefined ? undefined : { managedRoles: names };
|
|
242
|
+
}
|
|
243
|
+
function managedRoleNames(roles) {
|
|
244
|
+
if (roles === undefined)
|
|
245
|
+
return;
|
|
246
|
+
const names = new Set([roles.migration, roles.app]);
|
|
247
|
+
for (const role of roles.managed ?? [])
|
|
248
|
+
names.add(role.name);
|
|
249
|
+
return [...names];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export { sealViews, assertAuthorDrift2, catalogQuery2, managedRoleOptions2 };
|
|
@@ -8,8 +8,9 @@ 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
16
|
} from "./wqem4jed.js";
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
formatPlan,
|
|
23
24
|
parsePlan,
|
|
24
25
|
staleRenames
|
|
25
|
-
} from "./
|
|
26
|
+
} from "./ayz64vzj.js";
|
|
26
27
|
import {
|
|
27
28
|
contributedFields
|
|
28
29
|
} from "./n0dgs0pb.js";
|
|
@@ -623,7 +624,7 @@ var ERROR_DOCS = [
|
|
|
623
624
|
{
|
|
624
625
|
code: "OKM1840",
|
|
625
626
|
title: "Replica not allowed",
|
|
626
|
-
summary: "
|
|
627
|
+
summary: "A replica route was used on an operation that requires the primary, or inside tx().",
|
|
627
628
|
fix: "Run the operation on the primary. Writes, batch, locks, and tx() never go to a replica."
|
|
628
629
|
},
|
|
629
630
|
{
|
|
@@ -642,7 +643,7 @@ var ERROR_DOCS = [
|
|
|
642
643
|
code: "OKM1843",
|
|
643
644
|
title: "No eligible replica",
|
|
644
645
|
summary: "A replica was required and none is eligible: unhealthy, behind the session, or none configured.",
|
|
645
|
-
fix: "Configure a replica or drop .
|
|
646
|
+
fix: "Configure a replica or drop the route. This call does not read the primary."
|
|
646
647
|
},
|
|
647
648
|
{
|
|
648
649
|
code: "OKM1844",
|
|
@@ -1334,6 +1335,13 @@ function selectTarget(config, name) {
|
|
|
1334
1335
|
return found;
|
|
1335
1336
|
}
|
|
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
|
+
}
|
|
1337
1345
|
const url = typeof input === "string" ? input : input.url;
|
|
1338
1346
|
const protectedTarget = typeof input === "string" ? false : input.protected === true;
|
|
1339
1347
|
if (url === undefined || url.length === 0) {
|
|
@@ -1351,6 +1359,9 @@ function resolveTarget(name, input) {
|
|
|
1351
1359
|
database: endpoint.database
|
|
1352
1360
|
};
|
|
1353
1361
|
}
|
|
1362
|
+
function isReplicaTarget(input) {
|
|
1363
|
+
return "primary" in input || "replicas" in input || "weight" in input || "pool" in input;
|
|
1364
|
+
}
|
|
1354
1365
|
function endpointOf(url) {
|
|
1355
1366
|
let parsed;
|
|
1356
1367
|
try {
|
|
@@ -1945,7 +1956,7 @@ async function checkProject(cwd, flags) {
|
|
|
1945
1956
|
const target = selectTarget(opened.config, named);
|
|
1946
1957
|
assertTargetPolicy(target, "check", flags?.allowProtected ?? false);
|
|
1947
1958
|
assertTargetPolicy(target, "drift", flags?.allowProtected ?? false);
|
|
1948
|
-
const { assertAuthorDrift } = await import("./
|
|
1959
|
+
const { assertAuthorDrift } = await import("./ykv7wh5b.js");
|
|
1949
1960
|
await assertAuthorDrift(target.url, opened.built.catalog, opened.config.roles);
|
|
1950
1961
|
return findings;
|
|
1951
1962
|
}
|
|
@@ -2046,9 +2057,9 @@ function withRoles(built, config) {
|
|
|
2046
2057
|
function writeArtifact(cwd, config, built) {
|
|
2047
2058
|
const directory = join2(cwd, config.out ?? ".okm");
|
|
2048
2059
|
mkdirSync(directory, { recursive: true });
|
|
2049
|
-
const
|
|
2050
|
-
writeFileSync(join2(directory, "catalog.json"),
|
|
2051
|
-
writeFileSync(join2(directory, "catalog.hash"), `${catalogHash2(
|
|
2060
|
+
const startup = startupCatalog2(built.catalog);
|
|
2061
|
+
writeFileSync(join2(directory, "catalog.json"), serializeCatalog2(startup));
|
|
2062
|
+
writeFileSync(join2(directory, "catalog.hash"), `${catalogHash2(startup)}
|
|
2052
2063
|
`);
|
|
2053
2064
|
writeFileSync(join2(directory, "types.d.ts"), `${emitRowTypes(built)}${referenceAugmentation(built)}`);
|
|
2054
2065
|
return directory;
|