okmodel 0.8.0 → 0.9.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 +23 -1
- package/dist/adapters/pg/bunsql-extra.d.ts +5 -5
- package/dist/adapters/pg/bunsql.d.ts +6 -2
- package/dist/adapters/pg/nodepostgres.d.ts +16 -1
- package/dist/adapters/pg/postgres-extra.d.ts +3 -17
- package/dist/adapters/pg/postgresjs.d.ts +2 -1
- package/dist/contracts/driver.d.ts +3 -2
- package/dist/contracts/error.d.ts +1 -1
- package/dist/dialects/pg/errors.d.ts +2 -0
- package/dist/dialects/pg/ext/btree-gin.d.ts +26 -0
- package/dist/dialects/pg/ext/btree-gin.js +20 -0
- package/dist/dialects/pg/ext/btree-gist.d.ts +26 -0
- package/dist/dialects/pg/ext/btree-gist.js +20 -0
- package/dist/dialects/pg/ext/call.d.ts +53 -0
- package/dist/dialects/pg/ext/citext.js +1 -1
- package/dist/dialects/pg/ext/feature.d.ts +26 -0
- package/dist/dialects/pg/ext/fuzzystrmatch.d.ts +34 -0
- package/dist/dialects/pg/ext/fuzzystrmatch.js +33 -0
- package/dist/dialects/pg/ext/hstore.d.ts +41 -0
- package/dist/dialects/pg/ext/hstore.js +29 -0
- package/dist/dialects/pg/ext/index.d.ts +56 -0
- package/dist/dialects/pg/ext/index.js +1 -1
- package/dist/dialects/pg/ext/ltree.d.ts +41 -0
- package/dist/dialects/pg/ext/ltree.js +29 -0
- package/dist/dialects/pg/ext/pg-trgm.js +4 -13
- package/dist/dialects/pg/ext/pgcrypto.d.ts +33 -0
- package/dist/dialects/pg/ext/pgcrypto.js +33 -0
- package/dist/dialects/pg/ext/unaccent.d.ts +26 -0
- package/dist/dialects/pg/ext/unaccent.js +21 -0
- package/dist/dialects/pg/ext/using.d.ts +23 -0
- package/dist/dialects/pg/ext/versions.d.ts +36 -0
- package/dist/dialects/pg/external.d.ts +19 -0
- package/dist/dialects/pg/filters.d.ts +2 -2
- package/dist/dialects/pg/fn/index.js +5 -5
- package/dist/dialects/pg/index.d.ts +1 -0
- package/dist/dialects/pg/index.js +19 -14
- package/dist/dialects/pg/introspect.d.ts +12 -0
- package/dist/dialects/pg/view/index.js +7 -7
- package/dist/dialects/pg/view/scratch.d.ts +4 -1
- package/dist/runtime/artifact-file.d.ts +22 -0
- package/dist/runtime/client.d.ts +10 -0
- package/dist/runtime/drift-detail.d.ts +2 -1
- package/dist/runtime/drift.d.ts +3 -10
- package/dist/runtime/param-limit.d.ts +20 -0
- package/dist/runtime/pg/bun.js +4 -4
- package/dist/runtime/pg/neon.js +4 -4
- package/dist/runtime/pg/pg.js +4 -4
- package/dist/runtime/pg/pglite.js +4 -4
- package/dist/runtime/pg/postgresjs.js +11 -11
- package/dist/runtime/server-check.d.ts +36 -6
- package/dist/runtime/tenancy/index.js +1 -1
- package/dist/runtime/traits/index.js +6 -6
- package/dist/runtime/validate/index.js +2 -2
- package/dist/runtime/write.d.ts +1 -1
- package/dist/shared/{4m6hskg8.js → 0j6qe91y.js} +4 -4
- package/dist/shared/{6m78c8ss.js → 1h5h1rf5.js} +1 -1
- package/dist/shared/{4vbf83xx.js → 2vcnpf0n.js} +1 -1
- package/dist/shared/{qd9hhd8f.js → 3ap5hxvj.js} +2 -1
- package/dist/shared/{v1wad97q.js → 3d7a80fq.js} +1 -1
- package/dist/shared/{wcrheftc.js → 3dr444ny.js} +58 -79
- package/dist/shared/{hm5f8jby.js → 3vvpcphd.js} +8 -7
- package/dist/shared/{760ppr76.js → 4a3mmhhe.js} +2 -2
- package/dist/shared/{mkrn359n.js → 4pkz5nvw.js} +222 -39
- package/dist/shared/{agsfb86s.js → 4te76xc5.js} +4 -4
- package/dist/shared/{63f4ksbz.js → 60y4ahmw.js} +1 -1
- package/dist/shared/61bmrmhr.js +35 -0
- package/dist/shared/{bxas03mq.js → 63qjmhnp.js} +100 -17
- package/dist/shared/{g5yc06ka.js → 67w3kn71.js} +1 -1
- package/dist/shared/{jgqj11w4.js → 6f0z3hzh.js} +4 -0
- package/dist/shared/{njga14v4.js → 6vfvsdkt.js} +10 -8
- package/dist/shared/{kj9d8maq.js → 6xqvesa5.js} +1 -1
- package/dist/shared/8gdbty5v.js +665 -0
- package/dist/shared/{pan0370z.js → 8ma6drzr.js} +211 -30
- package/dist/shared/{mnvjvqyp.js → 8p2bgye0.js} +124 -32
- package/dist/shared/{mfedzr74.js → 8qwqrxed.js} +14 -6
- package/dist/shared/{hfqmpasr.js → 8v7vzpgt.js} +11 -8
- package/dist/shared/{7ghwamrf.js → 99qp9vtg.js} +1 -1
- package/dist/shared/{zgex00fd.js → ak61ye1f.js} +5 -5
- package/dist/shared/{dpc2r7rb.js → bdekbvmt.js} +218 -95
- package/dist/shared/{jebgrkeq.js → bxjq6xqv.js} +2 -2
- package/dist/shared/c7dqd3gk.js +1806 -0
- package/dist/shared/ddh72ym5.js +20 -0
- package/dist/shared/e1c55v04.js +10 -0
- package/dist/shared/{mtw6jz76.js → e275q7sd.js} +1 -1
- package/dist/shared/{k86n8vq3.js → ekhq3ytz.js} +1 -1
- package/dist/shared/esy4dyxe.js +17 -0
- package/dist/shared/{gcefca6p.js → f9t9g54z.js} +22 -9
- package/dist/shared/{sjk86yrx.js → fk9vef30.js} +84 -73
- package/dist/shared/fkjne6gx.js +8 -0
- package/dist/shared/{e8zyaqvj.js → fne90dvz.js} +4 -1
- package/dist/shared/{needfe47.js → g89fmz5s.js} +1 -1
- package/dist/shared/{m4xff2a6.js → ghqzdp94.js} +2 -2
- package/dist/shared/{n0jmpgj1.js → gr9sn4za.js} +1 -1
- package/dist/shared/{5mtf6cj3.js → gzgfsspx.js} +5 -2
- package/dist/shared/h9811jjm.js +17 -0
- package/dist/shared/{z581d7ny.js → hgt69zjf.js} +2 -2
- package/dist/shared/{35q0n9tz.js → jvxcp1qk.js} +71 -87
- package/dist/shared/k73e8n9d.js +136 -0
- package/dist/shared/{195pzzzz.js → kzgrcy8s.js} +12 -8
- package/dist/shared/kzgrnckd.js +29 -0
- package/dist/shared/{p3xbdcvt.js → mz48rs74.js} +68 -8
- package/dist/shared/{zbqbmmrp.js → n6je5b28.js} +13 -26
- package/dist/shared/{b6d6p0zc.js → ns35jqz0.js} +75 -49
- package/dist/shared/{n05z9nwz.js → nstwgzn8.js} +1 -1
- package/dist/{tooling/shared/08kemxjd.js → shared/pgsak1yw.js} +1 -1
- package/dist/shared/py2zrrm6.js +451 -0
- package/dist/shared/{9dr2qnx7.js → qqm3ze6q.js} +1 -1
- package/dist/shared/{wvdjznah.js → rcjm0yn7.js} +31 -38
- package/dist/shared/rged92v6.js +17 -0
- package/dist/shared/{jp36k7dp.js → rw4tpcd0.js} +81 -51
- package/dist/shared/{bsek72fg.js → s5qe47a6.js} +3 -0
- package/dist/shared/{1d9q9cv2.js → smxzjz60.js} +2 -2
- package/dist/shared/{wz2a78mp.js → th5rnaxw.js} +1 -1
- package/dist/{tooling/shared/rs1jb051.js → shared/tr64w5p0.js} +1 -1
- package/dist/shared/{14x60fbf.js → tyqapcgk.js} +73 -10
- package/dist/shared/vsna85qf.js +8 -0
- package/dist/shared/{63qtkrca.js → vy47ryqa.js} +55 -5
- package/dist/shared/{jx2m62cs.js → wbdh9qbg.js} +7 -5
- package/dist/shared/{f4jy1j7x.js → wp7y8q3y.js} +946 -105
- package/dist/shared/{16kvjebh.js → xbd9xs6p.js} +7 -26
- package/dist/shared/{bah9nbg3.js → xbvz446k.js} +18 -23
- package/dist/shared/{p5nh3r8q.js → y6tfjk9q.js} +2 -2
- package/dist/shared/{3bb7wsqt.js → ycnft340.js} +1 -1
- package/dist/shared/{z7s6fpxv.js → ymenf259.js} +1 -1
- package/dist/shared/{0wrea3jq.js → ysdw2fg3.js} +10 -2
- package/dist/shared/zecrwdk9.js +61 -0
- package/dist/shared/{3t6kw83m.js → zm2rzqba.js} +2 -1
- package/dist/tooling/cli.js +200 -50
- package/dist/tooling/driver-run.js +109 -343
- package/dist/tooling/github/comment.d.ts +56 -0
- package/dist/tooling/github/post-comment.d.ts +17 -0
- package/dist/tooling/migrate/apply.d.ts +17 -2
- package/dist/tooling/migrate/commands.d.ts +2 -1
- package/dist/tooling/migrate/config.d.ts +13 -0
- package/dist/tooling/migrate/drift.d.ts +12 -0
- package/dist/tooling/migrate/driver-url.d.ts +17 -0
- package/dist/tooling/migrate/export.d.ts +165 -0
- package/dist/tooling/migrate/ext-surface.d.ts +80 -0
- package/dist/tooling/migrate/ext.d.ts +31 -4
- package/dist/tooling/migrate/import-drizzle.d.ts +40 -0
- package/dist/tooling/migrate/index.d.ts +3 -0
- package/dist/tooling/migrate/index.js +57 -10
- package/dist/tooling/migrate/lint-rules.d.ts +18 -1
- package/dist/tooling/migrate/lint.d.ts +14 -1
- package/dist/tooling/migrate/place.d.ts +18 -0
- package/dist/tooling/migrate/plan-json.d.ts +65 -0
- package/dist/tooling/migrate/policy.d.ts +17 -5
- package/dist/tooling/migrate/pooler-probe.d.ts +23 -0
- package/dist/tooling/migrate/project.d.ts +10 -1
- package/dist/tooling/migrate/pull-emit.d.ts +98 -0
- package/dist/tooling/migrate/pull-routines.d.ts +126 -0
- package/dist/tooling/migrate/pull-verify.d.ts +26 -0
- package/dist/tooling/migrate/pull.d.ts +65 -0
- package/dist/tooling/migrate/reprint.d.ts +32 -0
- package/dist/tooling/migrate/runner.d.ts +24 -1
- package/dist/tooling/migrate/sandbox.d.ts +8 -2
- package/dist/tooling/migrate/schema-api.d.ts +43 -0
- package/dist/tooling/migrate/sql-lex.d.ts +18 -0
- package/dist/tooling/shared/{ppnnnvj8.js → 09a6vsys.js} +1 -1
- package/dist/tooling/shared/{7ae7rjmn.js → 0v9ptnxp.js} +8 -8
- package/dist/tooling/shared/{fb9k2qwd.js → 0wtzps9p.js} +11 -30
- package/dist/tooling/shared/{a6pp9d12.js → 0yhf1db4.js} +6 -6
- package/dist/tooling/shared/{dy672fz6.js → 19msgrsc.js} +2 -2
- package/dist/{shared/q0cqt2sp.js → tooling/shared/1fwfm5m3.js} +1 -1
- package/dist/tooling/shared/{f9mphxej.js → 2jdn09e2.js} +401 -24
- package/dist/tooling/shared/{jne7b93p.js → 2qrte2ct.js} +5 -5
- package/dist/tooling/shared/{5bvw2nev.js → 2v4nwnkb.js} +1 -1
- package/dist/tooling/shared/{spk1jg3r.js → 33z39trp.js} +106 -23
- package/dist/tooling/shared/{z8d7ptht.js → 353vnw9z.js} +5 -5
- package/dist/tooling/shared/{dw08f3m8.js → 390kry9g.js} +2 -2
- package/dist/tooling/shared/{xjs6zqtc.js → 3f6etb9z.js} +18 -31
- package/dist/{shared/bnby0esn.js → tooling/shared/3t2cqmsx.js} +1 -1
- package/dist/tooling/shared/{wwnekq5n.js → 4dzvjtpy.js} +1 -1
- package/dist/tooling/shared/{y5656f0e.js → 4ec0nxvw.js} +10 -10
- package/dist/tooling/shared/{3m1hkg7z.js → 4tg2bn41.js} +59 -78
- package/dist/tooling/shared/{zvnhh1n0.js → 5rah55z0.js} +2 -2
- package/dist/tooling/shared/{pqy21k24.js → 804w1t69.js} +4 -4
- package/dist/tooling/shared/8g0m0rvd.js +134 -0
- package/dist/tooling/shared/8msbtw5s.js +8 -0
- package/dist/tooling/shared/{4d2w25vw.js → 8t22h3h7.js} +2 -2
- package/dist/tooling/shared/{smn6kz1p.js → 91c59b7b.js} +2 -2
- package/dist/tooling/shared/{aftb2mwr.js → adk9k6va.js} +1 -1
- package/dist/tooling/shared/{j9t5s12a.js → ajjd5cwz.js} +2 -2
- package/dist/tooling/shared/{cmptmb4f.js → angjck01.js} +1 -1
- package/dist/tooling/shared/{yw30vnq2.js → asnnc3v5.js} +1 -1
- package/dist/tooling/shared/{ma2340rx.js → bj1kyqc8.js} +2 -2
- package/dist/tooling/shared/{2wtvc7rw.js → bt3tjemb.js} +11 -3
- package/dist/tooling/shared/{ec71zqgt.js → chk2bh4f.js} +2 -2
- package/dist/tooling/shared/{gmcf9cxf.js → ckq4z5ax.js} +5 -2
- package/dist/tooling/shared/{w7qe8sj1.js → d3rhb24q.js} +1 -1
- package/dist/tooling/shared/{y031jct9.js → d8q00vxg.js} +4 -4
- package/dist/tooling/shared/{vs0t6qt8.js → dx188rxs.js} +2 -2
- package/dist/tooling/shared/{g9ad4n80.js → efq6p9df.js} +35 -42
- package/dist/tooling/shared/{ddjtfgnx.js → en6rwzq0.js} +3 -3
- package/dist/tooling/shared/{6nyfyw70.js → f2r5g0fs.js} +2 -2
- package/dist/tooling/shared/{03k6res6.js → f4gep0nk.js} +1 -1
- package/dist/tooling/shared/{gfvtj6d0.js → f8a010fm.js} +13 -11
- package/dist/tooling/shared/{3tq8pnb8.js → fdsek6pa.js} +1 -1
- package/dist/tooling/shared/fvmrxsd1.js +8 -0
- package/dist/tooling/shared/{733a1tq3.js → g3xybeqf.js} +4 -3
- package/dist/tooling/shared/{pwsytxm5.js → gefmpwsa.js} +1 -1
- package/dist/tooling/shared/{mtrqn2hn.js → gjj71hfh.js} +25 -30
- package/dist/tooling/shared/{8sqrsw3s.js → jgzt69pa.js} +5 -5
- package/dist/tooling/shared/{nf7jmhfa.js → jm30k2ca.js} +10 -8
- package/dist/tooling/shared/{vx86qjv8.js → kayqzpbk.js} +6 -3
- package/dist/tooling/shared/{zhwnfyb2.js → kvw1672d.js} +1 -1
- package/dist/tooling/shared/{rq5kk8j6.js → mr1kcgpw.js} +3 -3
- package/dist/tooling/shared/{72fyg4zv.js → mwbdf409.js} +1 -1
- package/dist/tooling/shared/{wt7j7nf6.js → ns9207v3.js} +1 -1
- package/dist/tooling/shared/{9xzm7hjg.js → p6tjv97k.js} +3 -3
- package/dist/tooling/shared/{yfr8hzhm.js → p73evwf0.js} +1 -1
- package/dist/tooling/shared/{dzx85q70.js → phc1nc4g.js} +1 -1
- package/dist/tooling/shared/{417zdsk7.js → ppybjw50.js} +79 -16
- package/dist/tooling/shared/{yk9mp7vb.js → qdrn8ghn.js} +5 -4
- package/dist/tooling/shared/rd8h01ta.js +20 -0
- package/dist/tooling/shared/{bz2dqhab.js → rwg8y90m.js} +3 -3
- package/dist/tooling/shared/{xhrnjch9.js → ry2f3w8p.js} +3 -3
- package/dist/tooling/shared/{a7q7t4n3.js → ryc5sa58.js} +8 -8
- package/dist/tooling/shared/{2revefqt.js → ss87qxwv.js} +2 -2
- package/dist/tooling/shared/{rf83gr2d.js → vg68k11z.js} +7 -3
- package/dist/tooling/shared/{p7f08ktn.js → vms9dta5.js} +73 -89
- package/dist/tooling/shared/{3rrw5w8f.js → xchpnbdv.js} +1 -1
- package/dist/tooling/shared/{djydjpqt.js → xn6p01fh.js} +5 -5
- package/dist/tooling/shared/{9kazs5p6.js → xwr6q618.js} +3 -3
- package/dist/tooling/shared/{ep8zh3bk.js → xx3gmq94.js} +1 -1
- package/dist/tooling/shared/{tn8gay14.js → y2bd7yfx.js} +21 -13
- package/dist/tooling/shared/{vraj6d74.js → y4gq9rbe.js} +11 -11
- package/dist/tooling/shared/y4vwft92.js +9 -0
- package/dist/tooling/shared/{345tbc2k.js → yz68xcmc.js} +1 -1
- package/dist/tooling/testing/factories.d.ts +1 -3
- package/dist/tooling/testing/index.js +2 -2
- package/package.json +39 -9
- package/dist/shared/3449rcae.js +0 -14
- package/dist/shared/fjpnhqan.js +0 -71
- package/dist/shared/n41w4fbz.js +0 -113
- package/dist/tooling/shared/pw8fgahx.js +0 -69
- /package/dist/shared/{ae4j5f42.js → mfvx6kjx.js} +0 -0
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
VALIDATION_NOT_IMPORTED2,
|
|
3
|
-
VALIDATION_NOT_IMPORTED_FIX2
|
|
4
|
-
} from "./n442h54c.js";
|
|
5
1
|
import {
|
|
6
2
|
roleObject,
|
|
7
3
|
grantObject,
|
|
@@ -9,13 +5,22 @@ import {
|
|
|
9
5
|
serializeCatalog2,
|
|
10
6
|
catalogHash2,
|
|
11
7
|
startupCatalog2,
|
|
8
|
+
startupHash2,
|
|
12
9
|
catalogFileText2,
|
|
13
10
|
parseCatalog2
|
|
14
11
|
} from "./12z7aznj.js";
|
|
12
|
+
import {
|
|
13
|
+
quoteIdent2
|
|
14
|
+
} from "./fk9vef30.js";
|
|
15
|
+
import {
|
|
16
|
+
VALIDATION_NOT_IMPORTED2,
|
|
17
|
+
VALIDATION_NOT_IMPORTED_FIX2
|
|
18
|
+
} from "./n442h54c.js";
|
|
15
19
|
import {
|
|
16
20
|
open2
|
|
17
|
-
} from "./
|
|
21
|
+
} from "./jvxcp1qk.js";
|
|
18
22
|
import {
|
|
23
|
+
driverUrl,
|
|
19
24
|
sqlStatements,
|
|
20
25
|
transactionControl,
|
|
21
26
|
canonicalTypeName,
|
|
@@ -35,7 +40,7 @@ import {
|
|
|
35
40
|
assertTargetAlias,
|
|
36
41
|
listTargets,
|
|
37
42
|
selectTarget
|
|
38
|
-
} from "./
|
|
43
|
+
} from "./bdekbvmt.js";
|
|
39
44
|
import {
|
|
40
45
|
contributedFields
|
|
41
46
|
} from "./n0dgs0pb.js";
|
|
@@ -54,7 +59,7 @@ import {
|
|
|
54
59
|
import {
|
|
55
60
|
snakeCase,
|
|
56
61
|
emittedTypeName
|
|
57
|
-
} from "./
|
|
62
|
+
} from "./f9t9g54z.js";
|
|
58
63
|
import {
|
|
59
64
|
ColumnBuilder2
|
|
60
65
|
} from "./606g0nbf.js";
|
|
@@ -78,6 +83,379 @@ function projectPath(cwd, ...parts) {
|
|
|
78
83
|
return resolve(cwd, ...parts);
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
// src/tooling/migrate/export.ts
|
|
87
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
88
|
+
import { isAbsolute, join } from "node:path";
|
|
89
|
+
var EXPORT_FORMAT = 1;
|
|
90
|
+
var LOGICAL_FORMAT = 1;
|
|
91
|
+
function snapshotFromCatalog(source, namespace, database) {
|
|
92
|
+
return {
|
|
93
|
+
format: EXPORT_FORMAT,
|
|
94
|
+
logical: { namespace, name: namespace, format: LOGICAL_FORMAT },
|
|
95
|
+
catalogHash: startupHash2(source),
|
|
96
|
+
...database !== undefined ? { database } : {},
|
|
97
|
+
tables: exportTables(source)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function serializeSnapshot(snapshot) {
|
|
101
|
+
return `${JSON.stringify(snapshot, null, 2)}
|
|
102
|
+
`;
|
|
103
|
+
}
|
|
104
|
+
function parseSnapshot(path, text) {
|
|
105
|
+
let parsed;
|
|
106
|
+
try {
|
|
107
|
+
parsed = JSON.parse(text);
|
|
108
|
+
} catch {
|
|
109
|
+
throw snapshotError(path, "stale", `Catalog snapshot ${path} is not JSON.`);
|
|
110
|
+
}
|
|
111
|
+
if (!isSnapshot(parsed)) {
|
|
112
|
+
throw snapshotError(path, "stale", `Catalog snapshot ${path} is stale: the file format is not ${String(EXPORT_FORMAT)}.`);
|
|
113
|
+
}
|
|
114
|
+
return parsed;
|
|
115
|
+
}
|
|
116
|
+
function databaseName(url) {
|
|
117
|
+
try {
|
|
118
|
+
const name = decodeURIComponent(new URL(url).pathname.replace(/^\//, ""));
|
|
119
|
+
return name.length > 0 ? name : undefined;
|
|
120
|
+
} catch {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function catalogNamespace(config) {
|
|
125
|
+
return config.namespace ?? "public";
|
|
126
|
+
}
|
|
127
|
+
function loadSnapshots(cwd, config) {
|
|
128
|
+
const paths = config.external ?? [];
|
|
129
|
+
return paths.map((path) => {
|
|
130
|
+
const file = resolveSnapshot(cwd, path);
|
|
131
|
+
if (!existsSync(file)) {
|
|
132
|
+
throw snapshotError(path, "missing", `Catalog snapshot ${path} is missing.`);
|
|
133
|
+
}
|
|
134
|
+
return { path, snapshot: parseSnapshot(path, readFileSync(file, "utf8")) };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function externalNamespaceMap(snapshots) {
|
|
138
|
+
const names = new Map;
|
|
139
|
+
for (const loaded of snapshots) {
|
|
140
|
+
for (const table of loaded.snapshot.tables)
|
|
141
|
+
names.set(table.name, table.namespace);
|
|
142
|
+
}
|
|
143
|
+
return names;
|
|
144
|
+
}
|
|
145
|
+
function couplingFindings(source) {
|
|
146
|
+
const findings = [];
|
|
147
|
+
for (const object of source.objects) {
|
|
148
|
+
if (object.kind !== "constraint" || object.definition.constraintKind !== "foreignKey")
|
|
149
|
+
continue;
|
|
150
|
+
const references = object.definition.references;
|
|
151
|
+
if (references === undefined)
|
|
152
|
+
continue;
|
|
153
|
+
const from = object.identity.parent.namespace;
|
|
154
|
+
const to = references.parent.namespace;
|
|
155
|
+
if (from.form !== "static" || to.form !== "static" || from.name === to.name)
|
|
156
|
+
continue;
|
|
157
|
+
findings.push({
|
|
158
|
+
severity: "warning",
|
|
159
|
+
code: "coupling",
|
|
160
|
+
place: `${from.name}.${object.identity.parent.name}.${object.definition.columns.join(",")}`,
|
|
161
|
+
reason: `references ${to.name}.${references.parent.name}.${references.columns.join(",")}`,
|
|
162
|
+
fix: "Cross-catalog foreign keys are allowed inside one database."
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return findings;
|
|
166
|
+
}
|
|
167
|
+
function assertSnapshots(cwd, config, source) {
|
|
168
|
+
if ((config.external ?? []).length === 0)
|
|
169
|
+
return;
|
|
170
|
+
const snapshots = loadSnapshots(cwd, config);
|
|
171
|
+
for (const table of externalTables(source)) {
|
|
172
|
+
const found = findTable(snapshots, table.namespace, table.name);
|
|
173
|
+
if (found === undefined) {
|
|
174
|
+
throw snapshotError(table.name, "missing", `External table ${table.namespace}.${table.name} is missing from the catalog snapshots.`);
|
|
175
|
+
}
|
|
176
|
+
const differ = columnDifference(table.columns, found.table.columns);
|
|
177
|
+
if (differ !== undefined) {
|
|
178
|
+
throw snapshotError(found.path, "stale", `Catalog snapshot ${found.path} is stale: ${table.namespace}.${table.name}.${differ} does not match the export.`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function assertLiveExternal(url, source, snapshots) {
|
|
183
|
+
if (snapshots.length === 0 && externalTables(source).length === 0)
|
|
184
|
+
return;
|
|
185
|
+
const postgres = (await import("postgres")).default;
|
|
186
|
+
const sql = postgres(driverUrl(url), {
|
|
187
|
+
max: 1,
|
|
188
|
+
connect_timeout: 5,
|
|
189
|
+
idle_timeout: 1,
|
|
190
|
+
onnotice: () => {}
|
|
191
|
+
});
|
|
192
|
+
try {
|
|
193
|
+
const current = await sql`select current_database() as name`;
|
|
194
|
+
const database = current[0]?.name;
|
|
195
|
+
if (database !== undefined)
|
|
196
|
+
assertSameDatabase(source, snapshots, database);
|
|
197
|
+
for (const table of externalTables(source)) {
|
|
198
|
+
const found = findTable(snapshots, table.namespace, table.name);
|
|
199
|
+
if (found === undefined)
|
|
200
|
+
continue;
|
|
201
|
+
await assertLiveTable(sql, found.path, found.snapshot, table);
|
|
202
|
+
}
|
|
203
|
+
} finally {
|
|
204
|
+
await sql.end({ timeout: 5 });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function renderExport(cwd, config, source, args) {
|
|
208
|
+
const flags = exportFlags(args);
|
|
209
|
+
const database = exportDatabase(config);
|
|
210
|
+
const snapshot = snapshotFromCatalog(source, catalogNamespace(config), database);
|
|
211
|
+
const text = serializeSnapshot(snapshot);
|
|
212
|
+
if (flags.out !== undefined) {
|
|
213
|
+
const path = isAbsolute(flags.out) ? flags.out : join(cwd, flags.out);
|
|
214
|
+
writeFileSync(path, text);
|
|
215
|
+
}
|
|
216
|
+
if (flags.json || flags.out === undefined)
|
|
217
|
+
return text;
|
|
218
|
+
return `${flags.out}
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
function exportTables(source) {
|
|
222
|
+
const tables = [];
|
|
223
|
+
for (const object of source.objects) {
|
|
224
|
+
if (object.kind !== "table" || object.owner === "external" || object.owner === "ignored") {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (object.identity.namespace.form !== "static")
|
|
228
|
+
continue;
|
|
229
|
+
const columns = [];
|
|
230
|
+
for (const column of source.objects) {
|
|
231
|
+
if (!isColumnOf(column, object.identity.namespace.name, object.identity.name))
|
|
232
|
+
continue;
|
|
233
|
+
columns.push({
|
|
234
|
+
name: column.identity.name,
|
|
235
|
+
type: column.definition.dataType,
|
|
236
|
+
nullable: column.definition.nullable
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
tables.push({
|
|
240
|
+
namespace: object.identity.namespace.name,
|
|
241
|
+
name: object.identity.name,
|
|
242
|
+
columns
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return tables;
|
|
246
|
+
}
|
|
247
|
+
function isColumnOf(object, namespace, table) {
|
|
248
|
+
return object.kind === "column" && object.identity.parent.name === table && object.identity.parent.namespace.form === "static" && object.identity.parent.namespace.name === namespace;
|
|
249
|
+
}
|
|
250
|
+
function externalTables(source) {
|
|
251
|
+
const tables = [];
|
|
252
|
+
for (const object of source.objects) {
|
|
253
|
+
if (object.kind !== "table" || object.owner !== "external")
|
|
254
|
+
continue;
|
|
255
|
+
if (object.identity.namespace.form !== "static")
|
|
256
|
+
continue;
|
|
257
|
+
const columns = [];
|
|
258
|
+
for (const column of source.objects) {
|
|
259
|
+
if (!isColumnOf(column, object.identity.namespace.name, object.identity.name))
|
|
260
|
+
continue;
|
|
261
|
+
columns.push({
|
|
262
|
+
name: column.identity.name,
|
|
263
|
+
type: column.definition.dataType,
|
|
264
|
+
nullable: column.definition.nullable
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
tables.push({ namespace: object.identity.namespace.name, name: object.identity.name, columns });
|
|
268
|
+
}
|
|
269
|
+
return tables;
|
|
270
|
+
}
|
|
271
|
+
function findTable(snapshots, namespace, name) {
|
|
272
|
+
for (const loaded of snapshots) {
|
|
273
|
+
for (const table of loaded.snapshot.tables) {
|
|
274
|
+
if (table.name === name && (table.namespace === namespace || namespace === "public")) {
|
|
275
|
+
return { path: loaded.path, snapshot: loaded.snapshot, table };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
for (const loaded of snapshots) {
|
|
280
|
+
for (const table of loaded.snapshot.tables) {
|
|
281
|
+
if (table.name === name)
|
|
282
|
+
return { path: loaded.path, snapshot: loaded.snapshot, table };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
function columnDifference(left, right) {
|
|
288
|
+
const byName = new Map(right.map((column) => [column.name, column]));
|
|
289
|
+
for (const column of left) {
|
|
290
|
+
const other = byName.get(column.name);
|
|
291
|
+
if (other === undefined)
|
|
292
|
+
return column.name;
|
|
293
|
+
if (other.type !== column.type || other.nullable !== column.nullable)
|
|
294
|
+
return column.name;
|
|
295
|
+
}
|
|
296
|
+
for (const column of right) {
|
|
297
|
+
if (!left.some((item) => item.name === column.name))
|
|
298
|
+
return column.name;
|
|
299
|
+
}
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
function assertSameDatabase(source, snapshots, database) {
|
|
303
|
+
if (!hasCrossCatalogForeignKey(source))
|
|
304
|
+
return;
|
|
305
|
+
for (const loaded of snapshots) {
|
|
306
|
+
const name = loaded.snapshot.database;
|
|
307
|
+
if (name !== undefined && name !== database) {
|
|
308
|
+
throw new OkmError2("OKM1842", `A foreign key references ${loaded.snapshot.logical.namespace} in database ${name}. This database is ${database}.`, {
|
|
309
|
+
kind: "invalid",
|
|
310
|
+
fieldReason: "database",
|
|
311
|
+
fix: {
|
|
312
|
+
summary: "Keep the foreign key inside one database. Cross-catalog keys are allowed only there."
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function hasCrossCatalogForeignKey(source) {
|
|
319
|
+
for (const object of source.objects) {
|
|
320
|
+
if (object.kind !== "constraint" || object.definition.constraintKind !== "foreignKey")
|
|
321
|
+
continue;
|
|
322
|
+
const references = object.definition.references;
|
|
323
|
+
if (references === undefined)
|
|
324
|
+
continue;
|
|
325
|
+
const from = object.identity.parent.namespace;
|
|
326
|
+
const to = references.parent.namespace;
|
|
327
|
+
if (from.form === "static" && to.form === "static" && from.name !== to.name)
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
async function assertLiveTable(sql, path, snapshot, table) {
|
|
333
|
+
const hash = await liveHash(sql, table.namespace);
|
|
334
|
+
if (hash !== undefined && hash !== snapshot.catalogHash) {
|
|
335
|
+
throw snapshotError(path, "stale", `Catalog snapshot ${path} is stale: the catalog hash changed.`);
|
|
336
|
+
}
|
|
337
|
+
const live = await liveColumns(sql, table.namespace, table.name);
|
|
338
|
+
if (live === undefined) {
|
|
339
|
+
throw snapshotError(path, "missing", `External table ${table.namespace}.${table.name} is missing on the database.`);
|
|
340
|
+
}
|
|
341
|
+
const differ = columnDifference(snapshotTableColumns(snapshot, table), live);
|
|
342
|
+
if (differ !== undefined) {
|
|
343
|
+
throw snapshotError(path, "stale", `Catalog snapshot ${path} is stale: ${table.namespace}.${table.name}.${differ} is missing on the database.`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function snapshotTableColumns(snapshot, table) {
|
|
347
|
+
for (const item of snapshot.tables) {
|
|
348
|
+
if (item.name === table.name)
|
|
349
|
+
return item.columns;
|
|
350
|
+
}
|
|
351
|
+
return table.columns;
|
|
352
|
+
}
|
|
353
|
+
async function liveHash(sql, namespace) {
|
|
354
|
+
const reg = `${quoteIdent2(namespace)}.${quoteIdent2("okm_meta")}`;
|
|
355
|
+
const present = await sql.unsafe(`select to_regclass('${reg}')::text as reg`);
|
|
356
|
+
if (present[0]?.reg == null)
|
|
357
|
+
return;
|
|
358
|
+
const rows = await sql.unsafe(`select catalog_hash from ${reg} where id = 'head'`);
|
|
359
|
+
return rows[0]?.catalog_hash;
|
|
360
|
+
}
|
|
361
|
+
async function liveColumns(sql, namespace, table) {
|
|
362
|
+
const rows = await sql`select a.attname as name, format_type(a.atttypid, a.atttypmod) as type, a.attnotnull as not_null
|
|
363
|
+
from pg_attribute a
|
|
364
|
+
join pg_class c on c.oid = a.attrelid
|
|
365
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
366
|
+
where n.nspname = ${namespace} and c.relname = ${table}
|
|
367
|
+
and a.attnum > 0 and not a.attisdropped
|
|
368
|
+
order by a.attnum`;
|
|
369
|
+
if (rows.length === 0) {
|
|
370
|
+
const exists = await sql`
|
|
371
|
+
select to_regclass(${`${quoteIdent2(namespace)}.${quoteIdent2(table)}`})::text as reg
|
|
372
|
+
`;
|
|
373
|
+
if (exists[0]?.reg == null)
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
return rows.map((row) => ({ name: row.name, type: row.type, nullable: !row.not_null }));
|
|
377
|
+
}
|
|
378
|
+
function resolveSnapshot(cwd, path) {
|
|
379
|
+
return isAbsolute(path) ? path : projectPath(cwd, path);
|
|
380
|
+
}
|
|
381
|
+
function exportDatabase(config) {
|
|
382
|
+
const target = config.database;
|
|
383
|
+
if (typeof target === "string")
|
|
384
|
+
return databaseName(target);
|
|
385
|
+
if (target?.url !== undefined)
|
|
386
|
+
return databaseName(target.url);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
function exportFlags(args) {
|
|
390
|
+
let json = false;
|
|
391
|
+
let out;
|
|
392
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
393
|
+
const arg = args[index] ?? "";
|
|
394
|
+
if (arg === "--json") {
|
|
395
|
+
json = true;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (arg === "--out") {
|
|
399
|
+
const value = args[index + 1];
|
|
400
|
+
if (value === undefined || value.startsWith("--")) {
|
|
401
|
+
throw new OkmError2("invalid", "--out needs a file.", {
|
|
402
|
+
fix: { summary: "Pass --out <file>." }
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
out = value;
|
|
406
|
+
index += 1;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (arg.startsWith("--out=")) {
|
|
410
|
+
out = arg.slice("--out=".length);
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
throw new OkmError2("invalid", `Unknown flag ${arg}.`, {
|
|
414
|
+
fix: { summary: "okm catalog export accepts --json and --out <file>." }
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
return out === undefined ? { json } : { json, out };
|
|
418
|
+
}
|
|
419
|
+
function snapshotError(path, reason, message) {
|
|
420
|
+
return new OkmError2("OKM1841", message, {
|
|
421
|
+
kind: reason === "missing" ? "not_found" : "schema_drift",
|
|
422
|
+
fieldReason: reason,
|
|
423
|
+
columns: [path],
|
|
424
|
+
fix: {
|
|
425
|
+
summary: reason === "missing" ? "Point external at the file okm catalog export wrote, and apply the owning catalog." : "Run okm catalog export in the owning catalog and update the dependent."
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
function isSnapshot(value) {
|
|
430
|
+
if (!isRecord(value) || value.format !== EXPORT_FORMAT)
|
|
431
|
+
return false;
|
|
432
|
+
if (typeof value.catalogHash !== "string")
|
|
433
|
+
return false;
|
|
434
|
+
if (!isLogical(value.logical))
|
|
435
|
+
return false;
|
|
436
|
+
if (value.database !== undefined && typeof value.database !== "string")
|
|
437
|
+
return false;
|
|
438
|
+
if (!Array.isArray(value.tables))
|
|
439
|
+
return false;
|
|
440
|
+
return value.tables.every(isTable);
|
|
441
|
+
}
|
|
442
|
+
function isLogical(value) {
|
|
443
|
+
if (!isRecord(value))
|
|
444
|
+
return false;
|
|
445
|
+
return typeof value.namespace === "string" && typeof value.name === "string" && value.format === LOGICAL_FORMAT;
|
|
446
|
+
}
|
|
447
|
+
function isTable(value) {
|
|
448
|
+
if (!isRecord(value) || typeof value.namespace !== "string" || typeof value.name !== "string") {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
if (!Array.isArray(value.columns))
|
|
452
|
+
return false;
|
|
453
|
+
return value.columns.every((column) => isRecord(column) && typeof column.name === "string" && typeof column.type === "string" && typeof column.nullable === "boolean");
|
|
454
|
+
}
|
|
455
|
+
function isRecord(value) {
|
|
456
|
+
return typeof value === "object" && value !== null;
|
|
457
|
+
}
|
|
458
|
+
|
|
81
459
|
// src/tooling/errors/registry.ts
|
|
82
460
|
var ERROR_DOCS = [
|
|
83
461
|
{
|
|
@@ -209,7 +587,7 @@ var ERROR_DOCS = [
|
|
|
209
587
|
{
|
|
210
588
|
code: "OKM1120",
|
|
211
589
|
title: "Unknown field",
|
|
212
|
-
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).",
|
|
590
|
+
summary: "The name is not a field or an option on a write, read, or update. An unknown key on insert, update, upsert, or first-or-create is refused and names the key and the table. timeout and signal go in the second argument of update. Options go first: tx(options, fn).",
|
|
213
591
|
fix: "Use a field from the catalog, or move the option. The error includes a nearest-name hint when one is close."
|
|
214
592
|
},
|
|
215
593
|
{
|
|
@@ -239,8 +617,14 @@ var ERROR_DOCS = [
|
|
|
239
617
|
{
|
|
240
618
|
code: "OKM1125",
|
|
241
619
|
title: "Bad SQL fragment",
|
|
242
|
-
summary: "A composed fragment is empty, unbalanced, or interpolates a value it cannot bind. A select fragment that is not a number is the same code.",
|
|
243
|
-
fix: "Interpolate a string, number, boolean, null, ident(name), fragment.column, or another fragment. Keep parentheses and quotes balanced. A select fragment is a numeric expression named with as()."
|
|
620
|
+
summary: "A composed fragment is empty, unbalanced, includes a backslash, or interpolates a value it cannot bind. A select fragment that is not a number is the same code.",
|
|
621
|
+
fix: "Interpolate a string, number, boolean, null, ident(name), fragment.column, or another fragment. Keep parentheses and quotes balanced. Pass a value that needs a backslash as an interpolation. A select fragment is a numeric expression named with as()."
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
code: "OKM1126",
|
|
625
|
+
title: "Parameter limit",
|
|
626
|
+
summary: "The finished statement has more than 65,535 parameters. The limit is for the whole statement (filters, inList, limit, values), not for inList alone.",
|
|
627
|
+
fix: "Chunk the work, or filter with = any(array) instead of a long inList."
|
|
244
628
|
},
|
|
245
629
|
{
|
|
246
630
|
code: "OKM1130",
|
|
@@ -455,7 +839,7 @@ var ERROR_DOCS = [
|
|
|
455
839
|
{
|
|
456
840
|
code: "OKM1538",
|
|
457
841
|
title: "Type rewrite",
|
|
458
|
-
summary: "A statement changes a column type in a way that rewrites the table.",
|
|
842
|
+
summary: "A statement changes a column type in a way that rewrites the table. Binary-coercible changes, varchar widening, varchar to text, and a numeric precision increase at the same scale do not.",
|
|
459
843
|
fix: "There is no safe form for a rewriting type change in this version. A column swap is expand and contract work for a later step. OKM1524 already requires a reason for the type change."
|
|
460
844
|
},
|
|
461
845
|
{
|
|
@@ -536,6 +920,12 @@ var ERROR_DOCS = [
|
|
|
536
920
|
summary: "A step contains COMMIT, ROLLBACK, or other transaction control, including inside a DO body. A function or procedure body is not this.",
|
|
537
921
|
fix: "Remove the transaction control. okm migrate check replays one transaction and rolls it back. Apply accepts the step only with -- okm-allow OKM1551: reason."
|
|
538
922
|
},
|
|
923
|
+
{
|
|
924
|
+
code: "OKM1552",
|
|
925
|
+
title: "Reprinted declaration",
|
|
926
|
+
summary: "A domain check or a LANGUAGE sql function body is stored differently from the text in the migration catalog.",
|
|
927
|
+
fix: "Write the check or the body the way Postgres prints it, or allow OKM1552 with a reason. The warning does not fail apply. A domain whose check differs is still OKM1547."
|
|
928
|
+
},
|
|
539
929
|
{
|
|
540
930
|
code: "OKM1601",
|
|
541
931
|
title: "Stale typed SQL",
|
|
@@ -751,6 +1141,18 @@ var ERROR_DOCS = [
|
|
|
751
1141
|
title: "Pooler session",
|
|
752
1142
|
summary: "Two statements on the same connection saw different backend pids. A transaction-mode pooler is in the way.",
|
|
753
1143
|
fix: "Use a direct connection, or pass --allow-pooler."
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
code: "OKM1855",
|
|
1147
|
+
title: "Pull would overwrite",
|
|
1148
|
+
summary: "okm pull or okm import drizzle found a file it would replace. It writes nothing.",
|
|
1149
|
+
fix: "Pass --out <directory> to write somewhere else, or move the file."
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
code: "OKM1856",
|
|
1153
|
+
title: "Drizzle is not installed",
|
|
1154
|
+
summary: "okm import drizzle could not resolve drizzle-orm from the project.",
|
|
1155
|
+
fix: "Install drizzle-orm in the project that owns the schema."
|
|
754
1156
|
}
|
|
755
1157
|
];
|
|
756
1158
|
var byCode;
|
|
@@ -767,25 +1169,25 @@ function indexDocs() {
|
|
|
767
1169
|
}
|
|
768
1170
|
|
|
769
1171
|
// src/tooling/migrate/files.ts
|
|
770
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
771
|
-
import { join } from "node:path";
|
|
1172
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
1173
|
+
import { join as join2 } from "node:path";
|
|
772
1174
|
function loadMigrations(directory) {
|
|
773
|
-
if (!
|
|
1175
|
+
if (!existsSync2(directory))
|
|
774
1176
|
return [];
|
|
775
1177
|
const names = readdirSync(directory).filter((file) => file.endsWith(".sql")).sort();
|
|
776
1178
|
const migrations = [];
|
|
777
1179
|
for (const file of names) {
|
|
778
1180
|
const id = file.slice(0, -".sql".length);
|
|
779
|
-
const catalogPath =
|
|
780
|
-
if (!
|
|
1181
|
+
const catalogPath = join2(directory, `${id}.catalog.json`);
|
|
1182
|
+
if (!existsSync2(catalogPath)) {
|
|
781
1183
|
throw new OkmError2("invalid", `Migration ${id} is missing its catalog snapshot.`, {
|
|
782
1184
|
fix: {
|
|
783
1185
|
summary: "Generate the migration again so the SQL and the catalog are written together."
|
|
784
1186
|
}
|
|
785
1187
|
});
|
|
786
1188
|
}
|
|
787
|
-
const plan = readMigrationPlan(
|
|
788
|
-
const stored = parseCatalog2(
|
|
1189
|
+
const plan = readMigrationPlan(join2(directory, file));
|
|
1190
|
+
const stored = parseCatalog2(readFileSync2(catalogPath, "utf8"));
|
|
789
1191
|
const startup = startupCatalog2(stored);
|
|
790
1192
|
const hash = catalogHash2(startup);
|
|
791
1193
|
migrations.push(startup === stored ? { id, catalogHash: hash, steps: plan.steps } : { id, catalogHash: hash, legacyHash: catalogHash2(stored), steps: plan.steps });
|
|
@@ -793,7 +1195,7 @@ function loadMigrations(directory) {
|
|
|
793
1195
|
return migrations;
|
|
794
1196
|
}
|
|
795
1197
|
function readMigrationPlan(path) {
|
|
796
|
-
const text = prepareMigrationText(
|
|
1198
|
+
const text = prepareMigrationText(readFileSync2(path, "utf8"), path);
|
|
797
1199
|
const plan = parsePlan(text);
|
|
798
1200
|
assertMigrationSteps(text, plan.steps.length, path);
|
|
799
1201
|
return plan;
|
|
@@ -806,13 +1208,13 @@ function loadHeadSnapshot(directory) {
|
|
|
806
1208
|
return {
|
|
807
1209
|
id: last.id,
|
|
808
1210
|
catalogHash: last.catalogHash,
|
|
809
|
-
catalog: parseCatalog2(
|
|
1211
|
+
catalog: parseCatalog2(readFileSync2(join2(directory, `${last.id}.catalog.json`), "utf8"))
|
|
810
1212
|
};
|
|
811
1213
|
}
|
|
812
1214
|
|
|
813
1215
|
// src/tooling/migrate/lint.ts
|
|
814
|
-
import { existsSync as
|
|
815
|
-
import { join as
|
|
1216
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
1217
|
+
import { join as join3 } from "node:path";
|
|
816
1218
|
|
|
817
1219
|
// src/tooling/migrate/lint-rules.ts
|
|
818
1220
|
var INTEGER_WIDTH = {
|
|
@@ -899,7 +1301,7 @@ function rule(code, category, severity, doc, check) {
|
|
|
899
1301
|
function hit(matched, reason) {
|
|
900
1302
|
return matched ? [reason] : [];
|
|
901
1303
|
}
|
|
902
|
-
function
|
|
1304
|
+
function normalizedSql(sql) {
|
|
903
1305
|
const collapsed = sql.trim().replace(/\s+/g, " ");
|
|
904
1306
|
let out = "";
|
|
905
1307
|
let quoted = false;
|
|
@@ -914,7 +1316,7 @@ function normalized(sql) {
|
|
|
914
1316
|
return out;
|
|
915
1317
|
}
|
|
916
1318
|
function weakensRls(input) {
|
|
917
|
-
const text =
|
|
1319
|
+
const text = normalizedSql(input.step.sql);
|
|
918
1320
|
if (/\bdisable row level security\b/.test(text))
|
|
919
1321
|
return ["disables row level security"];
|
|
920
1322
|
if (/\bno force row level security\b/.test(text))
|
|
@@ -1031,7 +1433,7 @@ function parenthesized(text, open) {
|
|
|
1031
1433
|
}
|
|
1032
1434
|
function policyExpression(input, table, name) {
|
|
1033
1435
|
const expression = expressionOf(input.after, table, name) ?? expressionOf(input.before, table, name);
|
|
1034
|
-
return expression === undefined ? undefined :
|
|
1436
|
+
return expression === undefined ? undefined : normalizedSql(expression);
|
|
1035
1437
|
}
|
|
1036
1438
|
function expressionOf(source, table, name) {
|
|
1037
1439
|
for (const object of source.objects) {
|
|
@@ -1054,7 +1456,7 @@ function hasPolicy(source, table) {
|
|
|
1054
1456
|
return source.objects.some((object) => object.kind === "policy" && object.identity.parent.name === table);
|
|
1055
1457
|
}
|
|
1056
1458
|
function dataStatement(step) {
|
|
1057
|
-
const text =
|
|
1459
|
+
const text = normalizedSql(step.sql);
|
|
1058
1460
|
if (/^(?:do|call)\b/.test(text))
|
|
1059
1461
|
return ["the body is not analysed"];
|
|
1060
1462
|
if (step.backfill !== undefined)
|
|
@@ -1068,16 +1470,16 @@ function dataStatement(step) {
|
|
|
1068
1470
|
return [];
|
|
1069
1471
|
}
|
|
1070
1472
|
function matches(step, kind, pattern) {
|
|
1071
|
-
return pattern.test(
|
|
1473
|
+
return pattern.test(normalizedSql(step.sql)) || step.kind === kind;
|
|
1072
1474
|
}
|
|
1073
1475
|
function renamesTable(step) {
|
|
1074
|
-
const text =
|
|
1476
|
+
const text = normalizedSql(step.sql);
|
|
1075
1477
|
if (/^alter table\s+\S+\s+rename to\b/.test(text))
|
|
1076
1478
|
return true;
|
|
1077
1479
|
return step.kind === "rename-table" && !/^alter table\s+\S+\s+rename (column|constraint)\b/.test(text);
|
|
1078
1480
|
}
|
|
1079
1481
|
function droppedType(input) {
|
|
1080
|
-
const text =
|
|
1482
|
+
const text = normalizedSql(input.step.sql);
|
|
1081
1483
|
const dropping = /^drop type\b/.test(text);
|
|
1082
1484
|
if (!dropping && input.step.kind !== "drop-enum" && input.step.kind !== "drop-domain") {
|
|
1083
1485
|
return;
|
|
@@ -1093,7 +1495,7 @@ function droppedType(input) {
|
|
|
1093
1495
|
return "enum";
|
|
1094
1496
|
}
|
|
1095
1497
|
function requiredColumn(input) {
|
|
1096
|
-
const text =
|
|
1498
|
+
const text = normalizedSql(input.step.sql);
|
|
1097
1499
|
const adding = /^alter table\s+\S+\s+add column\b/.test(text) || input.step.kind === "add-column-required";
|
|
1098
1500
|
if (!adding)
|
|
1099
1501
|
return false;
|
|
@@ -1109,7 +1511,7 @@ function requiredColumn(input) {
|
|
|
1109
1511
|
function uniqueConstraint(input) {
|
|
1110
1512
|
if (!tableExists(input))
|
|
1111
1513
|
return false;
|
|
1112
|
-
const text =
|
|
1514
|
+
const text = normalizedSql(input.step.sql);
|
|
1113
1515
|
if (/\busing index\b/.test(text))
|
|
1114
1516
|
return false;
|
|
1115
1517
|
if (/^alter table\s+\S+\s+add constraint\b/.test(text) && /\b(unique|primary key)\b/.test(text)) {
|
|
@@ -1120,16 +1522,16 @@ function uniqueConstraint(input) {
|
|
|
1120
1522
|
const name = createdIndexName(text);
|
|
1121
1523
|
if (name === undefined)
|
|
1122
1524
|
return false;
|
|
1123
|
-
return input.later.some((step) => usesIndex(
|
|
1525
|
+
return input.later.some((step) => usesIndex(normalizedSql(step.sql), name));
|
|
1124
1526
|
}
|
|
1125
1527
|
function uniqueIndex(input) {
|
|
1126
|
-
const text =
|
|
1528
|
+
const text = normalizedSql(input.step.sql);
|
|
1127
1529
|
if (!/^create unique index\b/.test(text))
|
|
1128
1530
|
return false;
|
|
1129
1531
|
if (!tableExists(input))
|
|
1130
1532
|
return false;
|
|
1131
1533
|
const name = createdIndexName(text);
|
|
1132
|
-
if (name !== undefined && input.later.some((step) => usesIndex(
|
|
1534
|
+
if (name !== undefined && input.later.some((step) => usesIndex(normalizedSql(step.sql), name))) {
|
|
1133
1535
|
return false;
|
|
1134
1536
|
}
|
|
1135
1537
|
return true;
|
|
@@ -1142,7 +1544,7 @@ function usesIndex(text, name) {
|
|
|
1142
1544
|
return text.includes(`using index "${name}"`) || text.includes(`using index ${name}`);
|
|
1143
1545
|
}
|
|
1144
1546
|
function unsafeSetNotNull(input) {
|
|
1145
|
-
const text =
|
|
1547
|
+
const text = normalizedSql(input.step.sql);
|
|
1146
1548
|
const match = /^alter table\s+(\S+)\s+alter column\s+(\S+)\s+set not null\b/.exec(text);
|
|
1147
1549
|
if (match === null && input.step.kind !== "set-not-null")
|
|
1148
1550
|
return false;
|
|
@@ -1158,21 +1560,21 @@ function unsafeSetNotNull(input) {
|
|
|
1158
1560
|
function validatedNotNullCheck(earlier, table, column) {
|
|
1159
1561
|
const needle = `check (${column} is not null) not valid`;
|
|
1160
1562
|
for (const step of earlier) {
|
|
1161
|
-
const text =
|
|
1563
|
+
const text = normalizedSql(step.sql);
|
|
1162
1564
|
if (!text.startsWith(`alter table ${table} `) || !text.includes(needle))
|
|
1163
1565
|
continue;
|
|
1164
1566
|
const added = /^alter table\s+\S+\s+add constraint\s+(\S+)\s+check\b/.exec(text);
|
|
1165
1567
|
const name = added?.[1];
|
|
1166
1568
|
if (name === undefined)
|
|
1167
1569
|
continue;
|
|
1168
|
-
const validated = earlier.some((item) =>
|
|
1570
|
+
const validated = earlier.some((item) => normalizedSql(item.sql).startsWith(`alter table ${table} validate constraint ${name}`));
|
|
1169
1571
|
if (validated)
|
|
1170
1572
|
return true;
|
|
1171
1573
|
}
|
|
1172
1574
|
return false;
|
|
1173
1575
|
}
|
|
1174
1576
|
function blockingIndex(input) {
|
|
1175
|
-
const text =
|
|
1577
|
+
const text = normalizedSql(input.step.sql);
|
|
1176
1578
|
if (!/^create(?: unique)? index\b/.test(text) && input.step.kind !== "create-index")
|
|
1177
1579
|
return false;
|
|
1178
1580
|
if (/\bconcurrently\b/.test(text))
|
|
@@ -1180,20 +1582,40 @@ function blockingIndex(input) {
|
|
|
1180
1582
|
return tableExists(input);
|
|
1181
1583
|
}
|
|
1182
1584
|
function checkWithoutNotValid(input) {
|
|
1183
|
-
return addedConstraint(input, "check") && !/\bnot valid\b/.test(
|
|
1585
|
+
return addedConstraint(input, "check") && !/\bnot valid\b/.test(normalizedSql(input.step.sql));
|
|
1184
1586
|
}
|
|
1185
1587
|
function foreignKeyWithoutNotValid(input) {
|
|
1186
|
-
return addedConstraint(input, "foreign key") && !/\bnot valid\b/.test(
|
|
1588
|
+
return addedConstraint(input, "foreign key") && !/\bnot valid\b/.test(normalizedSql(input.step.sql));
|
|
1187
1589
|
}
|
|
1188
1590
|
function validatingCheck(input) {
|
|
1189
1591
|
if (widenValidate(input))
|
|
1190
1592
|
return false;
|
|
1593
|
+
if (notNullFloorValidate(input))
|
|
1594
|
+
return false;
|
|
1191
1595
|
if (validates(input, "check"))
|
|
1192
1596
|
return true;
|
|
1193
1597
|
return checkWithoutNotValid(input);
|
|
1194
1598
|
}
|
|
1599
|
+
function notNullFloorValidate(input) {
|
|
1600
|
+
const text = normalizedSql(input.step.sql);
|
|
1601
|
+
const name = /^alter table\s+(\S+)\s+validate constraint\s+(\S+)/.exec(text);
|
|
1602
|
+
if (name === null)
|
|
1603
|
+
return false;
|
|
1604
|
+
const table = name[1];
|
|
1605
|
+
const constraint = name[2];
|
|
1606
|
+
if (table === undefined || constraint === undefined)
|
|
1607
|
+
return false;
|
|
1608
|
+
return input.earlier.some((step) => {
|
|
1609
|
+
const prior = normalizedSql(step.sql);
|
|
1610
|
+
if (!prior.startsWith(`alter table ${table} `))
|
|
1611
|
+
return false;
|
|
1612
|
+
if (!prior.includes(`add constraint ${constraint} `))
|
|
1613
|
+
return false;
|
|
1614
|
+
return /check\s*\(\s*\S+\s+is not null\s*\)\s*not valid/.test(prior);
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1195
1617
|
function widenValidate(input) {
|
|
1196
|
-
const text =
|
|
1618
|
+
const text = normalizedSql(input.step.sql);
|
|
1197
1619
|
if (!/^alter table\s+\S+\s+validate constraint\b/.test(text))
|
|
1198
1620
|
return false;
|
|
1199
1621
|
if (input.step.kind === "widen-check")
|
|
@@ -1204,7 +1626,7 @@ function widenValidate(input) {
|
|
|
1204
1626
|
return input.earlier.some((step) => {
|
|
1205
1627
|
if (step.kind !== "widen-check")
|
|
1206
1628
|
return false;
|
|
1207
|
-
const prior =
|
|
1629
|
+
const prior = normalizedSql(step.sql);
|
|
1208
1630
|
return prior.includes(`add constraint ${name} `);
|
|
1209
1631
|
});
|
|
1210
1632
|
}
|
|
@@ -1216,13 +1638,13 @@ function validatingForeignKey(input) {
|
|
|
1216
1638
|
function addedConstraint(input, kind) {
|
|
1217
1639
|
if (!tableExists(input))
|
|
1218
1640
|
return false;
|
|
1219
|
-
const text =
|
|
1641
|
+
const text = normalizedSql(input.step.sql);
|
|
1220
1642
|
if (!/^alter table\s+\S+\s+add constraint\b/.test(text))
|
|
1221
1643
|
return false;
|
|
1222
1644
|
return new RegExp(`\\b${kind}\\b`).test(text);
|
|
1223
1645
|
}
|
|
1224
1646
|
function validates(input, kind) {
|
|
1225
|
-
const text =
|
|
1647
|
+
const text = normalizedSql(input.step.sql);
|
|
1226
1648
|
const table = /^alter table\s+\S+\s+validate constraint\s+(\S+)/.exec(text);
|
|
1227
1649
|
if (table !== null) {
|
|
1228
1650
|
const token = table[1];
|
|
@@ -1242,7 +1664,7 @@ function validates(input, kind) {
|
|
|
1242
1664
|
}
|
|
1243
1665
|
var TYPE_CHANGE = /^alter table\s+(\S+)\s+alter column\s+(\S+)\s+(?:set data type|type)\b/;
|
|
1244
1666
|
function typeChange(input, mode) {
|
|
1245
|
-
const text =
|
|
1667
|
+
const text = normalizedSql(input.step.sql);
|
|
1246
1668
|
const changing = TYPE_CHANGE.test(text) || input.step.kind === "set-column-type";
|
|
1247
1669
|
if (!changing)
|
|
1248
1670
|
return [];
|
|
@@ -1258,18 +1680,19 @@ function typeChange(input, mode) {
|
|
|
1258
1680
|
const shrink = lengthShrink(before, after);
|
|
1259
1681
|
if (mode === "shrink")
|
|
1260
1682
|
return shrink ? ["shortens a character length"] : [];
|
|
1261
|
-
if (shrink)
|
|
1262
|
-
return [];
|
|
1683
|
+
if (shrink) {
|
|
1684
|
+
return mode === "rewrite" && tableRewrites(before, after, input) ? ["rewrites the table"] : [];
|
|
1685
|
+
}
|
|
1263
1686
|
if (mode === "narrow")
|
|
1264
1687
|
return narrows(before, after) ? ["narrows a type"] : [];
|
|
1265
|
-
if (lengthGrow(before, after))
|
|
1688
|
+
if (lengthGrow(before, after) && mode !== "rewrite")
|
|
1266
1689
|
return [];
|
|
1267
1690
|
if (mode === "incompatible")
|
|
1268
1691
|
return ["changes a column type"];
|
|
1269
|
-
return
|
|
1692
|
+
return tableRewrites(before, after, input) ? ["rewrites the table"] : [];
|
|
1270
1693
|
}
|
|
1271
1694
|
function columnTypes(input) {
|
|
1272
|
-
const text =
|
|
1695
|
+
const text = normalizedSql(input.step.sql);
|
|
1273
1696
|
const match = TYPE_CHANGE.exec(text);
|
|
1274
1697
|
const table = lastName(match?.[1]);
|
|
1275
1698
|
const column = lastName(match?.[2]);
|
|
@@ -1294,13 +1717,115 @@ function lengthGrow(before, after) {
|
|
|
1294
1717
|
const right = charLength(after);
|
|
1295
1718
|
return left !== undefined && right !== undefined && left.family === right.family && right.length > left.length;
|
|
1296
1719
|
}
|
|
1297
|
-
function
|
|
1298
|
-
if (
|
|
1720
|
+
function tableRewrites(before, after, input) {
|
|
1721
|
+
if (usingRewrites(input))
|
|
1722
|
+
return true;
|
|
1723
|
+
if (varyingRelaxation(before, after))
|
|
1724
|
+
return false;
|
|
1725
|
+
if (isVarying(before) && after === "text")
|
|
1726
|
+
return false;
|
|
1727
|
+
if (before === "text" && unconstrainedVarying(after))
|
|
1728
|
+
return false;
|
|
1729
|
+
if ((before === "text" || isVarying(before)) && unconstrainedCharacter(after))
|
|
1730
|
+
return false;
|
|
1731
|
+
if (numericRelaxation(before, after))
|
|
1732
|
+
return false;
|
|
1733
|
+
if (binaryPair(before, after))
|
|
1299
1734
|
return false;
|
|
1300
|
-
if (
|
|
1735
|
+
if (domainSkipsRewrite(before, after, input))
|
|
1301
1736
|
return false;
|
|
1302
1737
|
return true;
|
|
1303
1738
|
}
|
|
1739
|
+
function statementMayReprint(sql) {
|
|
1740
|
+
const text = normalizedSql(sql);
|
|
1741
|
+
if (/^(?:create|alter) domain\b/.test(text))
|
|
1742
|
+
return true;
|
|
1743
|
+
return /^create(?: or replace)? function\b/.test(text) && /\blanguage sql\b/.test(text);
|
|
1744
|
+
}
|
|
1745
|
+
function usingRewrites(input) {
|
|
1746
|
+
const text = normalizedSql(input.step.sql);
|
|
1747
|
+
const at = text.search(/\busing\b/);
|
|
1748
|
+
if (at < 0)
|
|
1749
|
+
return false;
|
|
1750
|
+
const column = lastName(TYPE_CHANGE.exec(text)?.[2]);
|
|
1751
|
+
if (column === undefined)
|
|
1752
|
+
return true;
|
|
1753
|
+
const expression = text.slice(at + "using".length).trim().replace(/;$/, "");
|
|
1754
|
+
return !plainCast(expression, column);
|
|
1755
|
+
}
|
|
1756
|
+
function plainCast(expression, column) {
|
|
1757
|
+
const bare = column.toLowerCase();
|
|
1758
|
+
const quoted = `"${column}"`;
|
|
1759
|
+
return expression.startsWith(`${bare}::`) || expression.startsWith(`${quoted}::`) || expression.startsWith(`cast(${bare} as `) || expression.startsWith(`cast(${quoted} as `);
|
|
1760
|
+
}
|
|
1761
|
+
function isVarying(type) {
|
|
1762
|
+
return type === "character varying" || type === "varchar" || type.startsWith("character varying(") || type.startsWith("varchar(");
|
|
1763
|
+
}
|
|
1764
|
+
function unconstrainedVarying(type) {
|
|
1765
|
+
return type === "character varying" || type === "varchar";
|
|
1766
|
+
}
|
|
1767
|
+
function unconstrainedCharacter(type) {
|
|
1768
|
+
return type === "bpchar" || type === "character";
|
|
1769
|
+
}
|
|
1770
|
+
function varyingRelaxation(before, after) {
|
|
1771
|
+
const left = charLength(before);
|
|
1772
|
+
if (left?.family === "varying" && unconstrainedVarying(after))
|
|
1773
|
+
return true;
|
|
1774
|
+
const right = charLength(after);
|
|
1775
|
+
return left?.family === "varying" && right?.family === "varying" && right.length > left.length;
|
|
1776
|
+
}
|
|
1777
|
+
function numericRelaxation(before, after) {
|
|
1778
|
+
const left = numericBounds(before);
|
|
1779
|
+
const right = numericBounds(after);
|
|
1780
|
+
if (left === undefined || right === undefined)
|
|
1781
|
+
return false;
|
|
1782
|
+
if (right.precision === undefined)
|
|
1783
|
+
return left.precision !== undefined;
|
|
1784
|
+
if (left.precision === undefined || left.scale === undefined || right.scale === undefined) {
|
|
1785
|
+
return false;
|
|
1786
|
+
}
|
|
1787
|
+
return right.scale === left.scale && right.precision > left.precision;
|
|
1788
|
+
}
|
|
1789
|
+
function numericBounds(type) {
|
|
1790
|
+
const match = /^numeric(?:\((\d+)(?:,(\d+))?\))?$/.exec(type);
|
|
1791
|
+
if (match === null)
|
|
1792
|
+
return;
|
|
1793
|
+
if (match[1] === undefined)
|
|
1794
|
+
return { precision: undefined, scale: undefined };
|
|
1795
|
+
return {
|
|
1796
|
+
precision: Number(match[1]),
|
|
1797
|
+
scale: match[2] === undefined ? 0 : Number(match[2])
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
function binaryPair(before, after) {
|
|
1801
|
+
if (before === "integer" && after === "oid")
|
|
1802
|
+
return true;
|
|
1803
|
+
if (before === "oid" && after === "integer")
|
|
1804
|
+
return true;
|
|
1805
|
+
if (before === "cidr" && after === "inet")
|
|
1806
|
+
return true;
|
|
1807
|
+
return before === "xml" && after === "text";
|
|
1808
|
+
}
|
|
1809
|
+
function domainSkipsRewrite(before, after, input) {
|
|
1810
|
+
const next = domainNamed(input, after);
|
|
1811
|
+
if (next !== undefined && next.check.trim().length === 0 && canonical(next.base) === before) {
|
|
1812
|
+
return true;
|
|
1813
|
+
}
|
|
1814
|
+
const previous = domainNamed(input, before);
|
|
1815
|
+
return previous !== undefined && canonical(previous.base) === after;
|
|
1816
|
+
}
|
|
1817
|
+
function domainNamed(input, name) {
|
|
1818
|
+
for (const source of [input.after, input.before]) {
|
|
1819
|
+
for (const object of source.objects) {
|
|
1820
|
+
if (object.kind !== "type" || object.identity.name !== name)
|
|
1821
|
+
continue;
|
|
1822
|
+
if (!isDomain(object.definition))
|
|
1823
|
+
continue;
|
|
1824
|
+
return object.definition;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1304
1829
|
function narrows(before, after) {
|
|
1305
1830
|
const leftInt = INTEGER_WIDTH[before];
|
|
1306
1831
|
const rightInt = INTEGER_WIDTH[after];
|
|
@@ -1325,9 +1850,6 @@ function charLength(type) {
|
|
|
1325
1850
|
return { family: "fixed", length: Number(fixed[1]) };
|
|
1326
1851
|
return;
|
|
1327
1852
|
}
|
|
1328
|
-
function isCharacter(type) {
|
|
1329
|
-
return type === "text" || charLength(type) !== undefined;
|
|
1330
|
-
}
|
|
1331
1853
|
function numericSpec(type) {
|
|
1332
1854
|
const match = /^numeric(?:\((\d+)(?:,(\d+))?\))?$/.exec(type);
|
|
1333
1855
|
if (match === null)
|
|
@@ -1344,7 +1866,7 @@ function tableExists(input) {
|
|
|
1344
1866
|
return name !== undefined && input.existingTables.has(name);
|
|
1345
1867
|
}
|
|
1346
1868
|
function statementTable(step) {
|
|
1347
|
-
const text =
|
|
1869
|
+
const text = normalizedSql(step.sql);
|
|
1348
1870
|
const altered = /^(?:alter|drop) table\s+(\S+)/.exec(text);
|
|
1349
1871
|
if (altered?.[1] !== undefined)
|
|
1350
1872
|
return lastName(altered[1]);
|
|
@@ -1380,6 +1902,134 @@ function isSerial(column) {
|
|
|
1380
1902
|
return /nextval\s*\(/i.test(column.definition.defaultExpression ?? "");
|
|
1381
1903
|
}
|
|
1382
1904
|
|
|
1905
|
+
// src/tooling/migrate/reprint.ts
|
|
1906
|
+
var REPRINT_CODE = "OKM1552";
|
|
1907
|
+
function reprintHits(declared, stored, steps) {
|
|
1908
|
+
const hits = [];
|
|
1909
|
+
const silenced = new Set;
|
|
1910
|
+
for (const difference of differences(declared, stored)) {
|
|
1911
|
+
const index = stepIndex(steps, difference.kind, difference.name);
|
|
1912
|
+
if (index === undefined)
|
|
1913
|
+
continue;
|
|
1914
|
+
const step = steps[index];
|
|
1915
|
+
const allow = step?.allows?.find((item) => item.code === REPRINT_CODE);
|
|
1916
|
+
if (allow !== undefined && allow.reason.trim().length > 0) {
|
|
1917
|
+
silenced.add(index);
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1920
|
+
hits.push({
|
|
1921
|
+
severity: "warning",
|
|
1922
|
+
code: REPRINT_CODE,
|
|
1923
|
+
place: `step ${String(index + 1)}`,
|
|
1924
|
+
reason: difference.reason
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
steps.forEach((step, index) => {
|
|
1928
|
+
for (const allow of step.allows ?? []) {
|
|
1929
|
+
if (allow.code !== REPRINT_CODE)
|
|
1930
|
+
continue;
|
|
1931
|
+
const place = `step ${String(index + 1)}`;
|
|
1932
|
+
if (allow.reason.trim().length === 0) {
|
|
1933
|
+
hits.push({
|
|
1934
|
+
severity: "error",
|
|
1935
|
+
code: "OKM1510",
|
|
1936
|
+
place,
|
|
1937
|
+
reason: "override needs a reason"
|
|
1938
|
+
});
|
|
1939
|
+
continue;
|
|
1940
|
+
}
|
|
1941
|
+
if (silenced.has(index))
|
|
1942
|
+
continue;
|
|
1943
|
+
if (hits.some((item) => item.place === place && item.code === REPRINT_CODE))
|
|
1944
|
+
continue;
|
|
1945
|
+
if (!statementMayReprint(step.sql))
|
|
1946
|
+
continue;
|
|
1947
|
+
hits.push({
|
|
1948
|
+
severity: "error",
|
|
1949
|
+
code: "OKM1510",
|
|
1950
|
+
place,
|
|
1951
|
+
reason: `override ${REPRINT_CODE} does not match this statement`
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
});
|
|
1955
|
+
return hits;
|
|
1956
|
+
}
|
|
1957
|
+
function differences(declared, stored) {
|
|
1958
|
+
const found = [];
|
|
1959
|
+
const storedDomains = domainChecks(stored);
|
|
1960
|
+
for (const [name, check] of domainChecks(declared)) {
|
|
1961
|
+
const live = storedDomains.get(name);
|
|
1962
|
+
if (live === undefined || live === check)
|
|
1963
|
+
continue;
|
|
1964
|
+
found.push({
|
|
1965
|
+
kind: "domain",
|
|
1966
|
+
name,
|
|
1967
|
+
reason: `domain ${name} check reprints differently`
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
const storedFunctions = sqlBodies(stored);
|
|
1971
|
+
for (const [key, body] of sqlBodies(declared)) {
|
|
1972
|
+
const live = storedFunctions.get(key);
|
|
1973
|
+
if (live === undefined || live.body === body.body)
|
|
1974
|
+
continue;
|
|
1975
|
+
found.push({
|
|
1976
|
+
kind: "function",
|
|
1977
|
+
name: body.name,
|
|
1978
|
+
reason: `function ${body.name} body reprints differently`
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
return found;
|
|
1982
|
+
}
|
|
1983
|
+
function domainChecks(source) {
|
|
1984
|
+
const checks = new Map;
|
|
1985
|
+
for (const object of source.objects) {
|
|
1986
|
+
if (object.kind !== "type" || !isDomain(object.definition))
|
|
1987
|
+
continue;
|
|
1988
|
+
checks.set(object.identity.name, object.definition.check.trim());
|
|
1989
|
+
}
|
|
1990
|
+
return checks;
|
|
1991
|
+
}
|
|
1992
|
+
function sqlBodies(source) {
|
|
1993
|
+
const bodies = new Map;
|
|
1994
|
+
for (const object of source.objects) {
|
|
1995
|
+
if (object.kind !== "function" || object.definition.language !== "sql")
|
|
1996
|
+
continue;
|
|
1997
|
+
bodies.set(functionKey(object), {
|
|
1998
|
+
name: object.identity.name,
|
|
1999
|
+
body: object.definition.body.trim()
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
return bodies;
|
|
2003
|
+
}
|
|
2004
|
+
function functionKey(object) {
|
|
2005
|
+
const args = object.identity.argTypes.map((typeName) => canonicalTypeName(typeName)).join(",");
|
|
2006
|
+
return `${object.identity.name}(${args})`;
|
|
2007
|
+
}
|
|
2008
|
+
function stepIndex(steps, kind, name) {
|
|
2009
|
+
let found;
|
|
2010
|
+
steps.forEach((step, index) => {
|
|
2011
|
+
if (mentions(normalizedSql(step.sql), kind, name))
|
|
2012
|
+
found = index;
|
|
2013
|
+
});
|
|
2014
|
+
return found;
|
|
2015
|
+
}
|
|
2016
|
+
function mentions(text, kind, name) {
|
|
2017
|
+
if (kind === "domain" && !/^(?:create|alter) domain\b/.test(text))
|
|
2018
|
+
return false;
|
|
2019
|
+
if (kind === "function") {
|
|
2020
|
+
if (!/^create(?: or replace)? function\b/.test(text) || !/\blanguage sql\b/.test(text)) {
|
|
2021
|
+
return false;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
const quoted = `"${name}"`;
|
|
2025
|
+
if (text.includes(quoted))
|
|
2026
|
+
return true;
|
|
2027
|
+
return new RegExp(`\\b${escapeRegExp(name.toLowerCase())}\\b`).test(text);
|
|
2028
|
+
}
|
|
2029
|
+
function escapeRegExp(value) {
|
|
2030
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2031
|
+
}
|
|
2032
|
+
|
|
1383
2033
|
// src/tooling/migrate/lint.ts
|
|
1384
2034
|
function lintPlan(plan, before, after) {
|
|
1385
2035
|
const existingTables = tableNames(before);
|
|
@@ -1389,6 +2039,9 @@ function lintPlan(plan, before, after) {
|
|
|
1389
2039
|
});
|
|
1390
2040
|
return findings;
|
|
1391
2041
|
}
|
|
2042
|
+
function lintReprints(declared, stored, steps) {
|
|
2043
|
+
return reprintHits(declared, stored, steps).map((hit) => finding(hit.severity, hit.code, hit.place, hit.reason));
|
|
2044
|
+
}
|
|
1392
2045
|
function lintCatalog(source) {
|
|
1393
2046
|
const findings = [];
|
|
1394
2047
|
for (const object of source.objects) {
|
|
@@ -1404,15 +2057,15 @@ function lintCatalog(source) {
|
|
|
1404
2057
|
return findings;
|
|
1405
2058
|
}
|
|
1406
2059
|
function lintMigrationDirectory(directory, pending) {
|
|
1407
|
-
if (!
|
|
2060
|
+
if (!existsSync3(directory))
|
|
1408
2061
|
return [];
|
|
1409
2062
|
const names = readdirSync2(directory).filter((file) => file.endsWith(".sql")).sort();
|
|
1410
2063
|
let before = catalog([]);
|
|
1411
2064
|
const findings = [];
|
|
1412
2065
|
for (const file of names) {
|
|
1413
2066
|
const id = file.slice(0, -".sql".length);
|
|
1414
|
-
const plan = readMigrationPlan(
|
|
1415
|
-
const after = parseCatalog2(
|
|
2067
|
+
const plan = readMigrationPlan(join3(directory, file));
|
|
2068
|
+
const after = parseCatalog2(readFileSync3(join3(directory, `${id}.catalog.json`), "utf8"));
|
|
1416
2069
|
if (pending === undefined || pending.has(id)) {
|
|
1417
2070
|
for (const item of lintPlan(plan, before, after)) {
|
|
1418
2071
|
findings.push({ ...item, place: `${id} ${item.place}` });
|
|
@@ -1462,6 +2115,8 @@ function lintStep(step, number, before, after, existingTables, earlier, later) {
|
|
|
1462
2115
|
triggered.add(hit.code);
|
|
1463
2116
|
}
|
|
1464
2117
|
}
|
|
2118
|
+
if (statementMayReprint(statement.sql))
|
|
2119
|
+
triggered.add(REPRINT_CODE);
|
|
1465
2120
|
const place = multi ? `step ${String(number)} statement ${String(index + 1)}` : `step ${String(number)}`;
|
|
1466
2121
|
perStatement.push({ place, hits });
|
|
1467
2122
|
});
|
|
@@ -1531,8 +2186,16 @@ function columnPlace(column) {
|
|
|
1531
2186
|
}
|
|
1532
2187
|
|
|
1533
2188
|
// src/tooling/migrate/project.ts
|
|
1534
|
-
import {
|
|
1535
|
-
|
|
2189
|
+
import {
|
|
2190
|
+
existsSync as existsSync4,
|
|
2191
|
+
mkdirSync,
|
|
2192
|
+
readdirSync as readdirSync3,
|
|
2193
|
+
readFileSync as readFileSync4,
|
|
2194
|
+
statSync,
|
|
2195
|
+
unlinkSync,
|
|
2196
|
+
writeFileSync as writeFileSync2
|
|
2197
|
+
} from "node:fs";
|
|
2198
|
+
import { basename, dirname, join as join4 } from "node:path";
|
|
1536
2199
|
import { pathToFileURL } from "node:url";
|
|
1537
2200
|
|
|
1538
2201
|
// src/dialects/pg/declarations.ts
|
|
@@ -2143,6 +2806,131 @@ function columnNames(target, built, table) {
|
|
|
2143
2806
|
return built.flatMap((object) => object.kind === "column" && object.identity.parent.name === table ? [object.identity.name] : []);
|
|
2144
2807
|
}
|
|
2145
2808
|
|
|
2809
|
+
// src/tooling/migrate/place.ts
|
|
2810
|
+
function placeCatalog(source, namespace, externalNamespaces) {
|
|
2811
|
+
if (namespace === "public" && externalNamespaces.size === 0)
|
|
2812
|
+
return source;
|
|
2813
|
+
const homes = tableHomes(source, namespace, externalNamespaces);
|
|
2814
|
+
let changed = false;
|
|
2815
|
+
const objects = source.objects.map((object) => {
|
|
2816
|
+
const next = relocateObject(object, homes, namespace);
|
|
2817
|
+
if (next !== object)
|
|
2818
|
+
changed = true;
|
|
2819
|
+
return next;
|
|
2820
|
+
});
|
|
2821
|
+
if (!changed)
|
|
2822
|
+
return source;
|
|
2823
|
+
return catalog(objects);
|
|
2824
|
+
}
|
|
2825
|
+
function tableHomes(source, namespace, externalNamespaces) {
|
|
2826
|
+
const homes = new Map;
|
|
2827
|
+
for (const object of source.objects) {
|
|
2828
|
+
if (object.kind !== "table" && object.kind !== "view" && object.kind !== "materializedView") {
|
|
2829
|
+
continue;
|
|
2830
|
+
}
|
|
2831
|
+
const home = object.owner === "external" ? externalNamespaces.get(object.identity.name) ?? namespace : namespace;
|
|
2832
|
+
homes.set(object.identity.name, home);
|
|
2833
|
+
}
|
|
2834
|
+
return homes;
|
|
2835
|
+
}
|
|
2836
|
+
function relocateObject(object, homes, namespace) {
|
|
2837
|
+
const dependencies = relocateEdges(object.dependencies, homes, namespace);
|
|
2838
|
+
if (object.kind === "extension") {
|
|
2839
|
+
if (object.definition.schema === "public" && namespace !== "public") {
|
|
2840
|
+
return { ...object, definition: { ...object.definition, schema: namespace }, dependencies };
|
|
2841
|
+
}
|
|
2842
|
+
return dependencies === object.dependencies ? object : { ...object, dependencies };
|
|
2843
|
+
}
|
|
2844
|
+
if (object.kind === "role") {
|
|
2845
|
+
return dependencies === object.dependencies ? object : { ...object, dependencies };
|
|
2846
|
+
}
|
|
2847
|
+
const identity = relocateIdentity(object.identity, homes, namespace);
|
|
2848
|
+
if (object.kind === "constraint") {
|
|
2849
|
+
const references = relocateReferences(object, homes);
|
|
2850
|
+
if (identity === object.identity && references === object.definition.references && dependencies === object.dependencies) {
|
|
2851
|
+
return object;
|
|
2852
|
+
}
|
|
2853
|
+
if (references === undefined || references === object.definition.references) {
|
|
2854
|
+
return { ...object, identity, dependencies };
|
|
2855
|
+
}
|
|
2856
|
+
return {
|
|
2857
|
+
...object,
|
|
2858
|
+
identity,
|
|
2859
|
+
definition: { ...object.definition, references },
|
|
2860
|
+
dependencies
|
|
2861
|
+
};
|
|
2862
|
+
}
|
|
2863
|
+
if (identity === object.identity && dependencies === object.dependencies)
|
|
2864
|
+
return object;
|
|
2865
|
+
return { ...object, identity, dependencies };
|
|
2866
|
+
}
|
|
2867
|
+
function relocateReferences(object, homes) {
|
|
2868
|
+
const references = object.definition.references;
|
|
2869
|
+
if (references === undefined)
|
|
2870
|
+
return;
|
|
2871
|
+
const home = homes.get(references.parent.name);
|
|
2872
|
+
if (home === undefined)
|
|
2873
|
+
return references;
|
|
2874
|
+
const parent = moveRef(references.parent, home);
|
|
2875
|
+
if (parent === references.parent)
|
|
2876
|
+
return references;
|
|
2877
|
+
return { ...references, parent };
|
|
2878
|
+
}
|
|
2879
|
+
function relocateEdges(edges, homes, namespace) {
|
|
2880
|
+
let changed = false;
|
|
2881
|
+
const next = edges.map((edge) => {
|
|
2882
|
+
const target = relocateIdentity(edge.target, homes, namespace);
|
|
2883
|
+
if (target === edge.target)
|
|
2884
|
+
return edge;
|
|
2885
|
+
changed = true;
|
|
2886
|
+
return { target };
|
|
2887
|
+
});
|
|
2888
|
+
return changed ? next : edges;
|
|
2889
|
+
}
|
|
2890
|
+
function relocateIdentity(identity, homes, namespace) {
|
|
2891
|
+
if (identity.kind === "role" || identity.kind === "extension")
|
|
2892
|
+
return identity;
|
|
2893
|
+
if (identity.kind === "column" || identity.kind === "index" || identity.kind === "constraint" || identity.kind === "trigger" || identity.kind === "policy") {
|
|
2894
|
+
const home = homes.get(identity.parent.name) ?? namespace;
|
|
2895
|
+
const parent = moveRef(identity.parent, home);
|
|
2896
|
+
if (parent === identity.parent)
|
|
2897
|
+
return identity;
|
|
2898
|
+
return { ...identity, parent };
|
|
2899
|
+
}
|
|
2900
|
+
if (identity.kind === "grant") {
|
|
2901
|
+
const home = homes.get(identity.object.name) ?? namespace;
|
|
2902
|
+
const moved = moveNamespace(identity.object.namespace, home);
|
|
2903
|
+
if (moved === identity.object.namespace)
|
|
2904
|
+
return identity;
|
|
2905
|
+
return { ...identity, object: { ...identity.object, namespace: moved } };
|
|
2906
|
+
}
|
|
2907
|
+
if (identity.kind === "defaultPrivilege") {
|
|
2908
|
+
const moved = moveNamespace(identity.namespace, namespace);
|
|
2909
|
+
if (moved === identity.namespace)
|
|
2910
|
+
return identity;
|
|
2911
|
+
return { ...identity, namespace: moved };
|
|
2912
|
+
}
|
|
2913
|
+
if (identity.kind === "table" || identity.kind === "view" || identity.kind === "materializedView" || identity.kind === "sequence" || identity.kind === "type" || identity.kind === "function") {
|
|
2914
|
+
const home = homes.get(identity.name) ?? namespace;
|
|
2915
|
+
const moved = moveNamespace(identity.namespace, home);
|
|
2916
|
+
if (moved === identity.namespace)
|
|
2917
|
+
return identity;
|
|
2918
|
+
return { ...identity, namespace: moved };
|
|
2919
|
+
}
|
|
2920
|
+
return identity;
|
|
2921
|
+
}
|
|
2922
|
+
function moveRef(ref, namespace) {
|
|
2923
|
+
const moved = moveNamespace(ref.namespace, namespace);
|
|
2924
|
+
if (moved === ref.namespace)
|
|
2925
|
+
return ref;
|
|
2926
|
+
return { ...ref, namespace: moved };
|
|
2927
|
+
}
|
|
2928
|
+
function moveNamespace(namespace, name) {
|
|
2929
|
+
if (namespace.form !== "static" || namespace.name === name)
|
|
2930
|
+
return namespace;
|
|
2931
|
+
return staticNamespace2(name);
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2146
2934
|
// src/tooling/migrate/estimate.ts
|
|
2147
2935
|
var LARGE_TABLE_ROWS = 1e6;
|
|
2148
2936
|
var ESTIMATE_SQL = `
|
|
@@ -2154,7 +2942,7 @@ var ESTIMATE_SQL = `
|
|
|
2154
2942
|
and c.relkind in ('r', 'p', 'f', 'm')
|
|
2155
2943
|
`;
|
|
2156
2944
|
async function readRowEstimates(url, schema, tables) {
|
|
2157
|
-
const pool = open2({ url, max: 1 });
|
|
2945
|
+
const pool = open2({ url: driverUrl(url), max: 1 });
|
|
2158
2946
|
try {
|
|
2159
2947
|
const connection = await pool.reserve?.();
|
|
2160
2948
|
if (connection === undefined) {
|
|
@@ -2336,11 +3124,35 @@ function trimZero(text) {
|
|
|
2336
3124
|
return text.endsWith(".0") ? text.slice(0, -2) : text;
|
|
2337
3125
|
}
|
|
2338
3126
|
|
|
3127
|
+
// src/tooling/migrate/plan-json.ts
|
|
3128
|
+
function planDocument(plan, findings, lockText) {
|
|
3129
|
+
return {
|
|
3130
|
+
name: plan.name,
|
|
3131
|
+
class: plan.class,
|
|
3132
|
+
steps: plan.steps.map((step) => ({
|
|
3133
|
+
class: step.class,
|
|
3134
|
+
lock: lockText === undefined ? step.lock : lockText(step),
|
|
3135
|
+
sql: step.sql
|
|
3136
|
+
})),
|
|
3137
|
+
findings: findings.map((finding) => ({
|
|
3138
|
+
severity: finding.severity,
|
|
3139
|
+
code: finding.code,
|
|
3140
|
+
place: finding.place,
|
|
3141
|
+
reason: finding.reason,
|
|
3142
|
+
fix: finding.fix
|
|
3143
|
+
}))
|
|
3144
|
+
};
|
|
3145
|
+
}
|
|
3146
|
+
function formatPlanJson(plan, findings, lockText) {
|
|
3147
|
+
return `${JSON.stringify(planDocument(plan, findings, lockText))}
|
|
3148
|
+
`;
|
|
3149
|
+
}
|
|
3150
|
+
|
|
2339
3151
|
// src/tooling/migrate/project.ts
|
|
2340
3152
|
async function buildProject(cwd) {
|
|
2341
3153
|
const config = await loadConfig(cwd);
|
|
2342
3154
|
const built = await loadSchema(cwd, config.schema);
|
|
2343
|
-
return writeArtifact(cwd, config, withRoles(built, config));
|
|
3155
|
+
return writeArtifact(cwd, config, withRoles(built, config, cwd));
|
|
2344
3156
|
}
|
|
2345
3157
|
async function generateProject(cwd, name, flags) {
|
|
2346
3158
|
const prepared = await prepare(cwd, name, flags);
|
|
@@ -2352,17 +3164,20 @@ async function generateProject(cwd, name, flags) {
|
|
|
2352
3164
|
const directory = projectPath(cwd, prepared.config.migrations ?? "migrations");
|
|
2353
3165
|
mkdirSync(directory, { recursive: true });
|
|
2354
3166
|
const base = `${nextNumber(directory)}_${safeName(name)}`;
|
|
2355
|
-
const sqlPath =
|
|
2356
|
-
|
|
2357
|
-
|
|
3167
|
+
const sqlPath = join4(directory, `${base}.sql`);
|
|
3168
|
+
writeFileSync2(sqlPath, formatPlan(prepared.plan));
|
|
3169
|
+
writeFileSync2(join4(directory, `${base}.catalog.json`), serializeCatalog2(prepared.built.catalog));
|
|
2358
3170
|
return { path: sqlPath, findings: lintPrepared(prepared) };
|
|
2359
3171
|
}
|
|
2360
3172
|
async function planProject(cwd, name, flags, invoke) {
|
|
2361
3173
|
const prepared = await prepare(cwd, name, flags);
|
|
3174
|
+
const findings = lintPrepared(prepared);
|
|
3175
|
+
const lockText = await planLockText(prepared.config, prepared.plan, invoke);
|
|
2362
3176
|
return {
|
|
2363
3177
|
plan: prepared.plan,
|
|
2364
|
-
findings
|
|
2365
|
-
text:
|
|
3178
|
+
findings,
|
|
3179
|
+
text: formatPlan(prepared.plan, lockText),
|
|
3180
|
+
json: formatPlanJson(prepared.plan, findings, lockText)
|
|
2366
3181
|
};
|
|
2367
3182
|
}
|
|
2368
3183
|
async function checkProject(cwd, flags) {
|
|
@@ -2378,8 +3193,8 @@ async function checkProject(cwd, flags) {
|
|
|
2378
3193
|
assertTargetAlias(targets);
|
|
2379
3194
|
if (opened.config.tables !== undefined) {
|
|
2380
3195
|
const directory = projectPath(cwd, opened.config.tables);
|
|
2381
|
-
const files =
|
|
2382
|
-
const source =
|
|
3196
|
+
const files = existsSync4(directory) ? readdirSync3(directory) : [];
|
|
3197
|
+
const source = readFileSync4(projectPath(cwd, opened.config.schema), "utf8");
|
|
2383
3198
|
const missing = unlistedTableFiles(source, files);
|
|
2384
3199
|
if (missing.length > 0) {
|
|
2385
3200
|
throw new OkmError2("OKM1024", `Table file not in schema: ${missing.join(", ")}.`, {
|
|
@@ -2387,7 +3202,8 @@ async function checkProject(cwd, flags) {
|
|
|
2387
3202
|
});
|
|
2388
3203
|
}
|
|
2389
3204
|
}
|
|
2390
|
-
|
|
3205
|
+
assertSnapshots(cwd, opened.config, opened.built.catalog);
|
|
3206
|
+
const findings = [...lintOpened(opened), ...couplingFindings(opened.built.catalog)];
|
|
2391
3207
|
if (hasError(findings))
|
|
2392
3208
|
throw lintRefusal(findings);
|
|
2393
3209
|
const named = flags?.target;
|
|
@@ -2398,8 +3214,9 @@ async function checkProject(cwd, flags) {
|
|
|
2398
3214
|
const target = selectTarget(opened.config, named);
|
|
2399
3215
|
assertTargetPolicy(target, "check", flags?.allowProtected ?? false);
|
|
2400
3216
|
assertTargetPolicy(target, "drift", flags?.allowProtected ?? false);
|
|
2401
|
-
const { assertAuthorDrift } = await import("./
|
|
3217
|
+
const { assertAuthorDrift } = await import("./rged92v6.js");
|
|
2402
3218
|
const allowPooler = flags?.allowPooler === true || opened.config.allowPooler === true;
|
|
3219
|
+
await assertLiveExternal(target.url, opened.built.catalog, loadSnapshots(cwd, opened.config));
|
|
2403
3220
|
await assertAuthorDrift(target.url, opened.built.catalog, opened.config.roles, allowPooler);
|
|
2404
3221
|
return findings;
|
|
2405
3222
|
}
|
|
@@ -2429,6 +3246,7 @@ async function prepare(cwd, name, flags) {
|
|
|
2429
3246
|
renames: opened.renames,
|
|
2430
3247
|
replacements: flags.map((flag) => parseReplace(flag)),
|
|
2431
3248
|
name,
|
|
3249
|
+
...placedSchema(opened.config),
|
|
2432
3250
|
...batchSizeField(opened.config.backfill?.batchSize)
|
|
2433
3251
|
});
|
|
2434
3252
|
return {
|
|
@@ -2442,26 +3260,25 @@ async function prepare(cwd, name, flags) {
|
|
|
2442
3260
|
function lintPrepared(prepared) {
|
|
2443
3261
|
return lintPlan(prepared.plan, prepared.before, prepared.after);
|
|
2444
3262
|
}
|
|
2445
|
-
async function
|
|
2446
|
-
const offline = formatPlan(plan);
|
|
3263
|
+
async function planLockText(config, plan, invoke) {
|
|
2447
3264
|
const targets = listTargets(config);
|
|
2448
3265
|
if (targets.length === 0)
|
|
2449
|
-
return
|
|
3266
|
+
return;
|
|
2450
3267
|
assertTargetAlias(targets);
|
|
2451
3268
|
const named = invoke?.target;
|
|
2452
3269
|
if (named === undefined && targets.length !== 1)
|
|
2453
|
-
return
|
|
3270
|
+
return;
|
|
2454
3271
|
const target = selectTarget(config, named);
|
|
2455
3272
|
assertTargetPolicy(target, "plan", invoke?.allowProtected ?? false);
|
|
2456
3273
|
let estimates;
|
|
2457
3274
|
try {
|
|
2458
3275
|
const names = plan.steps.flatMap((step) => step.tables ?? []);
|
|
2459
|
-
estimates = await readRowEstimates(target.url,
|
|
3276
|
+
estimates = await readRowEstimates(target.url, catalogNamespace(config), names);
|
|
2460
3277
|
} catch {
|
|
2461
|
-
return
|
|
3278
|
+
return;
|
|
2462
3279
|
}
|
|
2463
3280
|
const aliases = renameAliases(plan.steps);
|
|
2464
|
-
return
|
|
3281
|
+
return (step) => annotateLock(step, estimates, aliases);
|
|
2465
3282
|
}
|
|
2466
3283
|
function lintOpened(opened) {
|
|
2467
3284
|
const plan = planMigration({
|
|
@@ -2469,6 +3286,7 @@ function lintOpened(opened) {
|
|
|
2469
3286
|
after: opened.built.catalog,
|
|
2470
3287
|
renames: opened.renames,
|
|
2471
3288
|
name: "check",
|
|
3289
|
+
...placedSchema(opened.config),
|
|
2472
3290
|
...batchSizeField(opened.config.backfill?.batchSize)
|
|
2473
3291
|
});
|
|
2474
3292
|
return [
|
|
@@ -2478,7 +3296,7 @@ function lintOpened(opened) {
|
|
|
2478
3296
|
}
|
|
2479
3297
|
async function openProject(cwd) {
|
|
2480
3298
|
const config = await loadConfig(cwd);
|
|
2481
|
-
const built =
|
|
3299
|
+
const built = await loadBuiltSchema(cwd, config);
|
|
2482
3300
|
const declarations = schemaDeclarations(built);
|
|
2483
3301
|
const previous = readPrevious(projectPath(cwd, config.migrations ?? "migrations"));
|
|
2484
3302
|
const stale = staleRenames(previous?.catalog, declarations.renames);
|
|
@@ -2494,15 +3312,22 @@ async function openProject(cwd) {
|
|
|
2494
3312
|
renames: declarations.renames
|
|
2495
3313
|
};
|
|
2496
3314
|
}
|
|
2497
|
-
function withRoles(built, config) {
|
|
3315
|
+
function withRoles(built, config, cwd) {
|
|
2498
3316
|
const marks = ownershipMarks(built);
|
|
2499
3317
|
const stamped = marks.size === 0 ? built.catalog : stampOwnership(built.catalog, marks);
|
|
3318
|
+
const placed = placeCatalog(stamped, catalogNamespace(config), externalNamespaceMap(loadSnapshots(cwd, config)));
|
|
2500
3319
|
const roles = config.roles;
|
|
2501
|
-
const catalog = roles === undefined ?
|
|
3320
|
+
const catalog = roles === undefined ? placed : attachRoles(placed, roles, exposureFor(built, roles));
|
|
2502
3321
|
if (catalog === built.catalog)
|
|
2503
3322
|
return built;
|
|
2504
3323
|
return { ...built, catalog };
|
|
2505
3324
|
}
|
|
3325
|
+
function placedSchema(config) {
|
|
3326
|
+
const namespace = catalogNamespace(config);
|
|
3327
|
+
if (namespace === "public")
|
|
3328
|
+
return;
|
|
3329
|
+
return { schema: namespace };
|
|
3330
|
+
}
|
|
2506
3331
|
function ownershipMarks(built) {
|
|
2507
3332
|
const marks = new Map;
|
|
2508
3333
|
const models = built.model;
|
|
@@ -2539,9 +3364,9 @@ var GIT_ATTRIBUTES = [
|
|
|
2539
3364
|
`);
|
|
2540
3365
|
function writeGitAttributes(cwd) {
|
|
2541
3366
|
const path = projectPath(cwd, ".gitattributes");
|
|
2542
|
-
if (
|
|
3367
|
+
if (existsSync4(path))
|
|
2543
3368
|
return;
|
|
2544
|
-
|
|
3369
|
+
writeFileSync2(path, GIT_ATTRIBUTES);
|
|
2545
3370
|
}
|
|
2546
3371
|
function typesText(cwd, spec, built) {
|
|
2547
3372
|
return `${emitRowTypes(built, jsonColumnTypes(readSchemaSource(cwd, spec)))}${referenceAugmentation(built)}`;
|
|
@@ -2550,7 +3375,7 @@ function readSchemaSource(cwd, spec) {
|
|
|
2550
3375
|
const parts = [];
|
|
2551
3376
|
const seen = new Set;
|
|
2552
3377
|
const visit = (file) => {
|
|
2553
|
-
if (seen.has(file) || !
|
|
3378
|
+
if (seen.has(file) || !existsSync4(file))
|
|
2554
3379
|
return;
|
|
2555
3380
|
let stat;
|
|
2556
3381
|
try {
|
|
@@ -2561,16 +3386,16 @@ function readSchemaSource(cwd, spec) {
|
|
|
2561
3386
|
if (!stat.isFile())
|
|
2562
3387
|
return;
|
|
2563
3388
|
seen.add(file);
|
|
2564
|
-
const text =
|
|
3389
|
+
const text = readFileSync4(file, "utf8");
|
|
2565
3390
|
parts.push(text);
|
|
2566
3391
|
for (const match of text.matchAll(/from\s+["'](\.[^"']+)["']/g)) {
|
|
2567
3392
|
const rel = match[1];
|
|
2568
3393
|
if (rel === undefined)
|
|
2569
3394
|
continue;
|
|
2570
|
-
const base =
|
|
2571
|
-
const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}.mts`,
|
|
3395
|
+
const base = join4(dirname(file), rel);
|
|
3396
|
+
const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}.mts`, join4(base, "index.ts")];
|
|
2572
3397
|
for (const candidate of candidates) {
|
|
2573
|
-
if (
|
|
3398
|
+
if (existsSync4(candidate)) {
|
|
2574
3399
|
visit(candidate);
|
|
2575
3400
|
break;
|
|
2576
3401
|
}
|
|
@@ -2586,19 +3411,35 @@ function writeArtifact(cwd, config, built) {
|
|
|
2586
3411
|
mkdirSync(directory, { recursive: true });
|
|
2587
3412
|
const startup = startupCatalog2(built.catalog);
|
|
2588
3413
|
const text = catalogFileText2(serializeCatalog2(startup));
|
|
2589
|
-
|
|
2590
|
-
|
|
3414
|
+
writeFileSync2(join4(directory, "catalog.json"), text);
|
|
3415
|
+
writeFileSync2(join4(directory, "catalog.hash"), `${sha2562(text)}
|
|
2591
3416
|
`);
|
|
2592
|
-
|
|
3417
|
+
writeFileSync2(join4(directory, "types.d.ts"), typesText(cwd, config.schema, built));
|
|
3418
|
+
writeNamespace(directory, catalogNamespace(config));
|
|
2593
3419
|
return directory;
|
|
2594
3420
|
}
|
|
3421
|
+
function writeNamespace(directory, namespace) {
|
|
3422
|
+
const path = join4(directory, "namespace");
|
|
3423
|
+
if (namespace === "public") {
|
|
3424
|
+
if (existsSync4(path))
|
|
3425
|
+
unlinkSync(path);
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3428
|
+
writeFileSync2(path, `${namespace}
|
|
3429
|
+
`);
|
|
3430
|
+
}
|
|
2595
3431
|
async function loadBuiltSchema(cwd, config) {
|
|
2596
|
-
return withRoles(await loadSchema(cwd, config.schema), config);
|
|
3432
|
+
return withRoles(await loadSchema(cwd, config.schema), config, cwd);
|
|
3433
|
+
}
|
|
3434
|
+
async function exportProject(cwd, args) {
|
|
3435
|
+
const config = await loadConfig(cwd);
|
|
3436
|
+
const built = await loadBuiltSchema(cwd, config);
|
|
3437
|
+
return renderExport(cwd, config, built.catalog, args);
|
|
2597
3438
|
}
|
|
2598
3439
|
async function loadConfig(cwd) {
|
|
2599
3440
|
const path = projectPath(cwd, "okmodel.config.ts");
|
|
2600
3441
|
const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
|
|
2601
|
-
const record =
|
|
3442
|
+
const record = isRecord2(imported) ? imported : {};
|
|
2602
3443
|
const config = record.default ?? record.config;
|
|
2603
3444
|
if (!isConfig(config)) {
|
|
2604
3445
|
throw new OkmError2("invalid", "okmodel.config.ts must default-export defineConfig(...).");
|
|
@@ -2608,7 +3449,7 @@ async function loadConfig(cwd) {
|
|
|
2608
3449
|
async function loadSchema(cwd, spec) {
|
|
2609
3450
|
const path = projectPath(cwd, spec);
|
|
2610
3451
|
const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
|
|
2611
|
-
const record =
|
|
3452
|
+
const record = isRecord2(imported) ? imported : {};
|
|
2612
3453
|
for (const value of Object.values(record)) {
|
|
2613
3454
|
if (!isBuilt(value))
|
|
2614
3455
|
continue;
|
|
@@ -2618,13 +3459,13 @@ async function loadSchema(cwd, spec) {
|
|
|
2618
3459
|
throw new OkmError2("invalid", `${spec} must export a schema().`);
|
|
2619
3460
|
}
|
|
2620
3461
|
function readPrevious(directory) {
|
|
2621
|
-
if (!
|
|
3462
|
+
if (!existsSync4(directory))
|
|
2622
3463
|
return;
|
|
2623
3464
|
const catalogs = readdirSync3(directory).filter((file) => file.endsWith(".catalog.json")).sort();
|
|
2624
3465
|
const latest = catalogs.at(-1);
|
|
2625
3466
|
if (latest === undefined)
|
|
2626
3467
|
return;
|
|
2627
|
-
const text =
|
|
3468
|
+
const text = readFileSync4(join4(directory, latest), "utf8");
|
|
2628
3469
|
return { catalog: parseCatalog2(text) };
|
|
2629
3470
|
}
|
|
2630
3471
|
function referenceAugmentation(source) {
|
|
@@ -2649,7 +3490,7 @@ function referenceAugmentation(source) {
|
|
|
2649
3490
|
}
|
|
2650
3491
|
function nextNumber(directory) {
|
|
2651
3492
|
let max = 0;
|
|
2652
|
-
if (
|
|
3493
|
+
if (existsSync4(directory)) {
|
|
2653
3494
|
for (const file of readdirSync3(directory)) {
|
|
2654
3495
|
const match = /^(\d+)_/.exec(file);
|
|
2655
3496
|
const value = match?.[1];
|
|
@@ -2666,14 +3507,14 @@ function safeName(name) {
|
|
|
2666
3507
|
return name;
|
|
2667
3508
|
}
|
|
2668
3509
|
function isBuilt(value) {
|
|
2669
|
-
if (!
|
|
3510
|
+
if (!isRecord2(value) || !Array.isArray(value.tables))
|
|
2670
3511
|
return false;
|
|
2671
3512
|
return "catalog" in value;
|
|
2672
3513
|
}
|
|
2673
3514
|
function isConfig(value) {
|
|
2674
|
-
return
|
|
3515
|
+
return isRecord2(value) && typeof value.schema === "string";
|
|
2675
3516
|
}
|
|
2676
|
-
function
|
|
3517
|
+
function isRecord2(value) {
|
|
2677
3518
|
return typeof value === "object" && value !== null;
|
|
2678
3519
|
}
|
|
2679
3520
|
var SKIP_SCAN = new Set(["node_modules", "dist", "coverage"]);
|
|
@@ -2698,7 +3539,7 @@ function directoryImportsValidate(dir) {
|
|
|
2698
3539
|
for (const entry of entries) {
|
|
2699
3540
|
if (entry.name.startsWith(".") || SKIP_SCAN.has(entry.name))
|
|
2700
3541
|
continue;
|
|
2701
|
-
const path =
|
|
3542
|
+
const path = join4(dir, entry.name);
|
|
2702
3543
|
if (entry.isDirectory()) {
|
|
2703
3544
|
if (directoryImportsValidate(path))
|
|
2704
3545
|
return true;
|
|
@@ -2706,7 +3547,7 @@ function directoryImportsValidate(dir) {
|
|
|
2706
3547
|
}
|
|
2707
3548
|
if (!/\.(?:ts|tsx|mts|cts)$/.test(entry.name))
|
|
2708
3549
|
continue;
|
|
2709
|
-
if (runtimeImportsValidate(
|
|
3550
|
+
if (runtimeImportsValidate(readFileSync4(path, "utf8")))
|
|
2710
3551
|
return true;
|
|
2711
3552
|
}
|
|
2712
3553
|
return false;
|
|
@@ -2728,4 +3569,4 @@ function runtimeImportsValidate(source) {
|
|
|
2728
3569
|
return false;
|
|
2729
3570
|
}
|
|
2730
3571
|
|
|
2731
|
-
export { projectPath, errorDoc, loadMigrations, readMigrationPlan, loadHeadSnapshot, lintMigrationDirectory, formatFindings, hasError, lintRefusal, buildProject, generateProject, planProject, checkProject, projectHead, openProject, loadBuiltSchema, loadConfig, loadSchema };
|
|
3572
|
+
export { projectPath, LOGICAL_FORMAT, catalogNamespace, loadSnapshots, assertSnapshots, assertLiveExternal, errorDoc, loadMigrations, readMigrationPlan, loadHeadSnapshot, lintReprints, lintMigrationDirectory, formatFindings, hasError, lintRefusal, buildProject, generateProject, planProject, checkProject, projectHead, openProject, loadBuiltSchema, exportProject, loadConfig, loadSchema };
|