okmodel 0.7.0 → 0.9.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 +14 -0
- package/dist/adapters/capabilities.d.ts +32 -0
- package/dist/adapters/failure.d.ts +1 -1
- package/dist/adapters/pg/bun-sql.d.ts +53 -0
- package/dist/adapters/pg/bunsql-extra.d.ts +1 -1
- package/dist/adapters/pg/bunsql.d.ts +1 -1
- package/dist/adapters/pg/neon-extra.d.ts +18 -0
- package/dist/adapters/pg/neon.d.ts +104 -0
- package/dist/adapters/pg/no-inflight-timeout.d.ts +1 -1
- package/dist/contracts/error.d.ts +1 -1
- 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/fn/index.d.ts +39 -1
- package/dist/dialects/pg/fn/index.js +118 -26
- package/dist/dialects/pg/index.d.ts +3 -0
- package/dist/dialects/pg/index.js +394 -14
- package/dist/dialects/pg/introspect.d.ts +11 -0
- package/dist/dialects/pg/operators.d.ts +1 -1
- package/dist/dialects/pg/ops/fragment.d.ts +119 -0
- package/dist/dialects/pg/ops/lane.d.ts +49 -0
- package/dist/dialects/pg/view/client.d.ts +14 -6
- package/dist/dialects/pg/view/index.d.ts +73 -1
- package/dist/dialects/pg/view/index.js +156 -5
- package/dist/dialects/pg/view/scratch.d.ts +11 -5
- 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/otel.d.ts +105 -0
- package/dist/runtime/otel.js +97 -0
- package/dist/runtime/pg/bun.js +4 -4
- package/dist/runtime/pg/neon.d.ts +38 -0
- package/dist/runtime/pg/neon.js +64 -0
- package/dist/runtime/pg/pg.js +4 -4
- package/dist/runtime/pg/pglite.js +4 -4
- package/dist/runtime/pg/postgresjs.js +4 -4
- package/dist/runtime/plan.d.ts +1 -1
- package/dist/runtime/server-check.d.ts +36 -6
- package/dist/runtime/sql-fragment.d.ts +43 -0
- package/dist/runtime/tenancy/index.js +10 -10
- package/dist/runtime/topology.d.ts +1 -1
- package/dist/runtime/traits/index.js +16 -17
- package/dist/runtime/types.d.ts +15 -1
- package/dist/runtime/validate/index.js +4 -4
- package/dist/shared/{81ebdx9p.js → 0j6qe91y.js} +4 -4
- package/dist/shared/{qq3899ht.js → 12z7aznj.js} +243 -14
- package/dist/shared/{gzaveazc.js → 1d9q9cv2.js} +29 -2
- package/dist/shared/{w79hsckn.js → 1mxm04fy.js} +5 -5
- package/dist/shared/{7k9grvf6.js → 2szgv6yj.js} +5 -5
- package/dist/shared/{gs74dbj5.js → 2zdxkt7b.js} +933 -111
- package/dist/shared/{m6p12kbr.js → 3456242y.js} +23 -23
- package/dist/shared/3bb7wsqt.js +99 -0
- package/dist/shared/{y6njgyda.js → 3t6kw83m.js} +2 -2
- package/dist/shared/{xhfsfkgk.js → 4vbf83xx.js} +9 -2
- package/dist/shared/{pt94kz7f.js → 5kyb3tqx.js} +1 -1
- package/dist/shared/61bmrmhr.js +35 -0
- package/dist/shared/{x933k9a3.js → 62b603gr.js} +10 -10
- package/dist/shared/{jgqj11w4.js → 6f0z3hzh.js} +4 -0
- package/dist/shared/{tsvww22q.js → 6nae0spm.js} +156 -95
- package/dist/shared/{mtaszv5a.js → 71n0jmhc.js} +102 -4
- package/dist/shared/{5e3tf63j.js → 760ppr76.js} +14 -6
- package/dist/shared/7ba5h6pb.js +17 -0
- package/dist/shared/{sfz1ksn6.js → 8jesqsrk.js} +2 -2
- package/dist/shared/{5v3xf80k.js → 8n688yjg.js} +1 -1
- package/dist/shared/{rs73fca9.js → 8qzkd9f3.js} +6 -6
- package/dist/shared/{ax7qv55k.js → 90sg0w5a.js} +202 -25
- package/dist/shared/{zm0ptry0.js → 99qp9vtg.js} +1 -1
- package/dist/shared/{xnxsed62.js → 9dr2qnx7.js} +14 -14
- package/dist/shared/{1w508jbr.js → 9dtgn1z2.js} +21 -17
- package/dist/shared/{h15jx9bc.js → 9dvh1dr6.js} +1 -1
- package/dist/shared/{q5c6ngn3.js → a62rb2da.js} +1 -1
- package/dist/shared/{te5as7z7.js → aksb89mn.js} +3 -3
- package/dist/shared/{bz84tkzm.js → bphrgwz9.js} +2 -2
- package/dist/shared/bsek72fg.js +225 -0
- package/dist/shared/{7mnme5tn.js → c3mvnq7a.js} +1 -1
- package/dist/shared/{p6qc5gcp.js → ccsmhv9r.js} +25 -7
- package/dist/shared/{pbp1ss85.js → cexb7z6b.js} +2 -2
- package/dist/shared/{ab9gmbwy.js → cf8zdz5p.js} +5 -5
- package/dist/shared/{jvd0mqag.js → ckz05z15.js} +5 -5
- package/dist/shared/{dk8v79p3.js → d5nmj7vv.js} +1 -1
- package/dist/shared/{14vx95q4.js → dd5pmnky.js} +7 -7
- package/dist/shared/{vb6qfyve.js → ds229x4r.js} +2 -2
- package/dist/shared/{1qa0qajz.js → e275q7sd.js} +1 -1
- package/dist/shared/{zp9fmend.js → ebhz8vh6.js} +4 -4
- package/dist/shared/{hyh73qk6.js → ecn8mfdq.js} +5 -5
- package/dist/shared/{je4axnm8.js → fdsz3w80.js} +2 -2
- package/dist/shared/{9s0ypp87.js → fk9vef30.js} +85 -74
- package/dist/shared/{g6evj1j3.js → g89fmz5s.js} +1 -1
- package/dist/shared/{aytqh5y2.js → ghqzdp94.js} +2 -2
- package/dist/shared/h9811jjm.js +17 -0
- package/dist/shared/{dkxgzaqd.js → hqpxf4a4.js} +1 -1
- package/dist/shared/{8skf0cam.js → jpybhf98.js} +1 -1
- package/dist/shared/{67txcrcw.js → kgwp4jdd.js} +4 -4
- package/dist/shared/kzgrnckd.js +29 -0
- package/dist/shared/mazpbcdj.js +233 -0
- package/dist/shared/{kb2sdhfb.js → mfvx6kjx.js} +17 -1
- package/dist/shared/{svncdeh5.js → mgaq2sd3.js} +11 -8
- package/dist/shared/{5mtf6cj3.js → mvmm3xpb.js} +5 -2
- package/dist/shared/{b5pftnn2.js → myk8fswx.js} +66 -21
- package/dist/shared/{2shdbn8q.js → n306gqkd.js} +9 -9
- package/dist/shared/{n816peh1.js → n454kcnw.js} +1 -1
- package/dist/shared/n5whk3mt.js +665 -0
- package/dist/shared/n7kg8mmx.js +61 -0
- package/dist/shared/{vqrjpcqn.js → nmnsgj7x.js} +1 -1
- package/dist/shared/{tbrvgw6z.js → npdftanb.js} +19 -26
- package/dist/shared/{ek2wt2dz.js → pg4ezgyf.js} +11 -30
- package/dist/shared/{5fe3kbsh.js → pgsak1yw.js} +1 -1
- package/dist/shared/{bm22fvzr.js → qfw14tna.js} +2 -2
- package/dist/shared/{wa07eas9.js → qvwwp1mp.js} +62 -41
- package/dist/shared/rbxt795q.js +1766 -0
- package/dist/shared/{xjk7hnn5.js → s7he5e5d.js} +2 -2
- package/dist/shared/{w72kgg4q.js → ss2kv2rs.js} +6 -6
- package/dist/shared/stfa9xcw.js +450 -0
- package/dist/shared/{3v7t1yrv.js → th3be82n.js} +2 -2
- package/dist/shared/{79dt02vc.js → tr64w5p0.js} +1 -1
- package/dist/shared/{cn957f8d.js → tw1nvvfj.js} +3 -3
- package/dist/shared/{ed0b8k4s.js → typ3vy7t.js} +1 -1
- package/dist/shared/{9628a9xt.js → vk7rag46.js} +1 -1
- package/dist/shared/{ze4hxsmy.js → w9ejzaqf.js} +1 -1
- package/dist/shared/{vczs8gd4.js → wvtjg5m3.js} +2 -2
- package/dist/shared/{9y3mmmh1.js → xfhe73sb.js} +1 -1
- package/dist/shared/xqacq37x.js +61 -0
- package/dist/shared/{1est4ezg.js → xtsydxjg.js} +3 -3
- package/dist/shared/xv8qj97g.js +96 -0
- package/dist/shared/{bg3rym3g.js → xwhdcrm4.js} +2 -2
- package/dist/shared/{kr0jzbw2.js → yvabyg9k.js} +7 -7
- package/dist/shared/yybhre6n.js +136 -0
- package/dist/shared/zdqf8k5d.js +8 -0
- package/dist/shared/{jznfax0y.js → zq1he88x.js} +4 -4
- package/dist/tooling/cli.js +108 -23
- package/dist/tooling/driver-run.js +4654 -0
- package/dist/tooling/driver-test.d.ts +36 -0
- package/dist/tooling/github/comment.d.ts +50 -0
- package/dist/tooling/github/post-comment.d.ts +17 -0
- package/dist/tooling/migrate/commands.d.ts +3 -2
- package/dist/tooling/migrate/config.d.ts +13 -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 +55 -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/project.d.ts +13 -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 +62 -0
- package/dist/tooling/migrate/reprint.d.ts +32 -0
- 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/04svnxxj.js +344 -0
- package/dist/tooling/shared/0j0jtse1.js +13 -0
- package/dist/tooling/shared/0r05zhyq.js +618 -0
- package/dist/tooling/shared/1800eymw.js +49 -0
- package/dist/tooling/shared/35zemrmf.js +12 -0
- package/dist/tooling/shared/3rmaz3pq.js +140 -0
- package/dist/tooling/shared/3yn79q5m.js +12 -0
- package/dist/tooling/shared/54tqkxja.js +51 -0
- package/dist/tooling/shared/57btdkgm.js +209 -0
- package/dist/tooling/shared/5bmksndh.js +118 -0
- package/dist/tooling/shared/5p8km5jb.js +38 -0
- package/dist/tooling/shared/5x3pf400.js +185 -0
- package/dist/tooling/shared/5zrzffnk.js +6 -0
- package/dist/tooling/shared/62fxpc1p.js +332 -0
- package/dist/tooling/shared/68tf18v4.js +20 -0
- package/dist/tooling/shared/6vk28rjp.js +98 -0
- package/dist/tooling/shared/76pv6qff.js +9 -0
- package/dist/tooling/shared/7aews1zg.js +311 -0
- package/dist/tooling/shared/7b3xp9gc.js +195 -0
- package/dist/tooling/shared/7kms6fvj.js +20 -0
- package/dist/tooling/shared/7pxncnzs.js +40 -0
- package/dist/tooling/shared/829fjxyw.js +198 -0
- package/dist/tooling/shared/8m5znpdt.js +66 -0
- package/dist/tooling/shared/8yr7ges1.js +211 -0
- package/dist/tooling/shared/96ps4g1z.js +32 -0
- package/dist/tooling/shared/9qr9rkbh.js +143 -0
- package/dist/tooling/shared/9sfbtwsk.js +49 -0
- package/dist/tooling/shared/9wsnsvbf.js +10 -0
- package/dist/tooling/shared/a2xe04bm.js +18 -0
- package/dist/tooling/shared/a4pzghha.js +20 -0
- package/dist/tooling/shared/am04v8na.js +233 -0
- package/dist/tooling/shared/asgr07mp.js +194 -0
- package/dist/tooling/shared/c0xevn1h.js +312 -0
- package/dist/tooling/shared/dhatassp.js +134 -0
- package/dist/tooling/shared/ezqn88nj.js +1696 -0
- package/dist/tooling/shared/f77nnfqy.js +231 -0
- package/dist/tooling/shared/fh07z1dt.js +15 -0
- package/dist/tooling/shared/gqwevek4.js +81 -0
- package/dist/tooling/shared/hfq3n3bd.js +4116 -0
- package/dist/tooling/shared/htnaj77v.js +17 -0
- package/dist/tooling/shared/j664qjrt.js +64 -0
- package/dist/tooling/shared/jpgmjsx3.js +38 -0
- package/dist/tooling/shared/jr2nmr81.js +497 -0
- package/dist/tooling/shared/jsjjz875.js +139 -0
- package/dist/tooling/shared/kbqnrs1x.js +1425 -0
- package/dist/tooling/shared/kz715295.js +94 -0
- package/dist/tooling/shared/m3xt8b1h.js +22 -0
- package/dist/tooling/shared/m4hhb8aa.js +9 -0
- package/dist/tooling/shared/mw5npye0.js +16 -0
- package/dist/tooling/shared/n4jqhpqz.js +27 -0
- package/dist/tooling/shared/nxq95eb6.js +42 -0
- package/dist/tooling/shared/qartgazh.js +70 -0
- package/dist/tooling/shared/rcywj2w8.js +190 -0
- package/dist/tooling/shared/s6qha57m.js +16 -0
- package/dist/tooling/shared/shjs0379.js +150 -0
- package/dist/tooling/shared/sjwkt1pg.js +16 -0
- package/dist/tooling/shared/stebqyyd.js +401 -0
- package/dist/tooling/shared/t4xveemq.js +250 -0
- package/dist/tooling/shared/t6zjeake.js +261 -0
- package/dist/tooling/shared/tcwpjvxe.js +9 -0
- package/dist/tooling/shared/tpssp7ac.js +160 -0
- package/dist/tooling/shared/v6j1ep6w.js +42 -0
- package/dist/tooling/shared/vwr2y833.js +270 -0
- package/dist/tooling/shared/w17sj05d.js +7 -0
- package/dist/tooling/shared/w5d89rb4.js +131 -0
- package/dist/tooling/shared/wbwq1jsa.js +157 -0
- package/dist/tooling/shared/wezxf3h8.js +17 -0
- package/dist/tooling/shared/yb4kd19s.js +12 -0
- package/dist/tooling/shared/ybdew6j7.js +294 -0
- package/dist/tooling/shared/yfedr1zb.js +6 -0
- package/dist/tooling/shared/yhp829bx.js +177 -0
- package/dist/tooling/shared/ytk1tjs0.js +604 -0
- package/dist/tooling/shared/zs9aymcp.js +210 -0
- package/dist/tooling/shared/zx3v9phv.js +8 -0
- package/dist/tooling/testing/index.js +3 -3
- package/package.json +50 -9
- package/dist/shared/ktz42yez.js +0 -113
- package/dist/shared/ttsqgp4p.js +0 -71
- package/dist/shared/y34a8714.js +0 -150
- package/dist/shared/zy8r7qvz.js +0 -116
- package/dist/shared/{ywr8hmey.js → 606g0nbf.js} +3 -3
- package/dist/shared/{rpycarcr.js → gcefca6p.js} +4 -4
|
@@ -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,12 +5,20 @@ import {
|
|
|
9
5
|
serializeCatalog2,
|
|
10
6
|
catalogHash2,
|
|
11
7
|
startupCatalog2,
|
|
8
|
+
startupHash2,
|
|
12
9
|
catalogFileText2,
|
|
13
10
|
parseCatalog2
|
|
14
|
-
} from "./
|
|
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 "./dd5pmnky.js";
|
|
18
22
|
import {
|
|
19
23
|
sqlStatements,
|
|
20
24
|
transactionControl,
|
|
@@ -35,14 +39,14 @@ import {
|
|
|
35
39
|
assertTargetAlias,
|
|
36
40
|
listTargets,
|
|
37
41
|
selectTarget
|
|
38
|
-
} from "./
|
|
42
|
+
} from "./6nae0spm.js";
|
|
39
43
|
import {
|
|
40
44
|
contributedFields
|
|
41
45
|
} from "./n0dgs0pb.js";
|
|
42
46
|
import {
|
|
43
47
|
assertValidation,
|
|
44
48
|
writeWouldValidate
|
|
45
|
-
} from "./
|
|
49
|
+
} from "./w9ejzaqf.js";
|
|
46
50
|
import {
|
|
47
51
|
tableOwner,
|
|
48
52
|
stampOwnership
|
|
@@ -51,17 +55,17 @@ import {
|
|
|
51
55
|
POSTGRES_FLOOR,
|
|
52
56
|
uuidv7NeedsPostgres
|
|
53
57
|
} from "./ga9j92qj.js";
|
|
54
|
-
import {
|
|
55
|
-
catalog,
|
|
56
|
-
isDomain
|
|
57
|
-
} from "./ywspezzx.js";
|
|
58
58
|
import {
|
|
59
59
|
snakeCase,
|
|
60
60
|
emittedTypeName
|
|
61
|
-
} from "./
|
|
61
|
+
} from "./gcefca6p.js";
|
|
62
62
|
import {
|
|
63
63
|
ColumnBuilder2
|
|
64
|
-
} from "./
|
|
64
|
+
} from "./606g0nbf.js";
|
|
65
|
+
import {
|
|
66
|
+
catalog,
|
|
67
|
+
isDomain
|
|
68
|
+
} from "./ywspezzx.js";
|
|
65
69
|
import {
|
|
66
70
|
staticNamespace2
|
|
67
71
|
} from "./358zsv4r.js";
|
|
@@ -78,6 +82,374 @@ function projectPath(cwd, ...parts) {
|
|
|
78
82
|
return resolve(cwd, ...parts);
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
// src/tooling/migrate/export.ts
|
|
86
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
87
|
+
import { isAbsolute, join } from "node:path";
|
|
88
|
+
var EXPORT_FORMAT = 1;
|
|
89
|
+
var LOGICAL_FORMAT = 1;
|
|
90
|
+
function snapshotFromCatalog(source, namespace, database) {
|
|
91
|
+
return {
|
|
92
|
+
format: EXPORT_FORMAT,
|
|
93
|
+
logical: { namespace, name: namespace, format: LOGICAL_FORMAT },
|
|
94
|
+
catalogHash: startupHash2(source),
|
|
95
|
+
...database !== undefined ? { database } : {},
|
|
96
|
+
tables: exportTables(source)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function serializeSnapshot(snapshot) {
|
|
100
|
+
return `${JSON.stringify(snapshot, null, 2)}
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
function parseSnapshot(path, text) {
|
|
104
|
+
let parsed;
|
|
105
|
+
try {
|
|
106
|
+
parsed = JSON.parse(text);
|
|
107
|
+
} catch {
|
|
108
|
+
throw snapshotError(path, "stale", `Catalog snapshot ${path} is not JSON.`);
|
|
109
|
+
}
|
|
110
|
+
if (!isSnapshot(parsed)) {
|
|
111
|
+
throw snapshotError(path, "stale", `Catalog snapshot ${path} is stale: the file format is not ${String(EXPORT_FORMAT)}.`);
|
|
112
|
+
}
|
|
113
|
+
return parsed;
|
|
114
|
+
}
|
|
115
|
+
function databaseName(url) {
|
|
116
|
+
try {
|
|
117
|
+
const name = decodeURIComponent(new URL(url).pathname.replace(/^\//, ""));
|
|
118
|
+
return name.length > 0 ? name : undefined;
|
|
119
|
+
} catch {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function catalogNamespace(config) {
|
|
124
|
+
return config.namespace ?? "public";
|
|
125
|
+
}
|
|
126
|
+
function loadSnapshots(cwd, config) {
|
|
127
|
+
const paths = config.external ?? [];
|
|
128
|
+
return paths.map((path) => {
|
|
129
|
+
const file = resolveSnapshot(cwd, path);
|
|
130
|
+
if (!existsSync(file)) {
|
|
131
|
+
throw snapshotError(path, "missing", `Catalog snapshot ${path} is missing.`);
|
|
132
|
+
}
|
|
133
|
+
return { path, snapshot: parseSnapshot(path, readFileSync(file, "utf8")) };
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function externalNamespaceMap(snapshots) {
|
|
137
|
+
const names = new Map;
|
|
138
|
+
for (const loaded of snapshots) {
|
|
139
|
+
for (const table of loaded.snapshot.tables)
|
|
140
|
+
names.set(table.name, table.namespace);
|
|
141
|
+
}
|
|
142
|
+
return names;
|
|
143
|
+
}
|
|
144
|
+
function couplingFindings(source) {
|
|
145
|
+
const findings = [];
|
|
146
|
+
for (const object of source.objects) {
|
|
147
|
+
if (object.kind !== "constraint" || object.definition.constraintKind !== "foreignKey")
|
|
148
|
+
continue;
|
|
149
|
+
const references = object.definition.references;
|
|
150
|
+
if (references === undefined)
|
|
151
|
+
continue;
|
|
152
|
+
const from = object.identity.parent.namespace;
|
|
153
|
+
const to = references.parent.namespace;
|
|
154
|
+
if (from.form !== "static" || to.form !== "static" || from.name === to.name)
|
|
155
|
+
continue;
|
|
156
|
+
findings.push({
|
|
157
|
+
severity: "warning",
|
|
158
|
+
code: "coupling",
|
|
159
|
+
place: `${from.name}.${object.identity.parent.name}.${object.definition.columns.join(",")}`,
|
|
160
|
+
reason: `references ${to.name}.${references.parent.name}.${references.columns.join(",")}`,
|
|
161
|
+
fix: "Cross-catalog foreign keys are allowed inside one database."
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return findings;
|
|
165
|
+
}
|
|
166
|
+
function assertSnapshots(cwd, config, source) {
|
|
167
|
+
if ((config.external ?? []).length === 0)
|
|
168
|
+
return;
|
|
169
|
+
const snapshots = loadSnapshots(cwd, config);
|
|
170
|
+
for (const table of externalTables(source)) {
|
|
171
|
+
const found = findTable(snapshots, table.namespace, table.name);
|
|
172
|
+
if (found === undefined) {
|
|
173
|
+
throw snapshotError(table.name, "missing", `External table ${table.namespace}.${table.name} is missing from the catalog snapshots.`);
|
|
174
|
+
}
|
|
175
|
+
const differ = columnDifference(table.columns, found.table.columns);
|
|
176
|
+
if (differ !== undefined) {
|
|
177
|
+
throw snapshotError(found.path, "stale", `Catalog snapshot ${found.path} is stale: ${table.namespace}.${table.name}.${differ} does not match the export.`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function assertLiveExternal(url, source, snapshots) {
|
|
182
|
+
if (snapshots.length === 0 && externalTables(source).length === 0)
|
|
183
|
+
return;
|
|
184
|
+
const postgres = (await import("postgres")).default;
|
|
185
|
+
const sql = postgres(url, { max: 1, connect_timeout: 5, idle_timeout: 1, onnotice: () => {} });
|
|
186
|
+
try {
|
|
187
|
+
const current = await sql`select current_database() as name`;
|
|
188
|
+
const database = current[0]?.name;
|
|
189
|
+
if (database !== undefined)
|
|
190
|
+
assertSameDatabase(source, snapshots, database);
|
|
191
|
+
for (const table of externalTables(source)) {
|
|
192
|
+
const found = findTable(snapshots, table.namespace, table.name);
|
|
193
|
+
if (found === undefined)
|
|
194
|
+
continue;
|
|
195
|
+
await assertLiveTable(sql, found.path, found.snapshot, table);
|
|
196
|
+
}
|
|
197
|
+
} finally {
|
|
198
|
+
await sql.end({ timeout: 5 });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function renderExport(cwd, config, source, args) {
|
|
202
|
+
const flags = exportFlags(args);
|
|
203
|
+
const database = exportDatabase(config);
|
|
204
|
+
const snapshot = snapshotFromCatalog(source, catalogNamespace(config), database);
|
|
205
|
+
const text = serializeSnapshot(snapshot);
|
|
206
|
+
if (flags.out !== undefined) {
|
|
207
|
+
const path = isAbsolute(flags.out) ? flags.out : join(cwd, flags.out);
|
|
208
|
+
writeFileSync(path, text);
|
|
209
|
+
}
|
|
210
|
+
if (flags.json || flags.out === undefined)
|
|
211
|
+
return text;
|
|
212
|
+
return `${flags.out}
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
function exportTables(source) {
|
|
216
|
+
const tables = [];
|
|
217
|
+
for (const object of source.objects) {
|
|
218
|
+
if (object.kind !== "table" || object.owner === "external" || object.owner === "ignored") {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (object.identity.namespace.form !== "static")
|
|
222
|
+
continue;
|
|
223
|
+
const columns = [];
|
|
224
|
+
for (const column of source.objects) {
|
|
225
|
+
if (!isColumnOf(column, object.identity.namespace.name, object.identity.name))
|
|
226
|
+
continue;
|
|
227
|
+
columns.push({
|
|
228
|
+
name: column.identity.name,
|
|
229
|
+
type: column.definition.dataType,
|
|
230
|
+
nullable: column.definition.nullable
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
tables.push({
|
|
234
|
+
namespace: object.identity.namespace.name,
|
|
235
|
+
name: object.identity.name,
|
|
236
|
+
columns
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return tables;
|
|
240
|
+
}
|
|
241
|
+
function isColumnOf(object, namespace, table) {
|
|
242
|
+
return object.kind === "column" && object.identity.parent.name === table && object.identity.parent.namespace.form === "static" && object.identity.parent.namespace.name === namespace;
|
|
243
|
+
}
|
|
244
|
+
function externalTables(source) {
|
|
245
|
+
const tables = [];
|
|
246
|
+
for (const object of source.objects) {
|
|
247
|
+
if (object.kind !== "table" || object.owner !== "external")
|
|
248
|
+
continue;
|
|
249
|
+
if (object.identity.namespace.form !== "static")
|
|
250
|
+
continue;
|
|
251
|
+
const columns = [];
|
|
252
|
+
for (const column of source.objects) {
|
|
253
|
+
if (!isColumnOf(column, object.identity.namespace.name, object.identity.name))
|
|
254
|
+
continue;
|
|
255
|
+
columns.push({
|
|
256
|
+
name: column.identity.name,
|
|
257
|
+
type: column.definition.dataType,
|
|
258
|
+
nullable: column.definition.nullable
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
tables.push({ namespace: object.identity.namespace.name, name: object.identity.name, columns });
|
|
262
|
+
}
|
|
263
|
+
return tables;
|
|
264
|
+
}
|
|
265
|
+
function findTable(snapshots, namespace, name) {
|
|
266
|
+
for (const loaded of snapshots) {
|
|
267
|
+
for (const table of loaded.snapshot.tables) {
|
|
268
|
+
if (table.name === name && (table.namespace === namespace || namespace === "public")) {
|
|
269
|
+
return { path: loaded.path, snapshot: loaded.snapshot, table };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
for (const loaded of snapshots) {
|
|
274
|
+
for (const table of loaded.snapshot.tables) {
|
|
275
|
+
if (table.name === name)
|
|
276
|
+
return { path: loaded.path, snapshot: loaded.snapshot, table };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
function columnDifference(left, right) {
|
|
282
|
+
const byName = new Map(right.map((column) => [column.name, column]));
|
|
283
|
+
for (const column of left) {
|
|
284
|
+
const other = byName.get(column.name);
|
|
285
|
+
if (other === undefined)
|
|
286
|
+
return column.name;
|
|
287
|
+
if (other.type !== column.type || other.nullable !== column.nullable)
|
|
288
|
+
return column.name;
|
|
289
|
+
}
|
|
290
|
+
for (const column of right) {
|
|
291
|
+
if (!left.some((item) => item.name === column.name))
|
|
292
|
+
return column.name;
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
function assertSameDatabase(source, snapshots, database) {
|
|
297
|
+
if (!hasCrossCatalogForeignKey(source))
|
|
298
|
+
return;
|
|
299
|
+
for (const loaded of snapshots) {
|
|
300
|
+
const name = loaded.snapshot.database;
|
|
301
|
+
if (name !== undefined && name !== database) {
|
|
302
|
+
throw new OkmError2("OKM1842", `A foreign key references ${loaded.snapshot.logical.namespace} in database ${name}. This database is ${database}.`, {
|
|
303
|
+
kind: "invalid",
|
|
304
|
+
fieldReason: "database",
|
|
305
|
+
fix: {
|
|
306
|
+
summary: "Keep the foreign key inside one database. Cross-catalog keys are allowed only there."
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function hasCrossCatalogForeignKey(source) {
|
|
313
|
+
for (const object of source.objects) {
|
|
314
|
+
if (object.kind !== "constraint" || object.definition.constraintKind !== "foreignKey")
|
|
315
|
+
continue;
|
|
316
|
+
const references = object.definition.references;
|
|
317
|
+
if (references === undefined)
|
|
318
|
+
continue;
|
|
319
|
+
const from = object.identity.parent.namespace;
|
|
320
|
+
const to = references.parent.namespace;
|
|
321
|
+
if (from.form === "static" && to.form === "static" && from.name !== to.name)
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
async function assertLiveTable(sql, path, snapshot, table) {
|
|
327
|
+
const hash = await liveHash(sql, table.namespace);
|
|
328
|
+
if (hash !== undefined && hash !== snapshot.catalogHash) {
|
|
329
|
+
throw snapshotError(path, "stale", `Catalog snapshot ${path} is stale: the catalog hash changed.`);
|
|
330
|
+
}
|
|
331
|
+
const live = await liveColumns(sql, table.namespace, table.name);
|
|
332
|
+
if (live === undefined) {
|
|
333
|
+
throw snapshotError(path, "missing", `External table ${table.namespace}.${table.name} is missing on the database.`);
|
|
334
|
+
}
|
|
335
|
+
const differ = columnDifference(snapshotTableColumns(snapshot, table), live);
|
|
336
|
+
if (differ !== undefined) {
|
|
337
|
+
throw snapshotError(path, "stale", `Catalog snapshot ${path} is stale: ${table.namespace}.${table.name}.${differ} is missing on the database.`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function snapshotTableColumns(snapshot, table) {
|
|
341
|
+
for (const item of snapshot.tables) {
|
|
342
|
+
if (item.name === table.name)
|
|
343
|
+
return item.columns;
|
|
344
|
+
}
|
|
345
|
+
return table.columns;
|
|
346
|
+
}
|
|
347
|
+
async function liveHash(sql, namespace) {
|
|
348
|
+
const reg = `${quoteIdent2(namespace)}.${quoteIdent2("okm_meta")}`;
|
|
349
|
+
const present = await sql.unsafe(`select to_regclass('${reg}')::text as reg`);
|
|
350
|
+
if (present[0]?.reg == null)
|
|
351
|
+
return;
|
|
352
|
+
const rows = await sql.unsafe(`select catalog_hash from ${reg} where id = 'head'`);
|
|
353
|
+
return rows[0]?.catalog_hash;
|
|
354
|
+
}
|
|
355
|
+
async function liveColumns(sql, namespace, table) {
|
|
356
|
+
const rows = await sql`select a.attname as name, format_type(a.atttypid, a.atttypmod) as type, a.attnotnull as not_null
|
|
357
|
+
from pg_attribute a
|
|
358
|
+
join pg_class c on c.oid = a.attrelid
|
|
359
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
360
|
+
where n.nspname = ${namespace} and c.relname = ${table}
|
|
361
|
+
and a.attnum > 0 and not a.attisdropped
|
|
362
|
+
order by a.attnum`;
|
|
363
|
+
if (rows.length === 0) {
|
|
364
|
+
const exists = await sql`
|
|
365
|
+
select to_regclass(${`${quoteIdent2(namespace)}.${quoteIdent2(table)}`})::text as reg
|
|
366
|
+
`;
|
|
367
|
+
if (exists[0]?.reg == null)
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
return rows.map((row) => ({ name: row.name, type: row.type, nullable: !row.not_null }));
|
|
371
|
+
}
|
|
372
|
+
function resolveSnapshot(cwd, path) {
|
|
373
|
+
return isAbsolute(path) ? path : projectPath(cwd, path);
|
|
374
|
+
}
|
|
375
|
+
function exportDatabase(config) {
|
|
376
|
+
const target = config.database;
|
|
377
|
+
if (typeof target === "string")
|
|
378
|
+
return databaseName(target);
|
|
379
|
+
if (target?.url !== undefined)
|
|
380
|
+
return databaseName(target.url);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
function exportFlags(args) {
|
|
384
|
+
let json = false;
|
|
385
|
+
let out;
|
|
386
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
387
|
+
const arg = args[index] ?? "";
|
|
388
|
+
if (arg === "--json") {
|
|
389
|
+
json = true;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (arg === "--out") {
|
|
393
|
+
const value = args[index + 1];
|
|
394
|
+
if (value === undefined || value.startsWith("--")) {
|
|
395
|
+
throw new OkmError2("invalid", "--out needs a file.", {
|
|
396
|
+
fix: { summary: "Pass --out <file>." }
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
out = value;
|
|
400
|
+
index += 1;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (arg.startsWith("--out=")) {
|
|
404
|
+
out = arg.slice("--out=".length);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
throw new OkmError2("invalid", `Unknown flag ${arg}.`, {
|
|
408
|
+
fix: { summary: "okm catalog export accepts --json and --out <file>." }
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
return out === undefined ? { json } : { json, out };
|
|
412
|
+
}
|
|
413
|
+
function snapshotError(path, reason, message) {
|
|
414
|
+
return new OkmError2("OKM1841", message, {
|
|
415
|
+
kind: reason === "missing" ? "not_found" : "schema_drift",
|
|
416
|
+
fieldReason: reason,
|
|
417
|
+
columns: [path],
|
|
418
|
+
fix: {
|
|
419
|
+
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."
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
function isSnapshot(value) {
|
|
424
|
+
if (!isRecord(value) || value.format !== EXPORT_FORMAT)
|
|
425
|
+
return false;
|
|
426
|
+
if (typeof value.catalogHash !== "string")
|
|
427
|
+
return false;
|
|
428
|
+
if (!isLogical(value.logical))
|
|
429
|
+
return false;
|
|
430
|
+
if (value.database !== undefined && typeof value.database !== "string")
|
|
431
|
+
return false;
|
|
432
|
+
if (!Array.isArray(value.tables))
|
|
433
|
+
return false;
|
|
434
|
+
return value.tables.every(isTable);
|
|
435
|
+
}
|
|
436
|
+
function isLogical(value) {
|
|
437
|
+
if (!isRecord(value))
|
|
438
|
+
return false;
|
|
439
|
+
return typeof value.namespace === "string" && typeof value.name === "string" && value.format === LOGICAL_FORMAT;
|
|
440
|
+
}
|
|
441
|
+
function isTable(value) {
|
|
442
|
+
if (!isRecord(value) || typeof value.namespace !== "string" || typeof value.name !== "string") {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
if (!Array.isArray(value.columns))
|
|
446
|
+
return false;
|
|
447
|
+
return value.columns.every((column) => isRecord(column) && typeof column.name === "string" && typeof column.type === "string" && typeof column.nullable === "boolean");
|
|
448
|
+
}
|
|
449
|
+
function isRecord(value) {
|
|
450
|
+
return typeof value === "object" && value !== null;
|
|
451
|
+
}
|
|
452
|
+
|
|
81
453
|
// src/tooling/errors/registry.ts
|
|
82
454
|
var ERROR_DOCS = [
|
|
83
455
|
{
|
|
@@ -236,6 +608,12 @@ var ERROR_DOCS = [
|
|
|
236
608
|
summary: "An operator does not apply to the column type.",
|
|
237
609
|
fix: "Use an operator the error lists for that column type. Search needs a column built for search, not a plain text column."
|
|
238
610
|
},
|
|
611
|
+
{
|
|
612
|
+
code: "OKM1125",
|
|
613
|
+
title: "Bad SQL fragment",
|
|
614
|
+
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.",
|
|
615
|
+
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()."
|
|
616
|
+
},
|
|
239
617
|
{
|
|
240
618
|
code: "OKM1130",
|
|
241
619
|
title: "Cursor order",
|
|
@@ -449,7 +827,7 @@ var ERROR_DOCS = [
|
|
|
449
827
|
{
|
|
450
828
|
code: "OKM1538",
|
|
451
829
|
title: "Type rewrite",
|
|
452
|
-
summary: "A statement changes a column type in a way that rewrites the table.",
|
|
830
|
+
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.",
|
|
453
831
|
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."
|
|
454
832
|
},
|
|
455
833
|
{
|
|
@@ -530,12 +908,24 @@ var ERROR_DOCS = [
|
|
|
530
908
|
summary: "A step contains COMMIT, ROLLBACK, or other transaction control, including inside a DO body. A function or procedure body is not this.",
|
|
531
909
|
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."
|
|
532
910
|
},
|
|
911
|
+
{
|
|
912
|
+
code: "OKM1552",
|
|
913
|
+
title: "Reprinted declaration",
|
|
914
|
+
summary: "A domain check or a LANGUAGE sql function body is stored differently from the text in the migration catalog.",
|
|
915
|
+
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."
|
|
916
|
+
},
|
|
533
917
|
{
|
|
534
918
|
code: "OKM1601",
|
|
535
919
|
title: "Stale typed SQL",
|
|
536
920
|
summary: "A typed SQL signature no longer matches the database.",
|
|
537
921
|
fix: "Regenerate the typed SQL and commit the new signature."
|
|
538
922
|
},
|
|
923
|
+
{
|
|
924
|
+
code: "OKM1602",
|
|
925
|
+
title: "Bad typed SQL",
|
|
926
|
+
summary: "A raw statement is missing its class, is not one select or one write, or the result class has no column initializers.",
|
|
927
|
+
fix: 'Pass "read" or "write", a fragment`...`, and a class with an initializer on every column. Identifiers are ident(name).'
|
|
928
|
+
},
|
|
539
929
|
{
|
|
540
930
|
code: "OKM1701",
|
|
541
931
|
title: "Tenant context missing",
|
|
@@ -545,8 +935,8 @@ var ERROR_DOCS = [
|
|
|
545
935
|
{
|
|
546
936
|
code: "OKM1702",
|
|
547
937
|
title: "Unverifiable tenant SQL",
|
|
548
|
-
summary: "Raw SQL
|
|
549
|
-
fix:
|
|
938
|
+
summary: "Raw SQL on a tenant table cannot prove the tenant predicate. A raw write is this. A raw read that does not select the primary key with ident is this.",
|
|
939
|
+
fix: 'Use update or delete, or select the primary key with ident(name). unscoped("reason") is the reviewed hatch.'
|
|
550
940
|
},
|
|
551
941
|
{
|
|
552
942
|
code: "OKM1704",
|
|
@@ -739,6 +1129,18 @@ var ERROR_DOCS = [
|
|
|
739
1129
|
title: "Pooler session",
|
|
740
1130
|
summary: "Two statements on the same connection saw different backend pids. A transaction-mode pooler is in the way.",
|
|
741
1131
|
fix: "Use a direct connection, or pass --allow-pooler."
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
code: "OKM1855",
|
|
1135
|
+
title: "Pull would overwrite",
|
|
1136
|
+
summary: "okm pull or okm import drizzle found a file it would replace. It writes nothing.",
|
|
1137
|
+
fix: "Pass --out <directory> to write somewhere else, or move the file."
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
code: "OKM1856",
|
|
1141
|
+
title: "Drizzle is not installed",
|
|
1142
|
+
summary: "okm import drizzle could not resolve drizzle-orm from the project.",
|
|
1143
|
+
fix: "Install drizzle-orm in the project that owns the schema."
|
|
742
1144
|
}
|
|
743
1145
|
];
|
|
744
1146
|
var byCode;
|
|
@@ -755,25 +1157,25 @@ function indexDocs() {
|
|
|
755
1157
|
}
|
|
756
1158
|
|
|
757
1159
|
// src/tooling/migrate/files.ts
|
|
758
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
759
|
-
import { join } from "node:path";
|
|
1160
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
1161
|
+
import { join as join2 } from "node:path";
|
|
760
1162
|
function loadMigrations(directory) {
|
|
761
|
-
if (!
|
|
1163
|
+
if (!existsSync2(directory))
|
|
762
1164
|
return [];
|
|
763
1165
|
const names = readdirSync(directory).filter((file) => file.endsWith(".sql")).sort();
|
|
764
1166
|
const migrations = [];
|
|
765
1167
|
for (const file of names) {
|
|
766
1168
|
const id = file.slice(0, -".sql".length);
|
|
767
|
-
const catalogPath =
|
|
768
|
-
if (!
|
|
1169
|
+
const catalogPath = join2(directory, `${id}.catalog.json`);
|
|
1170
|
+
if (!existsSync2(catalogPath)) {
|
|
769
1171
|
throw new OkmError2("invalid", `Migration ${id} is missing its catalog snapshot.`, {
|
|
770
1172
|
fix: {
|
|
771
1173
|
summary: "Generate the migration again so the SQL and the catalog are written together."
|
|
772
1174
|
}
|
|
773
1175
|
});
|
|
774
1176
|
}
|
|
775
|
-
const plan = readMigrationPlan(
|
|
776
|
-
const stored = parseCatalog2(
|
|
1177
|
+
const plan = readMigrationPlan(join2(directory, file));
|
|
1178
|
+
const stored = parseCatalog2(readFileSync2(catalogPath, "utf8"));
|
|
777
1179
|
const startup = startupCatalog2(stored);
|
|
778
1180
|
const hash = catalogHash2(startup);
|
|
779
1181
|
migrations.push(startup === stored ? { id, catalogHash: hash, steps: plan.steps } : { id, catalogHash: hash, legacyHash: catalogHash2(stored), steps: plan.steps });
|
|
@@ -781,7 +1183,7 @@ function loadMigrations(directory) {
|
|
|
781
1183
|
return migrations;
|
|
782
1184
|
}
|
|
783
1185
|
function readMigrationPlan(path) {
|
|
784
|
-
const text = prepareMigrationText(
|
|
1186
|
+
const text = prepareMigrationText(readFileSync2(path, "utf8"), path);
|
|
785
1187
|
const plan = parsePlan(text);
|
|
786
1188
|
assertMigrationSteps(text, plan.steps.length, path);
|
|
787
1189
|
return plan;
|
|
@@ -794,13 +1196,13 @@ function loadHeadSnapshot(directory) {
|
|
|
794
1196
|
return {
|
|
795
1197
|
id: last.id,
|
|
796
1198
|
catalogHash: last.catalogHash,
|
|
797
|
-
catalog: parseCatalog2(
|
|
1199
|
+
catalog: parseCatalog2(readFileSync2(join2(directory, `${last.id}.catalog.json`), "utf8"))
|
|
798
1200
|
};
|
|
799
1201
|
}
|
|
800
1202
|
|
|
801
1203
|
// src/tooling/migrate/lint.ts
|
|
802
|
-
import { existsSync as
|
|
803
|
-
import { join as
|
|
1204
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
1205
|
+
import { join as join3 } from "node:path";
|
|
804
1206
|
|
|
805
1207
|
// src/tooling/migrate/lint-rules.ts
|
|
806
1208
|
var INTEGER_WIDTH = {
|
|
@@ -887,7 +1289,7 @@ function rule(code, category, severity, doc, check) {
|
|
|
887
1289
|
function hit(matched, reason) {
|
|
888
1290
|
return matched ? [reason] : [];
|
|
889
1291
|
}
|
|
890
|
-
function
|
|
1292
|
+
function normalizedSql(sql) {
|
|
891
1293
|
const collapsed = sql.trim().replace(/\s+/g, " ");
|
|
892
1294
|
let out = "";
|
|
893
1295
|
let quoted = false;
|
|
@@ -902,7 +1304,7 @@ function normalized(sql) {
|
|
|
902
1304
|
return out;
|
|
903
1305
|
}
|
|
904
1306
|
function weakensRls(input) {
|
|
905
|
-
const text =
|
|
1307
|
+
const text = normalizedSql(input.step.sql);
|
|
906
1308
|
if (/\bdisable row level security\b/.test(text))
|
|
907
1309
|
return ["disables row level security"];
|
|
908
1310
|
if (/\bno force row level security\b/.test(text))
|
|
@@ -1019,7 +1421,7 @@ function parenthesized(text, open) {
|
|
|
1019
1421
|
}
|
|
1020
1422
|
function policyExpression(input, table, name) {
|
|
1021
1423
|
const expression = expressionOf(input.after, table, name) ?? expressionOf(input.before, table, name);
|
|
1022
|
-
return expression === undefined ? undefined :
|
|
1424
|
+
return expression === undefined ? undefined : normalizedSql(expression);
|
|
1023
1425
|
}
|
|
1024
1426
|
function expressionOf(source, table, name) {
|
|
1025
1427
|
for (const object of source.objects) {
|
|
@@ -1042,7 +1444,7 @@ function hasPolicy(source, table) {
|
|
|
1042
1444
|
return source.objects.some((object) => object.kind === "policy" && object.identity.parent.name === table);
|
|
1043
1445
|
}
|
|
1044
1446
|
function dataStatement(step) {
|
|
1045
|
-
const text =
|
|
1447
|
+
const text = normalizedSql(step.sql);
|
|
1046
1448
|
if (/^(?:do|call)\b/.test(text))
|
|
1047
1449
|
return ["the body is not analysed"];
|
|
1048
1450
|
if (step.backfill !== undefined)
|
|
@@ -1056,16 +1458,16 @@ function dataStatement(step) {
|
|
|
1056
1458
|
return [];
|
|
1057
1459
|
}
|
|
1058
1460
|
function matches(step, kind, pattern) {
|
|
1059
|
-
return pattern.test(
|
|
1461
|
+
return pattern.test(normalizedSql(step.sql)) || step.kind === kind;
|
|
1060
1462
|
}
|
|
1061
1463
|
function renamesTable(step) {
|
|
1062
|
-
const text =
|
|
1464
|
+
const text = normalizedSql(step.sql);
|
|
1063
1465
|
if (/^alter table\s+\S+\s+rename to\b/.test(text))
|
|
1064
1466
|
return true;
|
|
1065
1467
|
return step.kind === "rename-table" && !/^alter table\s+\S+\s+rename (column|constraint)\b/.test(text);
|
|
1066
1468
|
}
|
|
1067
1469
|
function droppedType(input) {
|
|
1068
|
-
const text =
|
|
1470
|
+
const text = normalizedSql(input.step.sql);
|
|
1069
1471
|
const dropping = /^drop type\b/.test(text);
|
|
1070
1472
|
if (!dropping && input.step.kind !== "drop-enum" && input.step.kind !== "drop-domain") {
|
|
1071
1473
|
return;
|
|
@@ -1081,7 +1483,7 @@ function droppedType(input) {
|
|
|
1081
1483
|
return "enum";
|
|
1082
1484
|
}
|
|
1083
1485
|
function requiredColumn(input) {
|
|
1084
|
-
const text =
|
|
1486
|
+
const text = normalizedSql(input.step.sql);
|
|
1085
1487
|
const adding = /^alter table\s+\S+\s+add column\b/.test(text) || input.step.kind === "add-column-required";
|
|
1086
1488
|
if (!adding)
|
|
1087
1489
|
return false;
|
|
@@ -1097,7 +1499,7 @@ function requiredColumn(input) {
|
|
|
1097
1499
|
function uniqueConstraint(input) {
|
|
1098
1500
|
if (!tableExists(input))
|
|
1099
1501
|
return false;
|
|
1100
|
-
const text =
|
|
1502
|
+
const text = normalizedSql(input.step.sql);
|
|
1101
1503
|
if (/\busing index\b/.test(text))
|
|
1102
1504
|
return false;
|
|
1103
1505
|
if (/^alter table\s+\S+\s+add constraint\b/.test(text) && /\b(unique|primary key)\b/.test(text)) {
|
|
@@ -1108,16 +1510,16 @@ function uniqueConstraint(input) {
|
|
|
1108
1510
|
const name = createdIndexName(text);
|
|
1109
1511
|
if (name === undefined)
|
|
1110
1512
|
return false;
|
|
1111
|
-
return input.later.some((step) => usesIndex(
|
|
1513
|
+
return input.later.some((step) => usesIndex(normalizedSql(step.sql), name));
|
|
1112
1514
|
}
|
|
1113
1515
|
function uniqueIndex(input) {
|
|
1114
|
-
const text =
|
|
1516
|
+
const text = normalizedSql(input.step.sql);
|
|
1115
1517
|
if (!/^create unique index\b/.test(text))
|
|
1116
1518
|
return false;
|
|
1117
1519
|
if (!tableExists(input))
|
|
1118
1520
|
return false;
|
|
1119
1521
|
const name = createdIndexName(text);
|
|
1120
|
-
if (name !== undefined && input.later.some((step) => usesIndex(
|
|
1522
|
+
if (name !== undefined && input.later.some((step) => usesIndex(normalizedSql(step.sql), name))) {
|
|
1121
1523
|
return false;
|
|
1122
1524
|
}
|
|
1123
1525
|
return true;
|
|
@@ -1130,7 +1532,7 @@ function usesIndex(text, name) {
|
|
|
1130
1532
|
return text.includes(`using index "${name}"`) || text.includes(`using index ${name}`);
|
|
1131
1533
|
}
|
|
1132
1534
|
function unsafeSetNotNull(input) {
|
|
1133
|
-
const text =
|
|
1535
|
+
const text = normalizedSql(input.step.sql);
|
|
1134
1536
|
const match = /^alter table\s+(\S+)\s+alter column\s+(\S+)\s+set not null\b/.exec(text);
|
|
1135
1537
|
if (match === null && input.step.kind !== "set-not-null")
|
|
1136
1538
|
return false;
|
|
@@ -1146,21 +1548,21 @@ function unsafeSetNotNull(input) {
|
|
|
1146
1548
|
function validatedNotNullCheck(earlier, table, column) {
|
|
1147
1549
|
const needle = `check (${column} is not null) not valid`;
|
|
1148
1550
|
for (const step of earlier) {
|
|
1149
|
-
const text =
|
|
1551
|
+
const text = normalizedSql(step.sql);
|
|
1150
1552
|
if (!text.startsWith(`alter table ${table} `) || !text.includes(needle))
|
|
1151
1553
|
continue;
|
|
1152
1554
|
const added = /^alter table\s+\S+\s+add constraint\s+(\S+)\s+check\b/.exec(text);
|
|
1153
1555
|
const name = added?.[1];
|
|
1154
1556
|
if (name === undefined)
|
|
1155
1557
|
continue;
|
|
1156
|
-
const validated = earlier.some((item) =>
|
|
1558
|
+
const validated = earlier.some((item) => normalizedSql(item.sql).startsWith(`alter table ${table} validate constraint ${name}`));
|
|
1157
1559
|
if (validated)
|
|
1158
1560
|
return true;
|
|
1159
1561
|
}
|
|
1160
1562
|
return false;
|
|
1161
1563
|
}
|
|
1162
1564
|
function blockingIndex(input) {
|
|
1163
|
-
const text =
|
|
1565
|
+
const text = normalizedSql(input.step.sql);
|
|
1164
1566
|
if (!/^create(?: unique)? index\b/.test(text) && input.step.kind !== "create-index")
|
|
1165
1567
|
return false;
|
|
1166
1568
|
if (/\bconcurrently\b/.test(text))
|
|
@@ -1168,10 +1570,10 @@ function blockingIndex(input) {
|
|
|
1168
1570
|
return tableExists(input);
|
|
1169
1571
|
}
|
|
1170
1572
|
function checkWithoutNotValid(input) {
|
|
1171
|
-
return addedConstraint(input, "check") && !/\bnot valid\b/.test(
|
|
1573
|
+
return addedConstraint(input, "check") && !/\bnot valid\b/.test(normalizedSql(input.step.sql));
|
|
1172
1574
|
}
|
|
1173
1575
|
function foreignKeyWithoutNotValid(input) {
|
|
1174
|
-
return addedConstraint(input, "foreign key") && !/\bnot valid\b/.test(
|
|
1576
|
+
return addedConstraint(input, "foreign key") && !/\bnot valid\b/.test(normalizedSql(input.step.sql));
|
|
1175
1577
|
}
|
|
1176
1578
|
function validatingCheck(input) {
|
|
1177
1579
|
if (widenValidate(input))
|
|
@@ -1181,7 +1583,7 @@ function validatingCheck(input) {
|
|
|
1181
1583
|
return checkWithoutNotValid(input);
|
|
1182
1584
|
}
|
|
1183
1585
|
function widenValidate(input) {
|
|
1184
|
-
const text =
|
|
1586
|
+
const text = normalizedSql(input.step.sql);
|
|
1185
1587
|
if (!/^alter table\s+\S+\s+validate constraint\b/.test(text))
|
|
1186
1588
|
return false;
|
|
1187
1589
|
if (input.step.kind === "widen-check")
|
|
@@ -1192,7 +1594,7 @@ function widenValidate(input) {
|
|
|
1192
1594
|
return input.earlier.some((step) => {
|
|
1193
1595
|
if (step.kind !== "widen-check")
|
|
1194
1596
|
return false;
|
|
1195
|
-
const prior =
|
|
1597
|
+
const prior = normalizedSql(step.sql);
|
|
1196
1598
|
return prior.includes(`add constraint ${name} `);
|
|
1197
1599
|
});
|
|
1198
1600
|
}
|
|
@@ -1204,13 +1606,13 @@ function validatingForeignKey(input) {
|
|
|
1204
1606
|
function addedConstraint(input, kind) {
|
|
1205
1607
|
if (!tableExists(input))
|
|
1206
1608
|
return false;
|
|
1207
|
-
const text =
|
|
1609
|
+
const text = normalizedSql(input.step.sql);
|
|
1208
1610
|
if (!/^alter table\s+\S+\s+add constraint\b/.test(text))
|
|
1209
1611
|
return false;
|
|
1210
1612
|
return new RegExp(`\\b${kind}\\b`).test(text);
|
|
1211
1613
|
}
|
|
1212
1614
|
function validates(input, kind) {
|
|
1213
|
-
const text =
|
|
1615
|
+
const text = normalizedSql(input.step.sql);
|
|
1214
1616
|
const table = /^alter table\s+\S+\s+validate constraint\s+(\S+)/.exec(text);
|
|
1215
1617
|
if (table !== null) {
|
|
1216
1618
|
const token = table[1];
|
|
@@ -1230,7 +1632,7 @@ function validates(input, kind) {
|
|
|
1230
1632
|
}
|
|
1231
1633
|
var TYPE_CHANGE = /^alter table\s+(\S+)\s+alter column\s+(\S+)\s+(?:set data type|type)\b/;
|
|
1232
1634
|
function typeChange(input, mode) {
|
|
1233
|
-
const text =
|
|
1635
|
+
const text = normalizedSql(input.step.sql);
|
|
1234
1636
|
const changing = TYPE_CHANGE.test(text) || input.step.kind === "set-column-type";
|
|
1235
1637
|
if (!changing)
|
|
1236
1638
|
return [];
|
|
@@ -1246,18 +1648,19 @@ function typeChange(input, mode) {
|
|
|
1246
1648
|
const shrink = lengthShrink(before, after);
|
|
1247
1649
|
if (mode === "shrink")
|
|
1248
1650
|
return shrink ? ["shortens a character length"] : [];
|
|
1249
|
-
if (shrink)
|
|
1250
|
-
return [];
|
|
1651
|
+
if (shrink) {
|
|
1652
|
+
return mode === "rewrite" && tableRewrites(before, after, input) ? ["rewrites the table"] : [];
|
|
1653
|
+
}
|
|
1251
1654
|
if (mode === "narrow")
|
|
1252
1655
|
return narrows(before, after) ? ["narrows a type"] : [];
|
|
1253
|
-
if (lengthGrow(before, after))
|
|
1656
|
+
if (lengthGrow(before, after) && mode !== "rewrite")
|
|
1254
1657
|
return [];
|
|
1255
1658
|
if (mode === "incompatible")
|
|
1256
1659
|
return ["changes a column type"];
|
|
1257
|
-
return
|
|
1660
|
+
return tableRewrites(before, after, input) ? ["rewrites the table"] : [];
|
|
1258
1661
|
}
|
|
1259
1662
|
function columnTypes(input) {
|
|
1260
|
-
const text =
|
|
1663
|
+
const text = normalizedSql(input.step.sql);
|
|
1261
1664
|
const match = TYPE_CHANGE.exec(text);
|
|
1262
1665
|
const table = lastName(match?.[1]);
|
|
1263
1666
|
const column = lastName(match?.[2]);
|
|
@@ -1282,13 +1685,115 @@ function lengthGrow(before, after) {
|
|
|
1282
1685
|
const right = charLength(after);
|
|
1283
1686
|
return left !== undefined && right !== undefined && left.family === right.family && right.length > left.length;
|
|
1284
1687
|
}
|
|
1285
|
-
function
|
|
1286
|
-
if (
|
|
1688
|
+
function tableRewrites(before, after, input) {
|
|
1689
|
+
if (usingRewrites(input))
|
|
1690
|
+
return true;
|
|
1691
|
+
if (varyingRelaxation(before, after))
|
|
1692
|
+
return false;
|
|
1693
|
+
if (isVarying(before) && after === "text")
|
|
1694
|
+
return false;
|
|
1695
|
+
if (before === "text" && unconstrainedVarying(after))
|
|
1696
|
+
return false;
|
|
1697
|
+
if ((before === "text" || isVarying(before)) && unconstrainedCharacter(after))
|
|
1698
|
+
return false;
|
|
1699
|
+
if (numericRelaxation(before, after))
|
|
1700
|
+
return false;
|
|
1701
|
+
if (binaryPair(before, after))
|
|
1287
1702
|
return false;
|
|
1288
|
-
if (
|
|
1703
|
+
if (domainSkipsRewrite(before, after, input))
|
|
1289
1704
|
return false;
|
|
1290
1705
|
return true;
|
|
1291
1706
|
}
|
|
1707
|
+
function statementMayReprint(sql) {
|
|
1708
|
+
const text = normalizedSql(sql);
|
|
1709
|
+
if (/^(?:create|alter) domain\b/.test(text))
|
|
1710
|
+
return true;
|
|
1711
|
+
return /^create(?: or replace)? function\b/.test(text) && /\blanguage sql\b/.test(text);
|
|
1712
|
+
}
|
|
1713
|
+
function usingRewrites(input) {
|
|
1714
|
+
const text = normalizedSql(input.step.sql);
|
|
1715
|
+
const at = text.search(/\busing\b/);
|
|
1716
|
+
if (at < 0)
|
|
1717
|
+
return false;
|
|
1718
|
+
const column = lastName(TYPE_CHANGE.exec(text)?.[2]);
|
|
1719
|
+
if (column === undefined)
|
|
1720
|
+
return true;
|
|
1721
|
+
const expression = text.slice(at + "using".length).trim().replace(/;$/, "");
|
|
1722
|
+
return !plainCast(expression, column);
|
|
1723
|
+
}
|
|
1724
|
+
function plainCast(expression, column) {
|
|
1725
|
+
const bare = column.toLowerCase();
|
|
1726
|
+
const quoted = `"${column}"`;
|
|
1727
|
+
return expression.startsWith(`${bare}::`) || expression.startsWith(`${quoted}::`) || expression.startsWith(`cast(${bare} as `) || expression.startsWith(`cast(${quoted} as `);
|
|
1728
|
+
}
|
|
1729
|
+
function isVarying(type) {
|
|
1730
|
+
return type === "character varying" || type === "varchar" || type.startsWith("character varying(") || type.startsWith("varchar(");
|
|
1731
|
+
}
|
|
1732
|
+
function unconstrainedVarying(type) {
|
|
1733
|
+
return type === "character varying" || type === "varchar";
|
|
1734
|
+
}
|
|
1735
|
+
function unconstrainedCharacter(type) {
|
|
1736
|
+
return type === "bpchar" || type === "character";
|
|
1737
|
+
}
|
|
1738
|
+
function varyingRelaxation(before, after) {
|
|
1739
|
+
const left = charLength(before);
|
|
1740
|
+
if (left?.family === "varying" && unconstrainedVarying(after))
|
|
1741
|
+
return true;
|
|
1742
|
+
const right = charLength(after);
|
|
1743
|
+
return left?.family === "varying" && right?.family === "varying" && right.length > left.length;
|
|
1744
|
+
}
|
|
1745
|
+
function numericRelaxation(before, after) {
|
|
1746
|
+
const left = numericBounds(before);
|
|
1747
|
+
const right = numericBounds(after);
|
|
1748
|
+
if (left === undefined || right === undefined)
|
|
1749
|
+
return false;
|
|
1750
|
+
if (right.precision === undefined)
|
|
1751
|
+
return left.precision !== undefined;
|
|
1752
|
+
if (left.precision === undefined || left.scale === undefined || right.scale === undefined) {
|
|
1753
|
+
return false;
|
|
1754
|
+
}
|
|
1755
|
+
return right.scale === left.scale && right.precision > left.precision;
|
|
1756
|
+
}
|
|
1757
|
+
function numericBounds(type) {
|
|
1758
|
+
const match = /^numeric(?:\((\d+)(?:,(\d+))?\))?$/.exec(type);
|
|
1759
|
+
if (match === null)
|
|
1760
|
+
return;
|
|
1761
|
+
if (match[1] === undefined)
|
|
1762
|
+
return { precision: undefined, scale: undefined };
|
|
1763
|
+
return {
|
|
1764
|
+
precision: Number(match[1]),
|
|
1765
|
+
scale: match[2] === undefined ? 0 : Number(match[2])
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
function binaryPair(before, after) {
|
|
1769
|
+
if (before === "integer" && after === "oid")
|
|
1770
|
+
return true;
|
|
1771
|
+
if (before === "oid" && after === "integer")
|
|
1772
|
+
return true;
|
|
1773
|
+
if (before === "cidr" && after === "inet")
|
|
1774
|
+
return true;
|
|
1775
|
+
return before === "xml" && after === "text";
|
|
1776
|
+
}
|
|
1777
|
+
function domainSkipsRewrite(before, after, input) {
|
|
1778
|
+
const next = domainNamed(input, after);
|
|
1779
|
+
if (next !== undefined && next.check.trim().length === 0 && canonical(next.base) === before) {
|
|
1780
|
+
return true;
|
|
1781
|
+
}
|
|
1782
|
+
const previous = domainNamed(input, before);
|
|
1783
|
+
return previous !== undefined && canonical(previous.base) === after;
|
|
1784
|
+
}
|
|
1785
|
+
function domainNamed(input, name) {
|
|
1786
|
+
for (const source of [input.after, input.before]) {
|
|
1787
|
+
for (const object of source.objects) {
|
|
1788
|
+
if (object.kind !== "type" || object.identity.name !== name)
|
|
1789
|
+
continue;
|
|
1790
|
+
if (!isDomain(object.definition))
|
|
1791
|
+
continue;
|
|
1792
|
+
return object.definition;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1292
1797
|
function narrows(before, after) {
|
|
1293
1798
|
const leftInt = INTEGER_WIDTH[before];
|
|
1294
1799
|
const rightInt = INTEGER_WIDTH[after];
|
|
@@ -1313,9 +1818,6 @@ function charLength(type) {
|
|
|
1313
1818
|
return { family: "fixed", length: Number(fixed[1]) };
|
|
1314
1819
|
return;
|
|
1315
1820
|
}
|
|
1316
|
-
function isCharacter(type) {
|
|
1317
|
-
return type === "text" || charLength(type) !== undefined;
|
|
1318
|
-
}
|
|
1319
1821
|
function numericSpec(type) {
|
|
1320
1822
|
const match = /^numeric(?:\((\d+)(?:,(\d+))?\))?$/.exec(type);
|
|
1321
1823
|
if (match === null)
|
|
@@ -1332,7 +1834,7 @@ function tableExists(input) {
|
|
|
1332
1834
|
return name !== undefined && input.existingTables.has(name);
|
|
1333
1835
|
}
|
|
1334
1836
|
function statementTable(step) {
|
|
1335
|
-
const text =
|
|
1837
|
+
const text = normalizedSql(step.sql);
|
|
1336
1838
|
const altered = /^(?:alter|drop) table\s+(\S+)/.exec(text);
|
|
1337
1839
|
if (altered?.[1] !== undefined)
|
|
1338
1840
|
return lastName(altered[1]);
|
|
@@ -1368,6 +1870,134 @@ function isSerial(column) {
|
|
|
1368
1870
|
return /nextval\s*\(/i.test(column.definition.defaultExpression ?? "");
|
|
1369
1871
|
}
|
|
1370
1872
|
|
|
1873
|
+
// src/tooling/migrate/reprint.ts
|
|
1874
|
+
var REPRINT_CODE = "OKM1552";
|
|
1875
|
+
function reprintHits(declared, stored, steps) {
|
|
1876
|
+
const hits = [];
|
|
1877
|
+
const silenced = new Set;
|
|
1878
|
+
for (const difference of differences(declared, stored)) {
|
|
1879
|
+
const index = stepIndex(steps, difference.kind, difference.name);
|
|
1880
|
+
if (index === undefined)
|
|
1881
|
+
continue;
|
|
1882
|
+
const step = steps[index];
|
|
1883
|
+
const allow = step?.allows?.find((item) => item.code === REPRINT_CODE);
|
|
1884
|
+
if (allow !== undefined && allow.reason.trim().length > 0) {
|
|
1885
|
+
silenced.add(index);
|
|
1886
|
+
continue;
|
|
1887
|
+
}
|
|
1888
|
+
hits.push({
|
|
1889
|
+
severity: "warning",
|
|
1890
|
+
code: REPRINT_CODE,
|
|
1891
|
+
place: `step ${String(index + 1)}`,
|
|
1892
|
+
reason: difference.reason
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
steps.forEach((step, index) => {
|
|
1896
|
+
for (const allow of step.allows ?? []) {
|
|
1897
|
+
if (allow.code !== REPRINT_CODE)
|
|
1898
|
+
continue;
|
|
1899
|
+
const place = `step ${String(index + 1)}`;
|
|
1900
|
+
if (allow.reason.trim().length === 0) {
|
|
1901
|
+
hits.push({
|
|
1902
|
+
severity: "error",
|
|
1903
|
+
code: "OKM1510",
|
|
1904
|
+
place,
|
|
1905
|
+
reason: "override needs a reason"
|
|
1906
|
+
});
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
if (silenced.has(index))
|
|
1910
|
+
continue;
|
|
1911
|
+
if (hits.some((item) => item.place === place && item.code === REPRINT_CODE))
|
|
1912
|
+
continue;
|
|
1913
|
+
if (!statementMayReprint(step.sql))
|
|
1914
|
+
continue;
|
|
1915
|
+
hits.push({
|
|
1916
|
+
severity: "error",
|
|
1917
|
+
code: "OKM1510",
|
|
1918
|
+
place,
|
|
1919
|
+
reason: `override ${REPRINT_CODE} does not match this statement`
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
});
|
|
1923
|
+
return hits;
|
|
1924
|
+
}
|
|
1925
|
+
function differences(declared, stored) {
|
|
1926
|
+
const found = [];
|
|
1927
|
+
const storedDomains = domainChecks(stored);
|
|
1928
|
+
for (const [name, check] of domainChecks(declared)) {
|
|
1929
|
+
const live = storedDomains.get(name);
|
|
1930
|
+
if (live === undefined || live === check)
|
|
1931
|
+
continue;
|
|
1932
|
+
found.push({
|
|
1933
|
+
kind: "domain",
|
|
1934
|
+
name,
|
|
1935
|
+
reason: `domain ${name} check reprints differently`
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
const storedFunctions = sqlBodies(stored);
|
|
1939
|
+
for (const [key, body] of sqlBodies(declared)) {
|
|
1940
|
+
const live = storedFunctions.get(key);
|
|
1941
|
+
if (live === undefined || live.body === body.body)
|
|
1942
|
+
continue;
|
|
1943
|
+
found.push({
|
|
1944
|
+
kind: "function",
|
|
1945
|
+
name: body.name,
|
|
1946
|
+
reason: `function ${body.name} body reprints differently`
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
return found;
|
|
1950
|
+
}
|
|
1951
|
+
function domainChecks(source) {
|
|
1952
|
+
const checks = new Map;
|
|
1953
|
+
for (const object of source.objects) {
|
|
1954
|
+
if (object.kind !== "type" || !isDomain(object.definition))
|
|
1955
|
+
continue;
|
|
1956
|
+
checks.set(object.identity.name, object.definition.check.trim());
|
|
1957
|
+
}
|
|
1958
|
+
return checks;
|
|
1959
|
+
}
|
|
1960
|
+
function sqlBodies(source) {
|
|
1961
|
+
const bodies = new Map;
|
|
1962
|
+
for (const object of source.objects) {
|
|
1963
|
+
if (object.kind !== "function" || object.definition.language !== "sql")
|
|
1964
|
+
continue;
|
|
1965
|
+
bodies.set(functionKey(object), {
|
|
1966
|
+
name: object.identity.name,
|
|
1967
|
+
body: object.definition.body.trim()
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
return bodies;
|
|
1971
|
+
}
|
|
1972
|
+
function functionKey(object) {
|
|
1973
|
+
const args = object.identity.argTypes.map((typeName) => canonicalTypeName(typeName)).join(",");
|
|
1974
|
+
return `${object.identity.name}(${args})`;
|
|
1975
|
+
}
|
|
1976
|
+
function stepIndex(steps, kind, name) {
|
|
1977
|
+
let found;
|
|
1978
|
+
steps.forEach((step, index) => {
|
|
1979
|
+
if (mentions(normalizedSql(step.sql), kind, name))
|
|
1980
|
+
found = index;
|
|
1981
|
+
});
|
|
1982
|
+
return found;
|
|
1983
|
+
}
|
|
1984
|
+
function mentions(text, kind, name) {
|
|
1985
|
+
if (kind === "domain" && !/^(?:create|alter) domain\b/.test(text))
|
|
1986
|
+
return false;
|
|
1987
|
+
if (kind === "function") {
|
|
1988
|
+
if (!/^create(?: or replace)? function\b/.test(text) || !/\blanguage sql\b/.test(text)) {
|
|
1989
|
+
return false;
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
const quoted = `"${name}"`;
|
|
1993
|
+
if (text.includes(quoted))
|
|
1994
|
+
return true;
|
|
1995
|
+
return new RegExp(`\\b${escapeRegExp(name.toLowerCase())}\\b`).test(text);
|
|
1996
|
+
}
|
|
1997
|
+
function escapeRegExp(value) {
|
|
1998
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1999
|
+
}
|
|
2000
|
+
|
|
1371
2001
|
// src/tooling/migrate/lint.ts
|
|
1372
2002
|
function lintPlan(plan, before, after) {
|
|
1373
2003
|
const existingTables = tableNames(before);
|
|
@@ -1377,6 +2007,9 @@ function lintPlan(plan, before, after) {
|
|
|
1377
2007
|
});
|
|
1378
2008
|
return findings;
|
|
1379
2009
|
}
|
|
2010
|
+
function lintReprints(declared, stored, steps) {
|
|
2011
|
+
return reprintHits(declared, stored, steps).map((hit) => finding(hit.severity, hit.code, hit.place, hit.reason));
|
|
2012
|
+
}
|
|
1380
2013
|
function lintCatalog(source) {
|
|
1381
2014
|
const findings = [];
|
|
1382
2015
|
for (const object of source.objects) {
|
|
@@ -1392,15 +2025,15 @@ function lintCatalog(source) {
|
|
|
1392
2025
|
return findings;
|
|
1393
2026
|
}
|
|
1394
2027
|
function lintMigrationDirectory(directory, pending) {
|
|
1395
|
-
if (!
|
|
2028
|
+
if (!existsSync3(directory))
|
|
1396
2029
|
return [];
|
|
1397
2030
|
const names = readdirSync2(directory).filter((file) => file.endsWith(".sql")).sort();
|
|
1398
2031
|
let before = catalog([]);
|
|
1399
2032
|
const findings = [];
|
|
1400
2033
|
for (const file of names) {
|
|
1401
2034
|
const id = file.slice(0, -".sql".length);
|
|
1402
|
-
const plan = readMigrationPlan(
|
|
1403
|
-
const after = parseCatalog2(
|
|
2035
|
+
const plan = readMigrationPlan(join3(directory, file));
|
|
2036
|
+
const after = parseCatalog2(readFileSync3(join3(directory, `${id}.catalog.json`), "utf8"));
|
|
1404
2037
|
if (pending === undefined || pending.has(id)) {
|
|
1405
2038
|
for (const item of lintPlan(plan, before, after)) {
|
|
1406
2039
|
findings.push({ ...item, place: `${id} ${item.place}` });
|
|
@@ -1450,6 +2083,8 @@ function lintStep(step, number, before, after, existingTables, earlier, later) {
|
|
|
1450
2083
|
triggered.add(hit.code);
|
|
1451
2084
|
}
|
|
1452
2085
|
}
|
|
2086
|
+
if (statementMayReprint(statement.sql))
|
|
2087
|
+
triggered.add(REPRINT_CODE);
|
|
1453
2088
|
const place = multi ? `step ${String(number)} statement ${String(index + 1)}` : `step ${String(number)}`;
|
|
1454
2089
|
perStatement.push({ place, hits });
|
|
1455
2090
|
});
|
|
@@ -1519,8 +2154,16 @@ function columnPlace(column) {
|
|
|
1519
2154
|
}
|
|
1520
2155
|
|
|
1521
2156
|
// src/tooling/migrate/project.ts
|
|
1522
|
-
import {
|
|
1523
|
-
|
|
2157
|
+
import {
|
|
2158
|
+
existsSync as existsSync4,
|
|
2159
|
+
mkdirSync,
|
|
2160
|
+
readdirSync as readdirSync3,
|
|
2161
|
+
readFileSync as readFileSync4,
|
|
2162
|
+
statSync,
|
|
2163
|
+
unlinkSync,
|
|
2164
|
+
writeFileSync as writeFileSync2
|
|
2165
|
+
} from "node:fs";
|
|
2166
|
+
import { basename, dirname, join as join4 } from "node:path";
|
|
1524
2167
|
import { pathToFileURL } from "node:url";
|
|
1525
2168
|
|
|
1526
2169
|
// src/dialects/pg/declarations.ts
|
|
@@ -2131,6 +2774,131 @@ function columnNames(target, built, table) {
|
|
|
2131
2774
|
return built.flatMap((object) => object.kind === "column" && object.identity.parent.name === table ? [object.identity.name] : []);
|
|
2132
2775
|
}
|
|
2133
2776
|
|
|
2777
|
+
// src/tooling/migrate/place.ts
|
|
2778
|
+
function placeCatalog(source, namespace, externalNamespaces) {
|
|
2779
|
+
if (namespace === "public" && externalNamespaces.size === 0)
|
|
2780
|
+
return source;
|
|
2781
|
+
const homes = tableHomes(source, namespace, externalNamespaces);
|
|
2782
|
+
let changed = false;
|
|
2783
|
+
const objects = source.objects.map((object) => {
|
|
2784
|
+
const next = relocateObject(object, homes, namespace);
|
|
2785
|
+
if (next !== object)
|
|
2786
|
+
changed = true;
|
|
2787
|
+
return next;
|
|
2788
|
+
});
|
|
2789
|
+
if (!changed)
|
|
2790
|
+
return source;
|
|
2791
|
+
return catalog(objects);
|
|
2792
|
+
}
|
|
2793
|
+
function tableHomes(source, namespace, externalNamespaces) {
|
|
2794
|
+
const homes = new Map;
|
|
2795
|
+
for (const object of source.objects) {
|
|
2796
|
+
if (object.kind !== "table" && object.kind !== "view" && object.kind !== "materializedView") {
|
|
2797
|
+
continue;
|
|
2798
|
+
}
|
|
2799
|
+
const home = object.owner === "external" ? externalNamespaces.get(object.identity.name) ?? namespace : namespace;
|
|
2800
|
+
homes.set(object.identity.name, home);
|
|
2801
|
+
}
|
|
2802
|
+
return homes;
|
|
2803
|
+
}
|
|
2804
|
+
function relocateObject(object, homes, namespace) {
|
|
2805
|
+
const dependencies = relocateEdges(object.dependencies, homes, namespace);
|
|
2806
|
+
if (object.kind === "extension") {
|
|
2807
|
+
if (object.definition.schema === "public" && namespace !== "public") {
|
|
2808
|
+
return { ...object, definition: { ...object.definition, schema: namespace }, dependencies };
|
|
2809
|
+
}
|
|
2810
|
+
return dependencies === object.dependencies ? object : { ...object, dependencies };
|
|
2811
|
+
}
|
|
2812
|
+
if (object.kind === "role") {
|
|
2813
|
+
return dependencies === object.dependencies ? object : { ...object, dependencies };
|
|
2814
|
+
}
|
|
2815
|
+
const identity = relocateIdentity(object.identity, homes, namespace);
|
|
2816
|
+
if (object.kind === "constraint") {
|
|
2817
|
+
const references = relocateReferences(object, homes);
|
|
2818
|
+
if (identity === object.identity && references === object.definition.references && dependencies === object.dependencies) {
|
|
2819
|
+
return object;
|
|
2820
|
+
}
|
|
2821
|
+
if (references === undefined || references === object.definition.references) {
|
|
2822
|
+
return { ...object, identity, dependencies };
|
|
2823
|
+
}
|
|
2824
|
+
return {
|
|
2825
|
+
...object,
|
|
2826
|
+
identity,
|
|
2827
|
+
definition: { ...object.definition, references },
|
|
2828
|
+
dependencies
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
if (identity === object.identity && dependencies === object.dependencies)
|
|
2832
|
+
return object;
|
|
2833
|
+
return { ...object, identity, dependencies };
|
|
2834
|
+
}
|
|
2835
|
+
function relocateReferences(object, homes) {
|
|
2836
|
+
const references = object.definition.references;
|
|
2837
|
+
if (references === undefined)
|
|
2838
|
+
return;
|
|
2839
|
+
const home = homes.get(references.parent.name);
|
|
2840
|
+
if (home === undefined)
|
|
2841
|
+
return references;
|
|
2842
|
+
const parent = moveRef(references.parent, home);
|
|
2843
|
+
if (parent === references.parent)
|
|
2844
|
+
return references;
|
|
2845
|
+
return { ...references, parent };
|
|
2846
|
+
}
|
|
2847
|
+
function relocateEdges(edges, homes, namespace) {
|
|
2848
|
+
let changed = false;
|
|
2849
|
+
const next = edges.map((edge) => {
|
|
2850
|
+
const target = relocateIdentity(edge.target, homes, namespace);
|
|
2851
|
+
if (target === edge.target)
|
|
2852
|
+
return edge;
|
|
2853
|
+
changed = true;
|
|
2854
|
+
return { target };
|
|
2855
|
+
});
|
|
2856
|
+
return changed ? next : edges;
|
|
2857
|
+
}
|
|
2858
|
+
function relocateIdentity(identity, homes, namespace) {
|
|
2859
|
+
if (identity.kind === "role" || identity.kind === "extension")
|
|
2860
|
+
return identity;
|
|
2861
|
+
if (identity.kind === "column" || identity.kind === "index" || identity.kind === "constraint" || identity.kind === "trigger" || identity.kind === "policy") {
|
|
2862
|
+
const home = homes.get(identity.parent.name) ?? namespace;
|
|
2863
|
+
const parent = moveRef(identity.parent, home);
|
|
2864
|
+
if (parent === identity.parent)
|
|
2865
|
+
return identity;
|
|
2866
|
+
return { ...identity, parent };
|
|
2867
|
+
}
|
|
2868
|
+
if (identity.kind === "grant") {
|
|
2869
|
+
const home = homes.get(identity.object.name) ?? namespace;
|
|
2870
|
+
const moved = moveNamespace(identity.object.namespace, home);
|
|
2871
|
+
if (moved === identity.object.namespace)
|
|
2872
|
+
return identity;
|
|
2873
|
+
return { ...identity, object: { ...identity.object, namespace: moved } };
|
|
2874
|
+
}
|
|
2875
|
+
if (identity.kind === "defaultPrivilege") {
|
|
2876
|
+
const moved = moveNamespace(identity.namespace, namespace);
|
|
2877
|
+
if (moved === identity.namespace)
|
|
2878
|
+
return identity;
|
|
2879
|
+
return { ...identity, namespace: moved };
|
|
2880
|
+
}
|
|
2881
|
+
if (identity.kind === "table" || identity.kind === "view" || identity.kind === "materializedView" || identity.kind === "sequence" || identity.kind === "type" || identity.kind === "function") {
|
|
2882
|
+
const home = homes.get(identity.name) ?? namespace;
|
|
2883
|
+
const moved = moveNamespace(identity.namespace, home);
|
|
2884
|
+
if (moved === identity.namespace)
|
|
2885
|
+
return identity;
|
|
2886
|
+
return { ...identity, namespace: moved };
|
|
2887
|
+
}
|
|
2888
|
+
return identity;
|
|
2889
|
+
}
|
|
2890
|
+
function moveRef(ref, namespace) {
|
|
2891
|
+
const moved = moveNamespace(ref.namespace, namespace);
|
|
2892
|
+
if (moved === ref.namespace)
|
|
2893
|
+
return ref;
|
|
2894
|
+
return { ...ref, namespace: moved };
|
|
2895
|
+
}
|
|
2896
|
+
function moveNamespace(namespace, name) {
|
|
2897
|
+
if (namespace.form !== "static" || namespace.name === name)
|
|
2898
|
+
return namespace;
|
|
2899
|
+
return staticNamespace2(name);
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2134
2902
|
// src/tooling/migrate/estimate.ts
|
|
2135
2903
|
var LARGE_TABLE_ROWS = 1e6;
|
|
2136
2904
|
var ESTIMATE_SQL = `
|
|
@@ -2324,11 +3092,35 @@ function trimZero(text) {
|
|
|
2324
3092
|
return text.endsWith(".0") ? text.slice(0, -2) : text;
|
|
2325
3093
|
}
|
|
2326
3094
|
|
|
3095
|
+
// src/tooling/migrate/plan-json.ts
|
|
3096
|
+
function planDocument(plan, findings, lockText) {
|
|
3097
|
+
return {
|
|
3098
|
+
name: plan.name,
|
|
3099
|
+
class: plan.class,
|
|
3100
|
+
steps: plan.steps.map((step) => ({
|
|
3101
|
+
class: step.class,
|
|
3102
|
+
lock: lockText === undefined ? step.lock : lockText(step),
|
|
3103
|
+
sql: step.sql
|
|
3104
|
+
})),
|
|
3105
|
+
findings: findings.map((finding) => ({
|
|
3106
|
+
severity: finding.severity,
|
|
3107
|
+
code: finding.code,
|
|
3108
|
+
place: finding.place,
|
|
3109
|
+
reason: finding.reason,
|
|
3110
|
+
fix: finding.fix
|
|
3111
|
+
}))
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
function formatPlanJson(plan, findings, lockText) {
|
|
3115
|
+
return `${JSON.stringify(planDocument(plan, findings, lockText))}
|
|
3116
|
+
`;
|
|
3117
|
+
}
|
|
3118
|
+
|
|
2327
3119
|
// src/tooling/migrate/project.ts
|
|
2328
3120
|
async function buildProject(cwd) {
|
|
2329
3121
|
const config = await loadConfig(cwd);
|
|
2330
3122
|
const built = await loadSchema(cwd, config.schema);
|
|
2331
|
-
return writeArtifact(cwd, config, withRoles(built, config));
|
|
3123
|
+
return writeArtifact(cwd, config, withRoles(built, config, cwd));
|
|
2332
3124
|
}
|
|
2333
3125
|
async function generateProject(cwd, name, flags) {
|
|
2334
3126
|
const prepared = await prepare(cwd, name, flags);
|
|
@@ -2340,17 +3132,20 @@ async function generateProject(cwd, name, flags) {
|
|
|
2340
3132
|
const directory = projectPath(cwd, prepared.config.migrations ?? "migrations");
|
|
2341
3133
|
mkdirSync(directory, { recursive: true });
|
|
2342
3134
|
const base = `${nextNumber(directory)}_${safeName(name)}`;
|
|
2343
|
-
const sqlPath =
|
|
2344
|
-
|
|
2345
|
-
|
|
3135
|
+
const sqlPath = join4(directory, `${base}.sql`);
|
|
3136
|
+
writeFileSync2(sqlPath, formatPlan(prepared.plan));
|
|
3137
|
+
writeFileSync2(join4(directory, `${base}.catalog.json`), serializeCatalog2(prepared.built.catalog));
|
|
2346
3138
|
return { path: sqlPath, findings: lintPrepared(prepared) };
|
|
2347
3139
|
}
|
|
2348
3140
|
async function planProject(cwd, name, flags, invoke) {
|
|
2349
3141
|
const prepared = await prepare(cwd, name, flags);
|
|
3142
|
+
const findings = lintPrepared(prepared);
|
|
3143
|
+
const lockText = await planLockText(prepared.config, prepared.plan, invoke);
|
|
2350
3144
|
return {
|
|
2351
3145
|
plan: prepared.plan,
|
|
2352
|
-
findings
|
|
2353
|
-
text:
|
|
3146
|
+
findings,
|
|
3147
|
+
text: formatPlan(prepared.plan, lockText),
|
|
3148
|
+
json: formatPlanJson(prepared.plan, findings, lockText)
|
|
2354
3149
|
};
|
|
2355
3150
|
}
|
|
2356
3151
|
async function checkProject(cwd, flags) {
|
|
@@ -2366,8 +3161,8 @@ async function checkProject(cwd, flags) {
|
|
|
2366
3161
|
assertTargetAlias(targets);
|
|
2367
3162
|
if (opened.config.tables !== undefined) {
|
|
2368
3163
|
const directory = projectPath(cwd, opened.config.tables);
|
|
2369
|
-
const files =
|
|
2370
|
-
const source =
|
|
3164
|
+
const files = existsSync4(directory) ? readdirSync3(directory) : [];
|
|
3165
|
+
const source = readFileSync4(projectPath(cwd, opened.config.schema), "utf8");
|
|
2371
3166
|
const missing = unlistedTableFiles(source, files);
|
|
2372
3167
|
if (missing.length > 0) {
|
|
2373
3168
|
throw new OkmError2("OKM1024", `Table file not in schema: ${missing.join(", ")}.`, {
|
|
@@ -2375,7 +3170,8 @@ async function checkProject(cwd, flags) {
|
|
|
2375
3170
|
});
|
|
2376
3171
|
}
|
|
2377
3172
|
}
|
|
2378
|
-
|
|
3173
|
+
assertSnapshots(cwd, opened.config, opened.built.catalog);
|
|
3174
|
+
const findings = [...lintOpened(opened), ...couplingFindings(opened.built.catalog)];
|
|
2379
3175
|
if (hasError(findings))
|
|
2380
3176
|
throw lintRefusal(findings);
|
|
2381
3177
|
const named = flags?.target;
|
|
@@ -2386,12 +3182,14 @@ async function checkProject(cwd, flags) {
|
|
|
2386
3182
|
const target = selectTarget(opened.config, named);
|
|
2387
3183
|
assertTargetPolicy(target, "check", flags?.allowProtected ?? false);
|
|
2388
3184
|
assertTargetPolicy(target, "drift", flags?.allowProtected ?? false);
|
|
2389
|
-
const { assertAuthorDrift } = await import("./
|
|
3185
|
+
const { assertAuthorDrift } = await import("./ckz05z15.js");
|
|
2390
3186
|
const allowPooler = flags?.allowPooler === true || opened.config.allowPooler === true;
|
|
3187
|
+
await assertLiveExternal(target.url, opened.built.catalog, loadSnapshots(cwd, opened.config));
|
|
2391
3188
|
await assertAuthorDrift(target.url, opened.built.catalog, opened.config.roles, allowPooler);
|
|
2392
3189
|
return findings;
|
|
2393
3190
|
}
|
|
2394
3191
|
function unlistedTableFiles(schemaSource, fileNames) {
|
|
3192
|
+
const folded = schemaSource.toLowerCase();
|
|
2395
3193
|
const missing = [];
|
|
2396
3194
|
for (const file of fileNames) {
|
|
2397
3195
|
if (!file.endsWith(".ts"))
|
|
@@ -2399,7 +3197,7 @@ function unlistedTableFiles(schemaSource, fileNames) {
|
|
|
2399
3197
|
const stem = basename(file, ".ts");
|
|
2400
3198
|
if (stem.length === 0 || stem.startsWith("."))
|
|
2401
3199
|
continue;
|
|
2402
|
-
if (!
|
|
3200
|
+
if (!folded.includes(stem.toLowerCase()))
|
|
2403
3201
|
missing.push(file);
|
|
2404
3202
|
}
|
|
2405
3203
|
return missing;
|
|
@@ -2416,6 +3214,7 @@ async function prepare(cwd, name, flags) {
|
|
|
2416
3214
|
renames: opened.renames,
|
|
2417
3215
|
replacements: flags.map((flag) => parseReplace(flag)),
|
|
2418
3216
|
name,
|
|
3217
|
+
...placedSchema(opened.config),
|
|
2419
3218
|
...batchSizeField(opened.config.backfill?.batchSize)
|
|
2420
3219
|
});
|
|
2421
3220
|
return {
|
|
@@ -2429,26 +3228,25 @@ async function prepare(cwd, name, flags) {
|
|
|
2429
3228
|
function lintPrepared(prepared) {
|
|
2430
3229
|
return lintPlan(prepared.plan, prepared.before, prepared.after);
|
|
2431
3230
|
}
|
|
2432
|
-
async function
|
|
2433
|
-
const offline = formatPlan(plan);
|
|
3231
|
+
async function planLockText(config, plan, invoke) {
|
|
2434
3232
|
const targets = listTargets(config);
|
|
2435
3233
|
if (targets.length === 0)
|
|
2436
|
-
return
|
|
3234
|
+
return;
|
|
2437
3235
|
assertTargetAlias(targets);
|
|
2438
3236
|
const named = invoke?.target;
|
|
2439
3237
|
if (named === undefined && targets.length !== 1)
|
|
2440
|
-
return
|
|
3238
|
+
return;
|
|
2441
3239
|
const target = selectTarget(config, named);
|
|
2442
3240
|
assertTargetPolicy(target, "plan", invoke?.allowProtected ?? false);
|
|
2443
3241
|
let estimates;
|
|
2444
3242
|
try {
|
|
2445
3243
|
const names = plan.steps.flatMap((step) => step.tables ?? []);
|
|
2446
|
-
estimates = await readRowEstimates(target.url,
|
|
3244
|
+
estimates = await readRowEstimates(target.url, catalogNamespace(config), names);
|
|
2447
3245
|
} catch {
|
|
2448
|
-
return
|
|
3246
|
+
return;
|
|
2449
3247
|
}
|
|
2450
3248
|
const aliases = renameAliases(plan.steps);
|
|
2451
|
-
return
|
|
3249
|
+
return (step) => annotateLock(step, estimates, aliases);
|
|
2452
3250
|
}
|
|
2453
3251
|
function lintOpened(opened) {
|
|
2454
3252
|
const plan = planMigration({
|
|
@@ -2456,6 +3254,7 @@ function lintOpened(opened) {
|
|
|
2456
3254
|
after: opened.built.catalog,
|
|
2457
3255
|
renames: opened.renames,
|
|
2458
3256
|
name: "check",
|
|
3257
|
+
...placedSchema(opened.config),
|
|
2459
3258
|
...batchSizeField(opened.config.backfill?.batchSize)
|
|
2460
3259
|
});
|
|
2461
3260
|
return [
|
|
@@ -2465,7 +3264,7 @@ function lintOpened(opened) {
|
|
|
2465
3264
|
}
|
|
2466
3265
|
async function openProject(cwd) {
|
|
2467
3266
|
const config = await loadConfig(cwd);
|
|
2468
|
-
const built =
|
|
3267
|
+
const built = await loadBuiltSchema(cwd, config);
|
|
2469
3268
|
const declarations = schemaDeclarations(built);
|
|
2470
3269
|
const previous = readPrevious(projectPath(cwd, config.migrations ?? "migrations"));
|
|
2471
3270
|
const stale = staleRenames(previous?.catalog, declarations.renames);
|
|
@@ -2481,15 +3280,22 @@ async function openProject(cwd) {
|
|
|
2481
3280
|
renames: declarations.renames
|
|
2482
3281
|
};
|
|
2483
3282
|
}
|
|
2484
|
-
function withRoles(built, config) {
|
|
3283
|
+
function withRoles(built, config, cwd) {
|
|
2485
3284
|
const marks = ownershipMarks(built);
|
|
2486
3285
|
const stamped = marks.size === 0 ? built.catalog : stampOwnership(built.catalog, marks);
|
|
3286
|
+
const placed = placeCatalog(stamped, catalogNamespace(config), externalNamespaceMap(loadSnapshots(cwd, config)));
|
|
2487
3287
|
const roles = config.roles;
|
|
2488
|
-
const catalog = roles === undefined ?
|
|
3288
|
+
const catalog = roles === undefined ? placed : attachRoles(placed, roles, exposureFor(built, roles));
|
|
2489
3289
|
if (catalog === built.catalog)
|
|
2490
3290
|
return built;
|
|
2491
3291
|
return { ...built, catalog };
|
|
2492
3292
|
}
|
|
3293
|
+
function placedSchema(config) {
|
|
3294
|
+
const namespace = catalogNamespace(config);
|
|
3295
|
+
if (namespace === "public")
|
|
3296
|
+
return;
|
|
3297
|
+
return { schema: namespace };
|
|
3298
|
+
}
|
|
2493
3299
|
function ownershipMarks(built) {
|
|
2494
3300
|
const marks = new Map;
|
|
2495
3301
|
const models = built.model;
|
|
@@ -2526,9 +3332,9 @@ var GIT_ATTRIBUTES = [
|
|
|
2526
3332
|
`);
|
|
2527
3333
|
function writeGitAttributes(cwd) {
|
|
2528
3334
|
const path = projectPath(cwd, ".gitattributes");
|
|
2529
|
-
if (
|
|
3335
|
+
if (existsSync4(path))
|
|
2530
3336
|
return;
|
|
2531
|
-
|
|
3337
|
+
writeFileSync2(path, GIT_ATTRIBUTES);
|
|
2532
3338
|
}
|
|
2533
3339
|
function typesText(cwd, spec, built) {
|
|
2534
3340
|
return `${emitRowTypes(built, jsonColumnTypes(readSchemaSource(cwd, spec)))}${referenceAugmentation(built)}`;
|
|
@@ -2537,7 +3343,7 @@ function readSchemaSource(cwd, spec) {
|
|
|
2537
3343
|
const parts = [];
|
|
2538
3344
|
const seen = new Set;
|
|
2539
3345
|
const visit = (file) => {
|
|
2540
|
-
if (seen.has(file) || !
|
|
3346
|
+
if (seen.has(file) || !existsSync4(file))
|
|
2541
3347
|
return;
|
|
2542
3348
|
let stat;
|
|
2543
3349
|
try {
|
|
@@ -2548,16 +3354,16 @@ function readSchemaSource(cwd, spec) {
|
|
|
2548
3354
|
if (!stat.isFile())
|
|
2549
3355
|
return;
|
|
2550
3356
|
seen.add(file);
|
|
2551
|
-
const text =
|
|
3357
|
+
const text = readFileSync4(file, "utf8");
|
|
2552
3358
|
parts.push(text);
|
|
2553
3359
|
for (const match of text.matchAll(/from\s+["'](\.[^"']+)["']/g)) {
|
|
2554
3360
|
const rel = match[1];
|
|
2555
3361
|
if (rel === undefined)
|
|
2556
3362
|
continue;
|
|
2557
|
-
const base =
|
|
2558
|
-
const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}.mts`,
|
|
3363
|
+
const base = join4(dirname(file), rel);
|
|
3364
|
+
const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}.mts`, join4(base, "index.ts")];
|
|
2559
3365
|
for (const candidate of candidates) {
|
|
2560
|
-
if (
|
|
3366
|
+
if (existsSync4(candidate)) {
|
|
2561
3367
|
visit(candidate);
|
|
2562
3368
|
break;
|
|
2563
3369
|
}
|
|
@@ -2573,19 +3379,35 @@ function writeArtifact(cwd, config, built) {
|
|
|
2573
3379
|
mkdirSync(directory, { recursive: true });
|
|
2574
3380
|
const startup = startupCatalog2(built.catalog);
|
|
2575
3381
|
const text = catalogFileText2(serializeCatalog2(startup));
|
|
2576
|
-
|
|
2577
|
-
|
|
3382
|
+
writeFileSync2(join4(directory, "catalog.json"), text);
|
|
3383
|
+
writeFileSync2(join4(directory, "catalog.hash"), `${sha2562(text)}
|
|
2578
3384
|
`);
|
|
2579
|
-
|
|
3385
|
+
writeFileSync2(join4(directory, "types.d.ts"), typesText(cwd, config.schema, built));
|
|
3386
|
+
writeNamespace(directory, catalogNamespace(config));
|
|
2580
3387
|
return directory;
|
|
2581
3388
|
}
|
|
3389
|
+
function writeNamespace(directory, namespace) {
|
|
3390
|
+
const path = join4(directory, "namespace");
|
|
3391
|
+
if (namespace === "public") {
|
|
3392
|
+
if (existsSync4(path))
|
|
3393
|
+
unlinkSync(path);
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
3396
|
+
writeFileSync2(path, `${namespace}
|
|
3397
|
+
`);
|
|
3398
|
+
}
|
|
2582
3399
|
async function loadBuiltSchema(cwd, config) {
|
|
2583
|
-
return withRoles(await loadSchema(cwd, config.schema), config);
|
|
3400
|
+
return withRoles(await loadSchema(cwd, config.schema), config, cwd);
|
|
3401
|
+
}
|
|
3402
|
+
async function exportProject(cwd, args) {
|
|
3403
|
+
const config = await loadConfig(cwd);
|
|
3404
|
+
const built = await loadBuiltSchema(cwd, config);
|
|
3405
|
+
return renderExport(cwd, config, built.catalog, args);
|
|
2584
3406
|
}
|
|
2585
3407
|
async function loadConfig(cwd) {
|
|
2586
3408
|
const path = projectPath(cwd, "okmodel.config.ts");
|
|
2587
3409
|
const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
|
|
2588
|
-
const record =
|
|
3410
|
+
const record = isRecord2(imported) ? imported : {};
|
|
2589
3411
|
const config = record.default ?? record.config;
|
|
2590
3412
|
if (!isConfig(config)) {
|
|
2591
3413
|
throw new OkmError2("invalid", "okmodel.config.ts must default-export defineConfig(...).");
|
|
@@ -2595,7 +3417,7 @@ async function loadConfig(cwd) {
|
|
|
2595
3417
|
async function loadSchema(cwd, spec) {
|
|
2596
3418
|
const path = projectPath(cwd, spec);
|
|
2597
3419
|
const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
|
|
2598
|
-
const record =
|
|
3420
|
+
const record = isRecord2(imported) ? imported : {};
|
|
2599
3421
|
for (const value of Object.values(record)) {
|
|
2600
3422
|
if (!isBuilt(value))
|
|
2601
3423
|
continue;
|
|
@@ -2605,13 +3427,13 @@ async function loadSchema(cwd, spec) {
|
|
|
2605
3427
|
throw new OkmError2("invalid", `${spec} must export a schema().`);
|
|
2606
3428
|
}
|
|
2607
3429
|
function readPrevious(directory) {
|
|
2608
|
-
if (!
|
|
3430
|
+
if (!existsSync4(directory))
|
|
2609
3431
|
return;
|
|
2610
3432
|
const catalogs = readdirSync3(directory).filter((file) => file.endsWith(".catalog.json")).sort();
|
|
2611
3433
|
const latest = catalogs.at(-1);
|
|
2612
3434
|
if (latest === undefined)
|
|
2613
3435
|
return;
|
|
2614
|
-
const text =
|
|
3436
|
+
const text = readFileSync4(join4(directory, latest), "utf8");
|
|
2615
3437
|
return { catalog: parseCatalog2(text) };
|
|
2616
3438
|
}
|
|
2617
3439
|
function referenceAugmentation(source) {
|
|
@@ -2636,7 +3458,7 @@ function referenceAugmentation(source) {
|
|
|
2636
3458
|
}
|
|
2637
3459
|
function nextNumber(directory) {
|
|
2638
3460
|
let max = 0;
|
|
2639
|
-
if (
|
|
3461
|
+
if (existsSync4(directory)) {
|
|
2640
3462
|
for (const file of readdirSync3(directory)) {
|
|
2641
3463
|
const match = /^(\d+)_/.exec(file);
|
|
2642
3464
|
const value = match?.[1];
|
|
@@ -2653,14 +3475,14 @@ function safeName(name) {
|
|
|
2653
3475
|
return name;
|
|
2654
3476
|
}
|
|
2655
3477
|
function isBuilt(value) {
|
|
2656
|
-
if (!
|
|
3478
|
+
if (!isRecord2(value) || !Array.isArray(value.tables))
|
|
2657
3479
|
return false;
|
|
2658
3480
|
return "catalog" in value;
|
|
2659
3481
|
}
|
|
2660
3482
|
function isConfig(value) {
|
|
2661
|
-
return
|
|
3483
|
+
return isRecord2(value) && typeof value.schema === "string";
|
|
2662
3484
|
}
|
|
2663
|
-
function
|
|
3485
|
+
function isRecord2(value) {
|
|
2664
3486
|
return typeof value === "object" && value !== null;
|
|
2665
3487
|
}
|
|
2666
3488
|
var SKIP_SCAN = new Set(["node_modules", "dist", "coverage"]);
|
|
@@ -2685,7 +3507,7 @@ function directoryImportsValidate(dir) {
|
|
|
2685
3507
|
for (const entry of entries) {
|
|
2686
3508
|
if (entry.name.startsWith(".") || SKIP_SCAN.has(entry.name))
|
|
2687
3509
|
continue;
|
|
2688
|
-
const path =
|
|
3510
|
+
const path = join4(dir, entry.name);
|
|
2689
3511
|
if (entry.isDirectory()) {
|
|
2690
3512
|
if (directoryImportsValidate(path))
|
|
2691
3513
|
return true;
|
|
@@ -2693,7 +3515,7 @@ function directoryImportsValidate(dir) {
|
|
|
2693
3515
|
}
|
|
2694
3516
|
if (!/\.(?:ts|tsx|mts|cts)$/.test(entry.name))
|
|
2695
3517
|
continue;
|
|
2696
|
-
if (runtimeImportsValidate(
|
|
3518
|
+
if (runtimeImportsValidate(readFileSync4(path, "utf8")))
|
|
2697
3519
|
return true;
|
|
2698
3520
|
}
|
|
2699
3521
|
return false;
|
|
@@ -2715,4 +3537,4 @@ function runtimeImportsValidate(source) {
|
|
|
2715
3537
|
return false;
|
|
2716
3538
|
}
|
|
2717
3539
|
|
|
2718
|
-
export { projectPath, errorDoc, loadMigrations, readMigrationPlan, loadHeadSnapshot, lintMigrationDirectory, formatFindings, hasError, lintRefusal, buildProject, generateProject, planProject, checkProject, projectHead, openProject, loadBuiltSchema, loadConfig, loadSchema };
|
|
3540
|
+
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 };
|