okmodel 0.1.1 → 0.3.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 +727 -37
- package/dist/adapters/capabilities.d.ts +34 -0
- package/dist/adapters/pg/bunsql-extra.d.ts +17 -0
- package/dist/adapters/pg/bunsql.d.ts +60 -0
- package/dist/adapters/pg/call.d.ts +2 -12
- package/dist/adapters/pg/nodepostgres-extra.d.ts +18 -0
- package/dist/adapters/pg/nodepostgres.d.ts +57 -0
- package/dist/adapters/pg/postgres-extra.d.ts +50 -1
- package/dist/adapters/pg/postgresjs.d.ts +0 -4
- package/dist/adapters/pg/session.d.ts +94 -0
- package/dist/adapters/pg/watch.d.ts +18 -0
- package/dist/contracts/catalog/enum.d.ts +31 -4
- package/dist/contracts/catalog/extension.d.ts +61 -0
- package/dist/contracts/catalog/object.d.ts +1 -0
- package/dist/contracts/catalog/privilege.d.ts +64 -0
- package/dist/contracts/catalog/routine.d.ts +56 -0
- package/dist/contracts/catalog/types.d.ts +179 -18
- package/dist/contracts/catalog/view.d.ts +73 -0
- package/dist/contracts/connection.d.ts +13 -0
- package/dist/contracts/driver.d.ts +18 -5
- package/dist/contracts/error.d.ts +17 -1
- package/dist/contracts/generator.d.ts +63 -0
- package/dist/contracts/index.d.ts +2 -2
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/internal.js +11 -9
- package/dist/contracts/location.d.ts +24 -0
- package/dist/contracts/rows.d.ts +15 -0
- package/dist/dialects/pg/archive-bind.d.ts +22 -0
- package/dist/dialects/pg/column.d.ts +129 -10
- package/dist/dialects/pg/compile.d.ts +1 -0
- package/dist/dialects/pg/custom.d.ts +6 -0
- package/dist/dialects/pg/ddl.d.ts +65 -1
- package/dist/dialects/pg/enum.d.ts +10 -8
- package/dist/dialects/pg/ext/citext.d.ts +14 -0
- package/dist/dialects/pg/ext/citext.js +11 -0
- package/dist/dialects/pg/ext/index.d.ts +52 -0
- package/dist/dialects/pg/ext/index.js +6 -0
- package/dist/dialects/pg/ext/pg-trgm.d.ts +53 -0
- package/dist/dialects/pg/ext/pg-trgm.js +42 -0
- package/dist/dialects/pg/filter-parse.d.ts +19 -0
- package/dist/dialects/pg/filters.d.ts +19 -0
- package/dist/dialects/pg/fn/index.d.ts +107 -0
- package/dist/dialects/pg/fn/index.js +175 -0
- package/dist/dialects/pg/fn/touch.d.ts +14 -0
- package/dist/dialects/pg/index.d.ts +11 -2
- package/dist/dialects/pg/index.js +243 -1062
- package/dist/dialects/pg/introspect.d.ts +10 -1
- package/dist/dialects/pg/keys.d.ts +19 -3
- package/dist/dialects/pg/model.d.ts +223 -2
- package/dist/dialects/pg/operator-fit.d.ts +86 -0
- package/dist/dialects/pg/operators.d.ts +46 -1
- package/dist/dialects/pg/ops/arr-append.d.ts +9 -0
- package/dist/dialects/pg/ops/arr-ns.d.ts +7 -0
- package/dist/dialects/pg/ops/arr-remove.d.ts +9 -0
- package/dist/dialects/pg/ops/containedBy.d.ts +9 -0
- package/dist/dialects/pg/ops/contains.d.ts +8 -4
- package/dist/dialects/pg/ops/hasAnyKey.d.ts +8 -0
- package/dist/dialects/pg/ops/hasKey.d.ts +8 -0
- package/dist/dialects/pg/ops/json-ns.d.ts +6 -0
- package/dist/dialects/pg/ops/json-set.d.ts +12 -0
- package/dist/dialects/pg/ops/matches.d.ts +16 -0
- package/dist/dialects/pg/ops/overlaps.d.ts +9 -0
- package/dist/dialects/pg/ops/path.d.ts +13 -0
- package/dist/dialects/pg/preset.d.ts +76 -0
- package/dist/dialects/pg/relations.d.ts +59 -2
- package/dist/dialects/pg/role/check.d.ts +45 -0
- package/dist/dialects/pg/role/index.d.ts +50 -0
- package/dist/dialects/pg/role/sql.d.ts +44 -0
- package/dist/dialects/pg/schema.d.ts +109 -5
- package/dist/dialects/pg/table.d.ts +137 -11
- package/dist/dialects/pg/temporal.d.ts +9 -7
- package/dist/dialects/pg/tenancy.d.ts +103 -0
- package/dist/dialects/pg/through.d.ts +21 -0
- package/dist/dialects/pg/trait.d.ts +141 -0
- package/dist/dialects/pg/view/client.d.ts +30 -0
- package/dist/dialects/pg/view/depend.d.ts +35 -0
- package/dist/dialects/pg/view/index.d.ts +114 -0
- package/dist/dialects/pg/view/index.js +289 -0
- package/dist/dialects/pg/view/scratch.d.ts +21 -0
- package/dist/dialects/pg/where.d.ts +37 -0
- package/dist/runtime/aggregate.d.ts +25 -0
- package/dist/runtime/archive-handle.d.ts +18 -0
- package/dist/runtime/archive-rules.d.ts +12 -0
- package/dist/runtime/archive.d.ts +73 -0
- package/dist/runtime/batch.d.ts +20 -0
- package/dist/runtime/client.d.ts +114 -5
- package/dist/runtime/conflict.d.ts +2 -1
- package/dist/runtime/exposure.d.ts +27 -0
- package/dist/runtime/hookm.d.ts +39 -0
- package/dist/runtime/ids/index.d.ts +11 -0
- package/dist/runtime/ids/index.js +193 -0
- package/dist/runtime/ids/okid-extended.d.ts +19 -0
- package/dist/runtime/ids/okid-generator.d.ts +21 -0
- package/dist/runtime/ids/okid-shared.d.ts +89 -0
- package/dist/runtime/ids/okid.d.ts +24 -0
- package/dist/runtime/ids/uuid.d.ts +21 -0
- package/dist/runtime/lock.d.ts +22 -0
- package/dist/runtime/operator-sql.d.ts +8 -0
- package/dist/runtime/page.d.ts +25 -0
- package/dist/runtime/pg/bun.d.ts +30 -0
- package/dist/runtime/pg/bun.js +52 -0
- package/dist/runtime/pg/pg.d.ts +29 -0
- package/dist/runtime/pg/pg.js +52 -0
- package/dist/runtime/pg/pglite.js +16 -17
- package/dist/runtime/pg/postgresjs.js +14 -24
- package/dist/runtime/plan.d.ts +146 -7
- package/dist/runtime/preset-stack.d.ts +17 -0
- package/dist/runtime/presets.d.ts +48 -0
- package/dist/runtime/read-stream.d.ts +26 -0
- package/dist/runtime/safety/index.d.ts +159 -0
- package/dist/runtime/safety/index.js +294 -0
- package/dist/runtime/safety-hook.d.ts +42 -0
- package/dist/runtime/server-check.d.ts +34 -0
- package/dist/runtime/tenancy/index.d.ts +37 -0
- package/dist/runtime/tenancy/index.js +368 -0
- package/dist/runtime/trait-read.d.ts +30 -0
- package/dist/runtime/traits/index.d.ts +133 -0
- package/dist/runtime/traits/index.js +646 -0
- package/dist/runtime/transaction.d.ts +19 -0
- package/dist/runtime/tx.d.ts +22 -7
- package/dist/runtime/types.d.ts +376 -27
- package/dist/runtime/validate/bind.d.ts +11 -0
- package/dist/runtime/validate/closed.d.ts +16 -0
- package/dist/runtime/validate/engine.d.ts +41 -0
- package/dist/runtime/validate/hook.d.ts +17 -0
- package/dist/runtime/validate/index.d.ts +21 -0
- package/dist/runtime/validate/index.js +175 -0
- package/dist/runtime/validate/load.d.ts +13 -0
- package/dist/runtime/validate/places.d.ts +41 -0
- package/dist/runtime/validate/rules.d.ts +108 -0
- package/dist/runtime/validate/surface.d.ts +102 -0
- package/dist/runtime/write.d.ts +30 -5
- package/dist/shared/01yr20s5.js +149 -0
- package/dist/shared/0jc3ffzf.js +154 -0
- package/dist/shared/0v1g424p.js +26 -0
- package/dist/shared/1g0drakv.js +225 -0
- package/dist/shared/{kprcwaha.js → 1tfdfvhh.js} +5 -3
- package/dist/shared/{05w44d8g.js → 2ssqrv66.js} +767 -715
- package/dist/shared/3ekj9x92.js +210 -0
- package/dist/shared/49ypnfwt.js +1122 -0
- package/dist/shared/4e4c6vq2.js +62 -0
- package/dist/shared/4m9fn52z.js +70 -0
- package/dist/shared/{ycwz42az.js → 52szzakm.js} +6 -26
- package/dist/shared/5nmdenb6.js +1229 -0
- package/dist/shared/672t5vym.js +12 -0
- package/dist/shared/6bazvqwm.js +86 -0
- package/dist/shared/6w5j844a.js +201 -0
- package/dist/shared/{fgqgsb5z.js → 75xkjv0t.js} +2 -2
- package/dist/shared/7fvtrkwr.js +84 -0
- package/dist/shared/7m88x3e9.js +45 -0
- package/dist/shared/{mtkwv8wx.js → 7p2wbz7f.js} +1 -1
- package/dist/shared/83eaefwr.js +10 -0
- package/dist/shared/94ez8kzh.js +19 -0
- package/dist/shared/96ps4g1z.js +32 -0
- package/dist/shared/{xqbhg6cn.js → 999ade2q.js} +95 -10
- package/dist/shared/9m80mkac.js +158 -0
- package/dist/shared/9x25fxhc.js +150 -0
- package/dist/shared/a5sxz52y.js +13 -0
- package/dist/shared/axhk06q1.js +156 -0
- package/dist/shared/{dynq16vb.js → b066g80h.js} +8 -6
- package/dist/shared/bqha5cg6.js +229 -0
- package/dist/shared/bryqk9k9.js +31 -0
- package/dist/shared/c8aghh05.js +442 -0
- package/dist/shared/c9z5g0fm.js +309 -0
- package/dist/shared/{m3ypagh5.js → dbjej1tw.js} +124 -15
- package/dist/shared/dyj86cmj.js +69 -0
- package/dist/shared/e6wg00wd.js +164 -0
- package/dist/shared/{hadrfb9h.js → edwqqryd.js} +17 -14
- package/dist/shared/{qk3hxfja.js → fptpmvxp.js} +5 -5
- package/dist/shared/fs0ex00p.js +76 -0
- package/dist/shared/{jjb7yht9.js → g4f5kcx9.js} +3 -3
- package/dist/shared/gjthds9j.js +48 -0
- package/dist/shared/hcmdtwgh.js +102 -0
- package/dist/shared/{g5fhqmwe.js → jkc6ws1p.js} +6 -56
- package/dist/shared/{yd1kzwn2.js → jns2wx4t.js} +55 -12
- package/dist/shared/k34e0xf2.js +450 -0
- package/dist/shared/k3ted12y.js +33 -0
- package/dist/shared/k7dwkhvq.js +31 -0
- package/dist/shared/{8b04d28c.js → kgnc8grt.js} +2 -2
- package/dist/shared/{hd293j9g.js → kyxb62mc.js} +14 -2
- package/dist/shared/m78s7898.js +376 -0
- package/dist/shared/m9tv7xn7.js +404 -0
- package/dist/shared/meck53jv.js +99 -0
- package/dist/shared/mt5wswjz.js +90 -0
- package/dist/shared/n0dgs0pb.js +75 -0
- package/dist/shared/n442h54c.js +14 -0
- package/dist/shared/n4jqhpqz.js +27 -0
- package/dist/shared/n7mcc1gk.js +132 -0
- package/dist/shared/ne02ng5c.js +68 -0
- package/dist/shared/{mkjmqjv4.js → nfzc5z0w.js} +2 -104
- package/dist/shared/nsva2a6b.js +42 -0
- package/dist/shared/p24x2334.js +195 -0
- package/dist/shared/qbxzvwwc.js +88 -0
- package/dist/shared/{w8samvk1.js → qybh48nd.js} +1 -1
- package/dist/shared/r6nwkxnt.js +86 -0
- package/dist/shared/{xy79j5hq.js → rrstp3kb.js} +25 -36
- package/dist/shared/rwmrejjz.js +26 -0
- package/dist/shared/{zhc5vmgf.js → s6gqfn5j.js} +2 -2
- package/dist/shared/{523ft3e8.js → scwxrpx2.js} +7 -5
- package/dist/shared/{j5cn2963.js → t4tbsy9h.js} +13 -64
- package/dist/shared/{y9m15dfv.js → t616ynfn.js} +544 -32
- package/dist/shared/{ngk8gj61.js → tbszx8mz.js} +15 -1
- package/dist/shared/th14ygv2.js +69 -0
- package/dist/shared/tmbdywx0.js +178 -0
- package/dist/shared/ty3v3hfk.js +64 -0
- package/dist/shared/wekze34d.js +246 -0
- package/dist/shared/x6wysh83.js +1132 -0
- package/dist/shared/{rgs0st28.js → xrfnq5k8.js} +237 -434
- package/dist/shared/{x5x59srr.js → xva8v7qk.js} +416 -83
- package/dist/shared/{dkn3y60a.js → xw1d69vd.js} +76 -112
- package/dist/shared/y005b761.js +112 -0
- package/dist/shared/ydaz1sv3.js +55 -0
- package/dist/shared/{p0cbr3g2.js → zwmqxb0g.js} +1 -1
- package/dist/tooling/cli.js +40 -12
- package/dist/tooling/migrate/apply.d.ts +16 -0
- package/dist/tooling/migrate/commands.d.ts +12 -1
- package/dist/tooling/migrate/config.d.ts +8 -0
- package/dist/tooling/migrate/doctor.d.ts +25 -0
- package/dist/tooling/migrate/drift.d.ts +19 -0
- package/dist/tooling/migrate/engine.d.ts +36 -0
- package/dist/tooling/migrate/ext.d.ts +17 -0
- package/dist/tooling/migrate/extensions.d.ts +51 -0
- package/dist/tooling/migrate/managed.d.ts +17 -0
- package/dist/tooling/migrate/plan.d.ts +14 -1
- package/dist/tooling/migrate/project.d.ts +25 -1
- package/dist/tooling/migrate/type-name.d.ts +19 -0
- package/package.json +61 -4
- package/dist/shared/gk7cs7sp.js +0 -566
|
@@ -1,170 +1,419 @@
|
|
|
1
1
|
import {
|
|
2
2
|
creationOrder2,
|
|
3
3
|
renameColumn2,
|
|
4
|
-
renameTable2
|
|
5
|
-
|
|
6
|
-
catalogHash2,
|
|
7
|
-
parseCatalog2
|
|
8
|
-
} from "./y9m15dfv.js";
|
|
4
|
+
renameTable2
|
|
5
|
+
} from "./t616ynfn.js";
|
|
9
6
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
quoteIdent,
|
|
8
|
+
ownedByView,
|
|
9
|
+
dropObjectSql,
|
|
10
|
+
createObjectSql,
|
|
11
|
+
functionSql,
|
|
12
|
+
viewSql,
|
|
13
|
+
refreshMaterializedViewSql,
|
|
14
|
+
alterColumnSql,
|
|
15
|
+
identitySequence,
|
|
16
|
+
createTableSql,
|
|
17
|
+
qualify
|
|
18
|
+
} from "./wekze34d.js";
|
|
15
19
|
import {
|
|
16
|
-
|
|
20
|
+
isExactVersion,
|
|
21
|
+
compareExtensionVersions,
|
|
22
|
+
sameExtensionDefinition
|
|
23
|
+
} from "./th14ygv2.js";
|
|
24
|
+
import {
|
|
25
|
+
isDomain,
|
|
17
26
|
sameEnumLabels
|
|
18
|
-
} from "./
|
|
27
|
+
} from "./qbxzvwwc.js";
|
|
28
|
+
import {
|
|
29
|
+
quoteLiteral
|
|
30
|
+
} from "./94ez8kzh.js";
|
|
19
31
|
import {
|
|
20
32
|
staticNamespace2,
|
|
21
33
|
identityKey2,
|
|
34
|
+
identityLabel2,
|
|
22
35
|
compareText
|
|
23
|
-
} from "./
|
|
36
|
+
} from "./t4tbsy9h.js";
|
|
37
|
+
import {
|
|
38
|
+
fitIdentifier2
|
|
39
|
+
} from "./nfzc5z0w.js";
|
|
24
40
|
import {
|
|
25
41
|
OkmError2
|
|
26
|
-
} from "./
|
|
42
|
+
} from "./52szzakm.js";
|
|
27
43
|
|
|
28
|
-
// src/dialects/pg/
|
|
29
|
-
|
|
30
|
-
|
|
44
|
+
// src/dialects/pg/role/sql.ts
|
|
45
|
+
var PRIVILEGE = /^(select|insert|update|delete|execute|usage)$/;
|
|
46
|
+
function privilegeSql(before, after, schema) {
|
|
47
|
+
const beforeBy = index(before);
|
|
48
|
+
const afterBy = index(after);
|
|
49
|
+
const revoke = [];
|
|
50
|
+
const roles = [];
|
|
51
|
+
const defaults = [];
|
|
52
|
+
const grant = [];
|
|
53
|
+
for (const [key, object] of beforeBy) {
|
|
54
|
+
if (afterBy.has(key))
|
|
55
|
+
continue;
|
|
56
|
+
if (object.kind === "grant")
|
|
57
|
+
revoke.push(grantStatement(object, schema, true));
|
|
58
|
+
if (object.kind === "defaultPrivilege")
|
|
59
|
+
revoke.push(defaultStatement(object, true));
|
|
60
|
+
}
|
|
61
|
+
for (const [key, object] of afterBy) {
|
|
62
|
+
const previous = beforeBy.get(key);
|
|
63
|
+
if (object.kind === "role") {
|
|
64
|
+
if (object.owner !== "managed")
|
|
65
|
+
continue;
|
|
66
|
+
if (previous === undefined) {
|
|
67
|
+
roles.push(createRoleSql(object));
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (previous.kind !== "role" || previous.owner !== "managed")
|
|
71
|
+
continue;
|
|
72
|
+
if (previous.definition.login !== object.definition.login || previous.definition.inherit !== object.definition.inherit) {
|
|
73
|
+
roles.push(alterRoleSql(object));
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (previous !== undefined)
|
|
78
|
+
continue;
|
|
79
|
+
if (object.kind === "defaultPrivilege")
|
|
80
|
+
defaults.push(defaultStatement(object, false));
|
|
81
|
+
if (object.kind === "grant")
|
|
82
|
+
grant.push(grantStatement(object, schema, false));
|
|
83
|
+
}
|
|
84
|
+
return { revoke, prepare: [...roles, ...defaults], grant };
|
|
85
|
+
}
|
|
86
|
+
function createdRoleName(sql) {
|
|
87
|
+
const match = /^\s*create\s+role\s+(?:"([^"]+)"|([A-Za-z_][\w$]*))/i.exec(sql);
|
|
88
|
+
const quoted = match?.[1];
|
|
89
|
+
if (quoted !== undefined)
|
|
90
|
+
return quoted.replaceAll('""', '"');
|
|
91
|
+
return match?.[2];
|
|
92
|
+
}
|
|
93
|
+
function changesRole(sql) {
|
|
94
|
+
return /^\s*(create|alter)\s+role\b/i.test(sql);
|
|
95
|
+
}
|
|
96
|
+
function index(objects) {
|
|
97
|
+
const by = new Map;
|
|
98
|
+
for (const object of objects) {
|
|
99
|
+
if (object.kind !== "role" && object.kind !== "grant" && object.kind !== "defaultPrivilege") {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
by.set(identityKey2(object.identity), object);
|
|
103
|
+
}
|
|
104
|
+
return by;
|
|
105
|
+
}
|
|
106
|
+
function createRoleSql(object) {
|
|
107
|
+
return `create role ${quoteIdent(object.identity.name)} with nosuperuser nocreatedb nocreaterole ${inherit(object)} ${login(object)}`;
|
|
108
|
+
}
|
|
109
|
+
function alterRoleSql(object) {
|
|
110
|
+
return `alter role ${quoteIdent(object.identity.name)} with ${inherit(object)} ${login(object)}`;
|
|
111
|
+
}
|
|
112
|
+
function login(object) {
|
|
113
|
+
return object.definition.login ? "login" : "nologin";
|
|
114
|
+
}
|
|
115
|
+
function inherit(object) {
|
|
116
|
+
return object.definition.inherit ? "inherit" : "noinherit";
|
|
117
|
+
}
|
|
118
|
+
function grantStatement(object, schema, revoke) {
|
|
119
|
+
const privilege = object.identity.privilege.toLowerCase();
|
|
120
|
+
assertPrivilege(privilege);
|
|
121
|
+
const verb = revoke ? "revoke" : "grant";
|
|
122
|
+
const direction = revoke ? "from" : "to";
|
|
123
|
+
return `${verb} ${privilege} on ${grantTarget(object.identity.object, schema)} ${direction} ${quoteIdent(object.identity.role)}`;
|
|
124
|
+
}
|
|
125
|
+
function defaultStatement(object, revoke) {
|
|
126
|
+
const privilege = object.identity.privilege.toLowerCase();
|
|
127
|
+
assertPrivilege(privilege);
|
|
128
|
+
const verb = revoke ? "revoke" : "grant";
|
|
129
|
+
const direction = revoke ? "from" : "to";
|
|
130
|
+
const namespace = object.identity.namespace.form === "static" ? object.identity.namespace.name : "public";
|
|
131
|
+
return `alter default privileges for role ${quoteIdent(object.identity.forRole)} in schema ${quoteIdent(namespace)} ${verb} ${privilege} on ${objectKindSql(object.identity.objectKind)} ${direction} ${quoteIdent(object.identity.grantee)}`;
|
|
132
|
+
}
|
|
133
|
+
function grantTarget(object, schema) {
|
|
134
|
+
const name = object.namespace.form === "static" ? object.namespace.name : schema;
|
|
135
|
+
if (object.kind === "sequence")
|
|
136
|
+
return `sequence ${qualify(name, object.name)}`;
|
|
137
|
+
if (object.kind === "function")
|
|
138
|
+
return functionTarget(object.name, name);
|
|
139
|
+
if (object.kind === "namespace")
|
|
140
|
+
return `schema ${quoteIdent(object.name)}`;
|
|
141
|
+
return `table ${qualify(name, object.name)}`;
|
|
142
|
+
}
|
|
143
|
+
function functionTarget(name, schema) {
|
|
144
|
+
const open = name.lastIndexOf("(");
|
|
145
|
+
if (open < 0 || !name.endsWith(")"))
|
|
146
|
+
return `function ${qualify(schema, name)}()`;
|
|
147
|
+
const fn = name.slice(0, open);
|
|
148
|
+
const args = name.slice(open + 1, -1);
|
|
149
|
+
return `function ${qualify(schema, fn)}(${args})`;
|
|
150
|
+
}
|
|
151
|
+
function objectKindSql(kind) {
|
|
152
|
+
if (kind === "function")
|
|
153
|
+
return "functions";
|
|
154
|
+
if (kind === "sequence")
|
|
155
|
+
return "sequences";
|
|
156
|
+
return "tables";
|
|
157
|
+
}
|
|
158
|
+
function assertPrivilege(privilege) {
|
|
159
|
+
if (!PRIVILEGE.test(privilege)) {
|
|
160
|
+
throw new OkmError2("invalid", `Privilege ${privilege} is not emitted.`, {
|
|
161
|
+
fix: {
|
|
162
|
+
summary: "Grants in this version are select, insert, update, delete, execute, and usage."
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
31
166
|
}
|
|
32
|
-
|
|
33
|
-
|
|
167
|
+
|
|
168
|
+
// src/tooling/migrate/extensions.ts
|
|
169
|
+
var CREATE_EXTENSION = /^\s*create\s+extension\s+(?:"([^"]+)"|([A-Za-z_][\w$]*))/i;
|
|
170
|
+
var UPDATE_EXTENSION = /^\s*alter\s+extension\s+(?:"([^"]+)"|([A-Za-z_][\w$]*))\s+update\s+to\s+'([^']*)'/i;
|
|
171
|
+
function extensionAlterSteps(beforeBy, afterBy, dropKeys) {
|
|
172
|
+
refuseDrops(beforeBy, afterBy, dropKeys);
|
|
173
|
+
const steps = [];
|
|
174
|
+
for (const after of afterBy.values()) {
|
|
175
|
+
if (after.kind !== "extension")
|
|
176
|
+
continue;
|
|
177
|
+
const before = beforeBy.get(identityKey2(after.identity));
|
|
178
|
+
if (before === undefined || before.kind !== "extension")
|
|
179
|
+
continue;
|
|
180
|
+
if (sameExtensionDefinition(before, after))
|
|
181
|
+
continue;
|
|
182
|
+
steps.push(...alterExtension(before, after));
|
|
183
|
+
}
|
|
184
|
+
return steps;
|
|
185
|
+
}
|
|
186
|
+
async function assertExtensionsAvailable(connection, migrations) {
|
|
187
|
+
const creates = new Set;
|
|
188
|
+
const updates = [];
|
|
189
|
+
for (const migration of migrations) {
|
|
190
|
+
for (const step of migration.steps) {
|
|
191
|
+
const created = CREATE_EXTENSION.exec(step.sql);
|
|
192
|
+
const name = created?.[1] ?? created?.[2];
|
|
193
|
+
if (name !== undefined)
|
|
194
|
+
creates.add(name.replaceAll('""', '"'));
|
|
195
|
+
const updated = UPDATE_EXTENSION.exec(step.sql);
|
|
196
|
+
const updateName = updated?.[1] ?? updated?.[2];
|
|
197
|
+
const version = updated?.[3];
|
|
198
|
+
if (updateName !== undefined && version !== undefined) {
|
|
199
|
+
updates.push({ name: updateName.replaceAll('""', '"'), version });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (creates.size === 0 && updates.length === 0)
|
|
34
204
|
return;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
205
|
+
const names = [...creates, ...updates.map((item) => item.name)];
|
|
206
|
+
const available = await availableExtensions(connection, names);
|
|
207
|
+
for (const name of creates) {
|
|
208
|
+
if (!available.has(name)) {
|
|
209
|
+
throw new OkmError2("OKM1811", `Extension ${name} is not available on the server. Nothing was changed.`, { fix: { summary: "Install the extension, or remove the declaration." } });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
for (const update of updates) {
|
|
213
|
+
if (!available.has(update.name)) {
|
|
214
|
+
throw new OkmError2("OKM1811", `Extension ${update.name} is not available on the server. Nothing was changed.`, { fix: { summary: "Install the extension, or remove the declaration." } });
|
|
215
|
+
}
|
|
216
|
+
const installed = await installedVersion(connection, update.name);
|
|
217
|
+
if (installed === undefined) {
|
|
218
|
+
throw new OkmError2("OKM1811", `Extension ${update.name} is not installed, so it cannot be updated. Nothing was changed.`, { fix: { summary: "Create the extension before updating it." } });
|
|
219
|
+
}
|
|
220
|
+
const path = await updatePath(connection, update.name, installed, update.version);
|
|
221
|
+
if (!path) {
|
|
222
|
+
throw new OkmError2("OKM1814", `Extension ${update.name} has no update path from ${installed} to ${update.version}. Nothing was changed.`, {
|
|
223
|
+
fix: {
|
|
224
|
+
summary: "Drop or move the dependents first. Postgres cannot lower an extension version."
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
50
228
|
}
|
|
51
229
|
}
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return;
|
|
230
|
+
function refuseDrops(beforeBy, afterBy, dropKeys) {
|
|
231
|
+
for (const key of dropKeys) {
|
|
232
|
+
const object = beforeBy.get(key);
|
|
233
|
+
if (object?.kind !== "extension")
|
|
234
|
+
continue;
|
|
235
|
+
for (const after of afterBy.values()) {
|
|
236
|
+
const used = after.dependencies.some((edge) => edge.target.kind === "extension" && edge.target.name === object.identity.name);
|
|
237
|
+
if (!used)
|
|
238
|
+
continue;
|
|
239
|
+
throw new OkmError2("OKM1814", `Extension ${object.identity.name} still has dependents. Nothing was dropped.`, {
|
|
240
|
+
fix: {
|
|
241
|
+
summary: "Drop or move the dependents first. Postgres cannot lower an extension version."
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
68
245
|
}
|
|
69
246
|
}
|
|
70
|
-
function
|
|
71
|
-
const
|
|
72
|
-
const table = qualify(schema, after.identity.parent.name);
|
|
247
|
+
function alterExtension(before, after) {
|
|
248
|
+
const steps = [];
|
|
73
249
|
const name = quoteIdent(after.identity.name);
|
|
74
|
-
if (before.definition.
|
|
75
|
-
|
|
250
|
+
if (before.definition.schema !== after.definition.schema) {
|
|
251
|
+
if (!after.definition.relocatable) {
|
|
252
|
+
throw new OkmError2("OKM1814", `Extension ${after.identity.name} is not relocatable, so it cannot move to ${after.definition.schema}.`, {
|
|
253
|
+
fix: {
|
|
254
|
+
summary: "Drop or move the dependents first. Postgres cannot lower an extension version."
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
steps.push({
|
|
259
|
+
sql: `alter extension ${name} set schema ${quoteIdent(after.definition.schema)}`,
|
|
260
|
+
class: "contract",
|
|
261
|
+
action: "ddl",
|
|
262
|
+
lock: "ACCESS EXCLUSIVE",
|
|
263
|
+
transactional: true
|
|
264
|
+
});
|
|
76
265
|
}
|
|
77
|
-
|
|
78
|
-
|
|
266
|
+
const previous = before.definition.version;
|
|
267
|
+
const next = after.definition.version;
|
|
268
|
+
if (!isExactVersion(previous) || !isExactVersion(next) || previous === next)
|
|
269
|
+
return steps;
|
|
270
|
+
if (compareExtensionVersions(next, previous) < 0) {
|
|
271
|
+
throw new OkmError2("OKM1814", `Extension ${after.identity.name} cannot move from ${previous} to ${next}. Postgres cannot lower an extension version.`, {
|
|
272
|
+
fix: {
|
|
273
|
+
summary: "Drop or move the dependents first. Postgres cannot lower an extension version."
|
|
274
|
+
}
|
|
275
|
+
});
|
|
79
276
|
}
|
|
80
|
-
|
|
81
|
-
|
|
277
|
+
steps.push({
|
|
278
|
+
sql: `alter extension ${name} update to ${quoteLiteral(next)}`,
|
|
279
|
+
class: "expand",
|
|
280
|
+
action: "ddl",
|
|
281
|
+
lock: "ACCESS EXCLUSIVE",
|
|
282
|
+
transactional: true,
|
|
283
|
+
path: "unverified"
|
|
284
|
+
});
|
|
285
|
+
return steps;
|
|
286
|
+
}
|
|
287
|
+
async function availableExtensions(connection, names) {
|
|
288
|
+
const listed = await connection.execute(`select name from pg_available_extensions where name in (${names.map((_, index) => `$${String(index + 1)}`).join(", ")})`, names);
|
|
289
|
+
const found = new Set;
|
|
290
|
+
for (const row of listed.rows) {
|
|
291
|
+
const name = row[0];
|
|
292
|
+
if (typeof name === "string")
|
|
293
|
+
found.add(name);
|
|
82
294
|
}
|
|
83
|
-
return
|
|
295
|
+
return found;
|
|
84
296
|
}
|
|
85
|
-
function
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
297
|
+
async function installedVersion(connection, name) {
|
|
298
|
+
const result = await connection.execute("select extversion from pg_extension where extname = $1", [name]);
|
|
299
|
+
const version = result.rows[0]?.[0];
|
|
300
|
+
return typeof version === "string" ? version : undefined;
|
|
301
|
+
}
|
|
302
|
+
async function updatePath(connection, name, source, target) {
|
|
303
|
+
const result = await connection.execute("select path from pg_extension_update_paths($1) where source = $2 and target = $3", [name, source, target]);
|
|
304
|
+
const path = result.rows[0]?.[0];
|
|
305
|
+
return typeof path === "string" && path.length > 0;
|
|
92
306
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const tail = partition === undefined ? "" : ` partition by ${partition.method} (${partition.columns.map((name) => quoteIdent(name)).join(", ")})`;
|
|
104
|
-
return `create table ${qualify(schema, parent)} (
|
|
105
|
-
${lines.join(`,
|
|
106
|
-
`)}
|
|
107
|
-
)${tail}`;
|
|
108
|
-
}
|
|
109
|
-
function createEnumSql(object, schema) {
|
|
110
|
-
const labels = object.definition.labels.map((label) => quoteLiteral(label)).join(", ");
|
|
111
|
-
return `create type ${qualify(schema, object.identity.name)} as enum (${labels})`;
|
|
112
|
-
}
|
|
113
|
-
function columnSql(column) {
|
|
114
|
-
const definition = column.definition;
|
|
115
|
-
let sql = `${quoteIdent(column.identity.name)} ${definition.dataType}`;
|
|
116
|
-
if (definition.identity !== undefined) {
|
|
117
|
-
sql += definition.identity.always ? " generated always as identity" : " generated by default as identity";
|
|
118
|
-
} else if (definition.generated !== undefined) {
|
|
119
|
-
sql += ` generated always as (${definition.generated.expression}) stored`;
|
|
120
|
-
}
|
|
121
|
-
if (!definition.nullable)
|
|
122
|
-
sql += " not null";
|
|
123
|
-
if (definition.defaultExpression !== undefined)
|
|
124
|
-
sql += ` default ${definition.defaultExpression}`;
|
|
125
|
-
return sql;
|
|
126
|
-
}
|
|
127
|
-
function createIndexSql(object, schema) {
|
|
128
|
-
const unique = object.definition.unique ? "unique " : "";
|
|
129
|
-
const target = object.definition.expression !== undefined ? `(${object.definition.expression})` : object.definition.columns.map((name) => quoteIdent(name)).join(", ");
|
|
130
|
-
const where = object.definition.predicate === undefined ? "" : ` where ${object.definition.predicate}`;
|
|
131
|
-
return `create ${unique}index ${quoteIdent(object.identity.name)} on ${qualify(schema, object.identity.parent.name)} (${target})${where}`;
|
|
132
|
-
}
|
|
133
|
-
function createSequenceSql(object, schema) {
|
|
134
|
-
const definition = object.definition;
|
|
135
|
-
const cycle = definition.cycle ? " cycle" : "";
|
|
136
|
-
return `create sequence ${qualify(schema, object.identity.name)} as ${definition.dataType} start ${definition.start} increment ${definition.increment}${cycle}`;
|
|
137
|
-
}
|
|
138
|
-
function constraintBody(object, schema) {
|
|
139
|
-
const definition = object.definition;
|
|
140
|
-
const columns = definition.columns.map((name) => quoteIdent(name)).join(", ");
|
|
141
|
-
switch (definition.constraintKind) {
|
|
142
|
-
case "primaryKey":
|
|
143
|
-
return `primary key (${columns})`;
|
|
144
|
-
case "unique": {
|
|
145
|
-
const nulls = definition.nullsNotDistinct ? " nulls not distinct" : "";
|
|
146
|
-
return `unique${nulls} (${columns})${deferClause(definition)}`;
|
|
307
|
+
|
|
308
|
+
// src/tooling/migrate/managed.ts
|
|
309
|
+
var MANAGED_TABLES = new Set(["okm_meta", "okm_history"]);
|
|
310
|
+
function omitManagedObjects(source) {
|
|
311
|
+
let dropped = false;
|
|
312
|
+
const objects = [];
|
|
313
|
+
for (const object of source.objects) {
|
|
314
|
+
if (ownedByMigration(object)) {
|
|
315
|
+
dropped = true;
|
|
316
|
+
continue;
|
|
147
317
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
318
|
+
objects.push(object);
|
|
319
|
+
}
|
|
320
|
+
if (!dropped)
|
|
321
|
+
return source;
|
|
322
|
+
return { version: source.version, objects };
|
|
323
|
+
}
|
|
324
|
+
function ownedByMigration(object) {
|
|
325
|
+
switch (object.kind) {
|
|
326
|
+
case "table":
|
|
327
|
+
case "view":
|
|
328
|
+
case "materializedView":
|
|
329
|
+
return MANAGED_TABLES.has(object.identity.name);
|
|
330
|
+
case "column":
|
|
331
|
+
case "index":
|
|
332
|
+
case "constraint":
|
|
333
|
+
case "trigger":
|
|
334
|
+
return MANAGED_TABLES.has(object.identity.parent.name);
|
|
335
|
+
case "sequence":
|
|
336
|
+
return managedSequence(object.identity.name);
|
|
337
|
+
case "grant": {
|
|
338
|
+
const target = object.identity.object;
|
|
339
|
+
if (target.kind === "namespace")
|
|
340
|
+
return false;
|
|
341
|
+
if (target.kind === "sequence" || target.kind === "function") {
|
|
342
|
+
return managedSequence(target.name);
|
|
343
|
+
}
|
|
344
|
+
return MANAGED_TABLES.has(target.name);
|
|
156
345
|
}
|
|
157
346
|
default:
|
|
158
|
-
return
|
|
347
|
+
return false;
|
|
159
348
|
}
|
|
160
349
|
}
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
350
|
+
function managedSequence(name) {
|
|
351
|
+
return name === "okm_meta" || name === "okm_history" || name.startsWith("okm_meta_") || name.startsWith("okm_history_");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/tooling/migrate/type-name.ts
|
|
355
|
+
function canonicalTypeName(typeName) {
|
|
356
|
+
const suffix = arraySuffix(typeName);
|
|
357
|
+
const scalar = suffix.length === 0 ? typeName : typeName.slice(0, -suffix.length);
|
|
358
|
+
return canonicalScalar(scalar) + suffix;
|
|
359
|
+
}
|
|
360
|
+
function arraySuffix(typeName) {
|
|
361
|
+
let end = typeName.length;
|
|
362
|
+
let suffix = "";
|
|
363
|
+
while (end >= 2 && typeName.endsWith("[]", end)) {
|
|
364
|
+
suffix = `[]${suffix}`;
|
|
365
|
+
end -= 2;
|
|
366
|
+
}
|
|
367
|
+
return suffix;
|
|
368
|
+
}
|
|
369
|
+
function canonicalScalar(typeName) {
|
|
370
|
+
const varying = /^(?:varchar|character varying)(\(\d+\))$/.exec(typeName);
|
|
371
|
+
if (varying !== null)
|
|
372
|
+
return `character varying${varying[1] ?? ""}`;
|
|
373
|
+
const fixed = /^(?:char|character)(\(\d+\))$/.exec(typeName);
|
|
374
|
+
if (fixed !== null)
|
|
375
|
+
return `character${fixed[1] ?? ""}`;
|
|
376
|
+
const timed = /^(timestamp with time zone|timestamp without time zone|time with time zone|time without time zone|timestamptz|timestamp|timetz|time)(\(\d+\))?$/.exec(typeName);
|
|
377
|
+
if (timed !== null)
|
|
378
|
+
return canonicalTime(timed[1] ?? "", timed[2] ?? "");
|
|
379
|
+
const numeric = /^(?:decimal|numeric)(\(\d+(?:,\d+)?\))?$/.exec(typeName);
|
|
380
|
+
if (numeric !== null)
|
|
381
|
+
return `numeric${numeric[1] ?? ""}`;
|
|
382
|
+
switch (typeName) {
|
|
383
|
+
case "int":
|
|
384
|
+
case "int4":
|
|
385
|
+
case "integer":
|
|
386
|
+
return "integer";
|
|
387
|
+
case "int8":
|
|
388
|
+
case "bigint":
|
|
389
|
+
return "bigint";
|
|
390
|
+
case "int2":
|
|
391
|
+
case "smallint":
|
|
392
|
+
return "smallint";
|
|
393
|
+
case "bool":
|
|
394
|
+
case "boolean":
|
|
395
|
+
return "boolean";
|
|
396
|
+
case "float8":
|
|
397
|
+
case "double precision":
|
|
398
|
+
return "double precision";
|
|
399
|
+
case "float4":
|
|
400
|
+
case "real":
|
|
401
|
+
return "real";
|
|
402
|
+
default:
|
|
403
|
+
return typeName;
|
|
404
|
+
}
|
|
165
405
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
406
|
+
function canonicalTime(name, precision) {
|
|
407
|
+
if (name === "timestamptz" || name === "timestamp with time zone") {
|
|
408
|
+
return `timestamp${precision} with time zone`;
|
|
409
|
+
}
|
|
410
|
+
if (name === "timestamp" || name === "timestamp without time zone") {
|
|
411
|
+
return `timestamp${precision} without time zone`;
|
|
412
|
+
}
|
|
413
|
+
if (name === "timetz" || name === "time with time zone") {
|
|
414
|
+
return `time${precision} with time zone`;
|
|
415
|
+
}
|
|
416
|
+
return `time${precision} without time zone`;
|
|
168
417
|
}
|
|
169
418
|
|
|
170
419
|
// src/tooling/migrate/values.ts
|
|
@@ -213,23 +462,32 @@ var ACCESS = "ACCESS EXCLUSIVE";
|
|
|
213
462
|
var SHARE = "SHARE";
|
|
214
463
|
var SHARE_UPDATE = "SHARE UPDATE EXCLUSIVE";
|
|
215
464
|
var ROW = "ROW EXCLUSIVE";
|
|
216
|
-
function planMigration(
|
|
465
|
+
function planMigration(input) {
|
|
466
|
+
const request = {
|
|
467
|
+
...input,
|
|
468
|
+
before: omitManagedObjects(input.before),
|
|
469
|
+
after: omitManagedObjects(input.after)
|
|
470
|
+
};
|
|
217
471
|
const schema = request.schema ?? "public";
|
|
218
472
|
const renames = request.renames ?? [];
|
|
219
473
|
const replacements = request.replacements ?? [];
|
|
220
474
|
assertNoChains(replacements);
|
|
221
475
|
const renamed = applyRenames(request.before, renames);
|
|
222
|
-
const beforeList =
|
|
223
|
-
const afterList =
|
|
476
|
+
const beforeList = index2(renamed);
|
|
477
|
+
const afterList = index2(request.after);
|
|
224
478
|
const beforeBy = new Map(beforeList.map((item) => [item.key, item.object]));
|
|
225
479
|
const afterBy = new Map(afterList.map((item) => [item.key, item.object]));
|
|
226
480
|
assertUnambiguous(beforeBy, afterBy);
|
|
227
481
|
const dropKeys = new Set;
|
|
228
482
|
const createKeys = new Set;
|
|
483
|
+
const replaces = [];
|
|
229
484
|
const alters = [];
|
|
230
485
|
for (const item of beforeList) {
|
|
231
|
-
if (!afterBy.has(item.key))
|
|
486
|
+
if (!afterBy.has(item.key)) {
|
|
487
|
+
if (item.object.kind === "role")
|
|
488
|
+
continue;
|
|
232
489
|
dropKeys.add(item.key);
|
|
490
|
+
}
|
|
233
491
|
}
|
|
234
492
|
for (const item of afterList) {
|
|
235
493
|
if (!beforeBy.has(item.key))
|
|
@@ -239,8 +497,24 @@ function planMigration(request) {
|
|
|
239
497
|
const previous = beforeBy.get(item.key);
|
|
240
498
|
if (previous === undefined || sameDefinition(previous, item.object))
|
|
241
499
|
continue;
|
|
242
|
-
if (previous.kind === "
|
|
500
|
+
if (previous.kind === "extension" && item.object.kind === "extension")
|
|
501
|
+
continue;
|
|
502
|
+
if (previous.kind === "role" && item.object.kind === "role")
|
|
503
|
+
continue;
|
|
504
|
+
if (previous.kind === "function" && item.object.kind === "function" && canonicalTypeName(previous.definition.returns) === canonicalTypeName(item.object.definition.returns)) {
|
|
505
|
+
replaces.push({ kind: "function", before: previous, after: item.object });
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
if (previous.kind === "view" && item.object.kind === "view") {
|
|
509
|
+
if (columnsAppended(previous.definition.columns, item.object.definition.columns)) {
|
|
510
|
+
replaces.push({ kind: "view", before: previous, after: item.object });
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (previous.kind === "type" && item.object.kind === "type") {
|
|
515
|
+
refuseDomainBaseChange(previous, item.object);
|
|
243
516
|
continue;
|
|
517
|
+
}
|
|
244
518
|
if (previous.kind === "column" && item.object.kind === "column" && columnAlterable(previous, item.object)) {
|
|
245
519
|
alters.push({ before: previous, after: item.object });
|
|
246
520
|
continue;
|
|
@@ -253,17 +527,51 @@ function planMigration(request) {
|
|
|
253
527
|
const enums = enumEdits(beforeBy, afterBy, schema);
|
|
254
528
|
requireReplacements(picklists, enums, replacements);
|
|
255
529
|
recreateDependents(beforeList, afterList, alters, dropKeys, createKeys, picklists);
|
|
530
|
+
recreateRoutineDependents(afterList, dropKeys, createKeys);
|
|
531
|
+
recreateViewDependents(afterList, dropKeys, createKeys);
|
|
532
|
+
refuseRoutineDrops(beforeBy, afterBy, dropKeys);
|
|
256
533
|
omitOwnedSequences(createKeys, afterBy, request.after);
|
|
257
534
|
omitOwnedSequences(dropKeys, beforeBy, renamed);
|
|
535
|
+
const privileges = privilegeSql(renamed.objects, request.after.objects, schema);
|
|
258
536
|
const steps = [];
|
|
259
537
|
steps.push(...renameSteps(request.before, renames, schema));
|
|
538
|
+
for (const sql of privileges.revoke)
|
|
539
|
+
steps.push(step(sql, "contract", "ddl", ACCESS));
|
|
260
540
|
steps.push(...keptNames.map((item) => renameShapeStep(item, schema)));
|
|
261
541
|
steps.push(...enumAddSteps(enums));
|
|
542
|
+
steps.push(...domainCheckSteps(beforeBy, afterBy, schema));
|
|
262
543
|
steps.push(...expandBackfills(picklists, enums, replacements, schema));
|
|
263
544
|
const droppedTables = new Set([...dropKeys].map((key) => beforeBy.get(key)).filter((object) => object?.kind === "table").map((object) => object.identity.name));
|
|
264
545
|
const deferred = new Set(picklists.map((change) => identityKey2(change.before.identity)));
|
|
265
546
|
const typeDrops = [];
|
|
266
|
-
|
|
547
|
+
const extensionDrops = [];
|
|
548
|
+
const reverse = [...creationOrder2(renamed)].reverse();
|
|
549
|
+
const emitDrop = (object) => {
|
|
550
|
+
const key = identityKey2(object.identity);
|
|
551
|
+
if (!dropKeys.has(key) || deferred.has(key) || covered(object, droppedTables))
|
|
552
|
+
return;
|
|
553
|
+
const sql = dropObjectSql(object, schema);
|
|
554
|
+
if (sql === undefined)
|
|
555
|
+
return;
|
|
556
|
+
steps.push(step(sql, "contract", "ddl", ACCESS));
|
|
557
|
+
};
|
|
558
|
+
for (const object of reverse)
|
|
559
|
+
if (object.kind === "trigger")
|
|
560
|
+
emitDrop(object);
|
|
561
|
+
for (const object of reverse)
|
|
562
|
+
if (object.kind === "function")
|
|
563
|
+
emitDrop(object);
|
|
564
|
+
for (const object of reverse)
|
|
565
|
+
if (ownedByView(object))
|
|
566
|
+
emitDrop(object);
|
|
567
|
+
for (const object of reverse) {
|
|
568
|
+
if (object.kind === "view" || object.kind === "materializedView")
|
|
569
|
+
emitDrop(object);
|
|
570
|
+
}
|
|
571
|
+
for (const object of reverse) {
|
|
572
|
+
if (object.kind === "trigger" || object.kind === "function" || object.kind === "view" || object.kind === "materializedView" || ownedByView(object)) {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
267
575
|
const key = identityKey2(object.identity);
|
|
268
576
|
if (!dropKeys.has(key) || deferred.has(key) || covered(object, droppedTables))
|
|
269
577
|
continue;
|
|
@@ -271,19 +579,27 @@ function planMigration(request) {
|
|
|
271
579
|
typeDrops.push(object);
|
|
272
580
|
continue;
|
|
273
581
|
}
|
|
582
|
+
if (object.kind === "extension") {
|
|
583
|
+
extensionDrops.push(object);
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
emitDrop(object);
|
|
587
|
+
}
|
|
588
|
+
for (const object of typeDrops) {
|
|
274
589
|
const sql = dropObjectSql(object, schema);
|
|
275
590
|
if (sql === undefined)
|
|
276
591
|
continue;
|
|
277
592
|
steps.push(step(sql, "contract", "ddl", ACCESS));
|
|
278
593
|
}
|
|
279
|
-
for (const object of
|
|
594
|
+
for (const object of extensionDrops) {
|
|
280
595
|
const sql = dropObjectSql(object, schema);
|
|
281
596
|
if (sql === undefined)
|
|
282
597
|
continue;
|
|
283
598
|
steps.push(step(sql, "contract", "ddl", ACCESS));
|
|
284
599
|
}
|
|
600
|
+
steps.push(...extensionAlterSteps(beforeBy, afterBy, dropKeys));
|
|
285
601
|
for (const change of alters) {
|
|
286
|
-
for (const sql of alterColumnSql(change.before, change.after, schema)) {
|
|
602
|
+
for (const sql of alterColumnSql(alignedColumnType(change.before, change.after), change.after, schema)) {
|
|
287
603
|
steps.push(step(sql, alterClass(sql), "ddl", ACCESS));
|
|
288
604
|
}
|
|
289
605
|
const identitySql = identityChangeSql(change.before, change.after, schema);
|
|
@@ -292,6 +608,20 @@ function planMigration(request) {
|
|
|
292
608
|
}
|
|
293
609
|
const createdTables = new Set([...createKeys].map((key) => afterBy.get(key)).filter((object) => object?.kind === "table").map((object) => object.identity.name));
|
|
294
610
|
const emitted = new Set;
|
|
611
|
+
for (const sql of privileges.prepare)
|
|
612
|
+
steps.push(step(sql, "expand", "ddl", ACCESS));
|
|
613
|
+
for (const object of creationOrder2(request.after)) {
|
|
614
|
+
if (object.kind !== "extension")
|
|
615
|
+
continue;
|
|
616
|
+
const key = identityKey2(object.identity);
|
|
617
|
+
if (!createKeys.has(key) || emitted.has(key))
|
|
618
|
+
continue;
|
|
619
|
+
emitted.add(key);
|
|
620
|
+
const sql = createObjectSql(object, schema);
|
|
621
|
+
if (sql === undefined)
|
|
622
|
+
continue;
|
|
623
|
+
steps.push(step(sql, "expand", "ddl", ACCESS));
|
|
624
|
+
}
|
|
295
625
|
for (const object of creationOrder2(request.after)) {
|
|
296
626
|
if (object.kind !== "type")
|
|
297
627
|
continue;
|
|
@@ -305,8 +635,9 @@ function planMigration(request) {
|
|
|
305
635
|
steps.push(step(sql, "expand", "ddl", ACCESS));
|
|
306
636
|
}
|
|
307
637
|
for (const object of creationOrder2(request.after)) {
|
|
308
|
-
if (object.kind === "type")
|
|
638
|
+
if (object.kind === "type" || object.kind === "extension" || object.kind === "function" || object.kind === "trigger" || object.kind === "view" || object.kind === "materializedView" || ownedByView(object)) {
|
|
309
639
|
continue;
|
|
640
|
+
}
|
|
310
641
|
const key = identityKey2(object.identity);
|
|
311
642
|
if (deferred.has(key))
|
|
312
643
|
continue;
|
|
@@ -327,6 +658,59 @@ function planMigration(request) {
|
|
|
327
658
|
continue;
|
|
328
659
|
steps.push(step(sql, "expand", "ddl", object.kind === "index" ? SHARE : ACCESS));
|
|
329
660
|
}
|
|
661
|
+
for (const change of replaces) {
|
|
662
|
+
if (sameDefinition(change.before, change.after))
|
|
663
|
+
continue;
|
|
664
|
+
const key = identityKey2(change.after.identity);
|
|
665
|
+
if (dropKeys.has(key) && createKeys.has(key))
|
|
666
|
+
continue;
|
|
667
|
+
const sql = change.kind === "function" ? functionSql(change.after, schema, true) : viewSql(change.after, schema, true);
|
|
668
|
+
steps.push({
|
|
669
|
+
sql,
|
|
670
|
+
class: "expand",
|
|
671
|
+
action: "ddl",
|
|
672
|
+
lock: ACCESS,
|
|
673
|
+
transactional: true,
|
|
674
|
+
behavior: "change"
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
for (const kind of ["function", "trigger", "view", "materializedView"]) {
|
|
678
|
+
for (const object of creationOrder2(request.after)) {
|
|
679
|
+
if (object.kind !== kind)
|
|
680
|
+
continue;
|
|
681
|
+
const key = identityKey2(object.identity);
|
|
682
|
+
if (!createKeys.has(key) || emitted.has(key))
|
|
683
|
+
continue;
|
|
684
|
+
emitted.add(key);
|
|
685
|
+
const sql = createObjectSql(object, schema);
|
|
686
|
+
if (sql === undefined)
|
|
687
|
+
continue;
|
|
688
|
+
steps.push(step(sql, "expand", "ddl", ACCESS));
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
for (const object of creationOrder2(request.after)) {
|
|
692
|
+
if (!ownedByView(object))
|
|
693
|
+
continue;
|
|
694
|
+
const key = identityKey2(object.identity);
|
|
695
|
+
if (!createKeys.has(key) || emitted.has(key))
|
|
696
|
+
continue;
|
|
697
|
+
emitted.add(key);
|
|
698
|
+
const sql = createObjectSql(object, schema);
|
|
699
|
+
if (sql === undefined)
|
|
700
|
+
continue;
|
|
701
|
+
steps.push(step(sql, "expand", "ddl", SHARE));
|
|
702
|
+
}
|
|
703
|
+
for (const object of creationOrder2(request.after)) {
|
|
704
|
+
if (object.kind !== "materializedView")
|
|
705
|
+
continue;
|
|
706
|
+
const key = identityKey2(object.identity);
|
|
707
|
+
if (!createKeys.has(key))
|
|
708
|
+
continue;
|
|
709
|
+
assertConcurrentRefresh(object, request.after);
|
|
710
|
+
steps.push(step(refreshMaterializedViewSql(object, schema, false), "expand", "backfill", ACCESS));
|
|
711
|
+
}
|
|
712
|
+
for (const sql of privileges.grant)
|
|
713
|
+
steps.push(step(sql, "expand", "ddl", ACCESS));
|
|
330
714
|
steps.push(...contractSwaps(picklists, enums, replacements, schema));
|
|
331
715
|
return {
|
|
332
716
|
name: request.name ?? "migration",
|
|
@@ -348,6 +732,10 @@ function formatPlan(plan) {
|
|
|
348
732
|
lines.push(`-- lock: ${item.lock}`);
|
|
349
733
|
if (!item.transactional)
|
|
350
734
|
lines.push("-- transactional: false");
|
|
735
|
+
if (item.path !== undefined)
|
|
736
|
+
lines.push(`-- path: ${item.path}`);
|
|
737
|
+
if (item.behavior === "change")
|
|
738
|
+
lines.push("-- behavior: change");
|
|
351
739
|
lines.push(`${item.sql};`);
|
|
352
740
|
lines.push("");
|
|
353
741
|
}
|
|
@@ -380,6 +768,8 @@ function parsePlan(text) {
|
|
|
380
768
|
let action = "ddl";
|
|
381
769
|
let lock = "";
|
|
382
770
|
let transactional = true;
|
|
771
|
+
let path;
|
|
772
|
+
let behavior;
|
|
383
773
|
const sql = [];
|
|
384
774
|
while (index < lines.length && (lines[index] ?? "") !== "") {
|
|
385
775
|
const line = lines[index] ?? "";
|
|
@@ -392,13 +782,25 @@ function parsePlan(text) {
|
|
|
392
782
|
lock = line.slice("-- lock: ".length);
|
|
393
783
|
else if (line === "-- transactional: false")
|
|
394
784
|
transactional = false;
|
|
785
|
+
else if (line === "-- path: unverified")
|
|
786
|
+
path = "unverified";
|
|
787
|
+
else if (line === "-- behavior: change")
|
|
788
|
+
behavior = "change";
|
|
395
789
|
else if (!line.startsWith("--"))
|
|
396
790
|
sql.push(line);
|
|
397
791
|
}
|
|
398
792
|
const statement = sql.join(`
|
|
399
793
|
`).replace(/;\s*$/, "");
|
|
400
794
|
if (statement.length > 0) {
|
|
401
|
-
steps.push({
|
|
795
|
+
steps.push({
|
|
796
|
+
sql: statement,
|
|
797
|
+
class: stepClass,
|
|
798
|
+
action,
|
|
799
|
+
lock,
|
|
800
|
+
transactional,
|
|
801
|
+
...path !== undefined ? { path } : {},
|
|
802
|
+
...behavior !== undefined ? { behavior } : {}
|
|
803
|
+
});
|
|
402
804
|
}
|
|
403
805
|
}
|
|
404
806
|
return { name, class: steps.length === 0 ? planClass : overall(steps), steps };
|
|
@@ -454,7 +856,7 @@ function renameSteps(source, renames, schema) {
|
|
|
454
856
|
if (rename.kind !== "table" || !hasTable(source, rename.from))
|
|
455
857
|
continue;
|
|
456
858
|
tables.set(rename.to, rename.from);
|
|
457
|
-
steps.push(step(`alter table ${
|
|
859
|
+
steps.push(step(`alter table ${qualify(schema, rename.from)} rename to ${quoteIdent(rename.to)}`, "contract", "ddl", ACCESS));
|
|
458
860
|
}
|
|
459
861
|
for (const rename of renames) {
|
|
460
862
|
if (rename.kind !== "column")
|
|
@@ -462,7 +864,7 @@ function renameSteps(source, renames, schema) {
|
|
|
462
864
|
const previousTable = tables.get(rename.table) ?? rename.table;
|
|
463
865
|
if (!hasColumn(source, previousTable, rename.from))
|
|
464
866
|
continue;
|
|
465
|
-
steps.push(step(`alter table ${
|
|
867
|
+
steps.push(step(`alter table ${qualify(schema, rename.table)} rename column ${quoteIdent(rename.from)} to ${quoteIdent(rename.to)}`, "contract", "ddl", ACCESS));
|
|
466
868
|
}
|
|
467
869
|
return steps;
|
|
468
870
|
}
|
|
@@ -489,7 +891,7 @@ function pairSameShape(beforeBy, afterBy, dropKeys, createKeys) {
|
|
|
489
891
|
used.add(rightKey);
|
|
490
892
|
dropKeys.delete(identityKey2(left.identity));
|
|
491
893
|
createKeys.delete(rightKey);
|
|
492
|
-
if (left
|
|
894
|
+
if (anchoredName(left) !== anchoredName(right))
|
|
493
895
|
pairs.push({ from: left, to: right });
|
|
494
896
|
}
|
|
495
897
|
return pairs;
|
|
@@ -511,12 +913,12 @@ function withoutNameKey(value) {
|
|
|
511
913
|
}
|
|
512
914
|
function renameShapeStep(pair, schema) {
|
|
513
915
|
if (pair.from.kind === "index" && pair.to.kind === "index") {
|
|
514
|
-
return step(`alter index ${
|
|
916
|
+
return step(`alter index ${qualify(schema, pair.from.identity.name)} rename to ${quoteIdent(pair.to.identity.name)}`, "contract", "ddl", ACCESS);
|
|
515
917
|
}
|
|
516
918
|
if (pair.from.kind !== "constraint" || pair.to.kind !== "constraint") {
|
|
517
919
|
return step("select 1", "unclassified", "ddl", ACCESS);
|
|
518
920
|
}
|
|
519
|
-
return step(`alter table ${
|
|
921
|
+
return step(`alter table ${qualify(schema, pair.from.identity.parent.name)} rename constraint ${quoteIdent(pair.from.identity.name)} to ${quoteIdent(pair.to.identity.name)}`, "contract", "ddl", ACCESS);
|
|
520
922
|
}
|
|
521
923
|
function assertUnambiguous(beforeBy, afterBy) {
|
|
522
924
|
const dropped = new Map;
|
|
@@ -528,7 +930,10 @@ function assertUnambiguous(beforeBy, afterBy) {
|
|
|
528
930
|
if (group === undefined)
|
|
529
931
|
continue;
|
|
530
932
|
const list = dropped.get(group) ?? [];
|
|
531
|
-
|
|
933
|
+
const name = anchoredName(object);
|
|
934
|
+
if (name === undefined)
|
|
935
|
+
continue;
|
|
936
|
+
list.push(name);
|
|
532
937
|
dropped.set(group, list);
|
|
533
938
|
}
|
|
534
939
|
for (const [key, object] of afterBy) {
|
|
@@ -538,7 +943,10 @@ function assertUnambiguous(beforeBy, afterBy) {
|
|
|
538
943
|
if (group === undefined)
|
|
539
944
|
continue;
|
|
540
945
|
const list = added.get(group) ?? [];
|
|
541
|
-
|
|
946
|
+
const name = anchoredName(object);
|
|
947
|
+
if (name === undefined)
|
|
948
|
+
continue;
|
|
949
|
+
list.push(name);
|
|
542
950
|
added.set(group, list);
|
|
543
951
|
}
|
|
544
952
|
for (const [group, names] of added) {
|
|
@@ -553,6 +961,15 @@ function assertUnambiguous(beforeBy, afterBy) {
|
|
|
553
961
|
});
|
|
554
962
|
}
|
|
555
963
|
}
|
|
964
|
+
function anchoredName(object) {
|
|
965
|
+
switch (object.kind) {
|
|
966
|
+
case "grant":
|
|
967
|
+
case "defaultPrivilege":
|
|
968
|
+
return;
|
|
969
|
+
default:
|
|
970
|
+
return object.identity.name;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
556
973
|
function groupOf(object) {
|
|
557
974
|
if (object.kind === "table")
|
|
558
975
|
return "table:public";
|
|
@@ -561,7 +978,7 @@ function groupOf(object) {
|
|
|
561
978
|
return;
|
|
562
979
|
}
|
|
563
980
|
function recreateDependents(beforeList, afterList, alters, dropKeys, createKeys, picklists) {
|
|
564
|
-
const changed = new Set(alters.filter((item) => item.before.definition.dataType !== item.after.definition.dataType).map((item) => identityKey2(item.after.identity)));
|
|
981
|
+
const changed = new Set(alters.filter((item) => canonicalTypeName(item.before.definition.dataType) !== canonicalTypeName(item.after.definition.dataType)).map((item) => identityKey2(item.after.identity)));
|
|
565
982
|
if (changed.size === 0)
|
|
566
983
|
return;
|
|
567
984
|
const deferred = new Set(picklists.map((change) => identityKey2(change.before.identity)));
|
|
@@ -576,11 +993,99 @@ function recreateDependents(beforeList, afterList, alters, dropKeys, createKeys,
|
|
|
576
993
|
createKeys.add(key);
|
|
577
994
|
}
|
|
578
995
|
}
|
|
996
|
+
function recreateRoutineDependents(afterList, dropKeys, createKeys) {
|
|
997
|
+
const recreated = new Set;
|
|
998
|
+
for (const item of afterList) {
|
|
999
|
+
if (!dropKeys.has(item.key) || !createKeys.has(item.key))
|
|
1000
|
+
continue;
|
|
1001
|
+
if (item.object.kind === "function" || item.object.kind === "column" || item.object.kind === "table") {
|
|
1002
|
+
recreated.add(item.key);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (recreated.size === 0)
|
|
1006
|
+
return;
|
|
1007
|
+
for (const item of afterList) {
|
|
1008
|
+
if (item.object.kind !== "trigger" && item.object.kind !== "function")
|
|
1009
|
+
continue;
|
|
1010
|
+
const hit = item.object.dependencies.some((edge) => recreated.has(identityKey2(edge.target)));
|
|
1011
|
+
if (!hit)
|
|
1012
|
+
continue;
|
|
1013
|
+
dropKeys.add(item.key);
|
|
1014
|
+
createKeys.add(item.key);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function refuseRoutineDrops(beforeBy, afterBy, dropKeys) {
|
|
1018
|
+
for (const after of afterBy.values()) {
|
|
1019
|
+
const key = identityKey2(after.identity);
|
|
1020
|
+
if (dropKeys.has(key))
|
|
1021
|
+
continue;
|
|
1022
|
+
if (after.kind !== "trigger" && after.kind !== "function" && after.kind !== "view" && after.kind !== "materializedView") {
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
for (const edge of after.dependencies) {
|
|
1026
|
+
const targetKey = identityKey2(edge.target);
|
|
1027
|
+
if (!dropKeys.has(targetKey))
|
|
1028
|
+
continue;
|
|
1029
|
+
const target = beforeBy.get(targetKey);
|
|
1030
|
+
if (target === undefined)
|
|
1031
|
+
continue;
|
|
1032
|
+
if (target.kind !== "function" && target.kind !== "table" && target.kind !== "column" && target.kind !== "view" && target.kind !== "materializedView") {
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
throw new OkmError2("OKM1821", `${identityLabel2(after.identity)} depends on ${identityLabel2(target.identity)}, which this plan drops. CASCADE is never used.`, { fix: { summary: "Review the dependents the plan lists and recreate them explicitly." } });
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
579
1039
|
function dependsOnChanged(object, columns) {
|
|
580
|
-
if (object.kind !== "index" && object.kind !== "constraint")
|
|
1040
|
+
if (object.kind !== "index" && object.kind !== "constraint" && object.kind !== "view" && object.kind !== "materializedView") {
|
|
581
1041
|
return false;
|
|
1042
|
+
}
|
|
582
1043
|
return object.dependencies.some((edge) => columns.has(identityKey2(edge.target)));
|
|
583
1044
|
}
|
|
1045
|
+
function recreateViewDependents(afterList, dropKeys, createKeys) {
|
|
1046
|
+
let changed = true;
|
|
1047
|
+
while (changed) {
|
|
1048
|
+
changed = false;
|
|
1049
|
+
for (const item of afterList) {
|
|
1050
|
+
if (dropKeys.has(item.key) && createKeys.has(item.key))
|
|
1051
|
+
continue;
|
|
1052
|
+
const kind = item.object.kind;
|
|
1053
|
+
if (kind !== "view" && kind !== "materializedView" && kind !== "index")
|
|
1054
|
+
continue;
|
|
1055
|
+
const hit = item.object.dependencies.some((edge) => {
|
|
1056
|
+
const key = identityKey2(edge.target);
|
|
1057
|
+
return dropKeys.has(key) && createKeys.has(key);
|
|
1058
|
+
});
|
|
1059
|
+
if (!hit)
|
|
1060
|
+
continue;
|
|
1061
|
+
dropKeys.add(item.key);
|
|
1062
|
+
createKeys.add(item.key);
|
|
1063
|
+
changed = true;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
function columnsAppended(before, after) {
|
|
1068
|
+
if (after.length < before.length)
|
|
1069
|
+
return false;
|
|
1070
|
+
for (let index = 0;index < before.length; index += 1) {
|
|
1071
|
+
const left = before[index];
|
|
1072
|
+
const right = after[index];
|
|
1073
|
+
if (left === undefined || right === undefined)
|
|
1074
|
+
return false;
|
|
1075
|
+
if (left.name !== right.name || canonicalTypeName(left.dataType) !== canonicalTypeName(right.dataType)) {
|
|
1076
|
+
return false;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return true;
|
|
1080
|
+
}
|
|
1081
|
+
function assertConcurrentRefresh(object, source) {
|
|
1082
|
+
if (object.definition.refresh !== "concurrently")
|
|
1083
|
+
return;
|
|
1084
|
+
const unique = source.objects.some((item) => item.kind === "index" && item.definition.unique && item.identity.parent.name === object.identity.name);
|
|
1085
|
+
if (unique)
|
|
1086
|
+
return;
|
|
1087
|
+
throw new OkmError2("OKM1822", `Materialized view ${object.identity.name} refreshes concurrently and has no unique index.`, { fix: { summary: "Add a unique index, or refresh without CONCURRENTLY." } });
|
|
1088
|
+
}
|
|
584
1089
|
function enumAddSteps(enums) {
|
|
585
1090
|
const steps = [];
|
|
586
1091
|
for (const change of enums) {
|
|
@@ -599,7 +1104,7 @@ function contractSwaps(picklists, enums, replacements, schema) {
|
|
|
599
1104
|
const steps = [];
|
|
600
1105
|
steps.push(...dataSteps(picklists, enums, replacements, schema, "contract"));
|
|
601
1106
|
for (const change of picklists) {
|
|
602
|
-
const table =
|
|
1107
|
+
const table = qualify(schema, change.after.identity.parent.name);
|
|
603
1108
|
const name = quoteIdent(change.after.identity.name);
|
|
604
1109
|
steps.push(step(`alter table ${table} drop constraint ${name}`, change.removed.length > 0 ? "contract" : "expand", "ddl", ACCESS));
|
|
605
1110
|
const expression = change.after.definition.expression ?? "true";
|
|
@@ -609,17 +1114,17 @@ function contractSwaps(picklists, enums, replacements, schema) {
|
|
|
609
1114
|
for (const change of enums) {
|
|
610
1115
|
if (change.kind !== "replace")
|
|
611
1116
|
continue;
|
|
612
|
-
const type =
|
|
1117
|
+
const type = qualify(schema, change.name);
|
|
613
1118
|
const old = quoteIdent(`${change.name}_old`);
|
|
614
|
-
const labels = change.labels.map((label) =>
|
|
1119
|
+
const labels = change.labels.map((label) => quoteLiteral(label)).join(", ");
|
|
615
1120
|
steps.push(step(`alter type ${type} rename to ${old}`, "contract", "ddl", ACCESS));
|
|
616
1121
|
steps.push(step(`create type ${type} as enum (${labels})`, "contract", "ddl", ACCESS));
|
|
617
1122
|
for (const column of change.columns) {
|
|
618
|
-
const table =
|
|
1123
|
+
const table = qualify(schema, column.table);
|
|
619
1124
|
const name = quoteIdent(column.column);
|
|
620
1125
|
steps.push(step(`alter table ${table} alter column ${name} type ${type} using ${name}::text::${type}`, "contract", "ddl", ACCESS));
|
|
621
1126
|
}
|
|
622
|
-
steps.push(step(`drop type ${
|
|
1127
|
+
steps.push(step(`drop type ${qualify(schema, `${change.name}_old`)}`, "contract", "ddl", ACCESS));
|
|
623
1128
|
}
|
|
624
1129
|
return steps;
|
|
625
1130
|
}
|
|
@@ -737,30 +1242,69 @@ function picklistChanges(beforeBy, afterBy, after) {
|
|
|
737
1242
|
}
|
|
738
1243
|
return changes;
|
|
739
1244
|
}
|
|
1245
|
+
function refuseDomainBaseChange(before, after) {
|
|
1246
|
+
if (before.kind !== "type" || after.kind !== "type")
|
|
1247
|
+
return;
|
|
1248
|
+
if (!isDomain(before.definition) || !isDomain(after.definition))
|
|
1249
|
+
return;
|
|
1250
|
+
if (before.definition.base === after.definition.base)
|
|
1251
|
+
return;
|
|
1252
|
+
throw new OkmError2("OKM1020", `Domain ${after.identity.name} cannot change its base type from ${before.definition.base} to ${after.definition.base}. The base type of a domain stays as it was created.`, {
|
|
1253
|
+
fix: {
|
|
1254
|
+
summary: "Keep the base type. Add a new domain when the column needs another type."
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
function domainCheckSteps(beforeBy, afterBy, schema) {
|
|
1259
|
+
const steps = [];
|
|
1260
|
+
for (const [key, next] of afterBy) {
|
|
1261
|
+
if (next.kind !== "type" || !isDomain(next.definition))
|
|
1262
|
+
continue;
|
|
1263
|
+
const previous = beforeBy.get(key);
|
|
1264
|
+
if (previous === undefined || previous.kind !== "type" || !isDomain(previous.definition)) {
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
if (previous.definition.check === next.definition.check)
|
|
1268
|
+
continue;
|
|
1269
|
+
const name = next.identity.name;
|
|
1270
|
+
const type = qualify(schema, name);
|
|
1271
|
+
const current = quoteIdent(fitIdentifier2(`${name}_check`));
|
|
1272
|
+
const upcoming = quoteIdent(fitIdentifier2(`${name}_check_next`));
|
|
1273
|
+
const expression = next.definition.check;
|
|
1274
|
+
steps.push(step(`alter domain ${type} add constraint ${upcoming} check (${expression}) not valid`, "expand", "ddl", ACCESS));
|
|
1275
|
+
steps.push(step(`alter domain ${type} validate constraint ${upcoming}`, "expand", "ddl", ACCESS));
|
|
1276
|
+
steps.push(step(`alter domain ${type} drop constraint ${current}`, "contract", "ddl", ACCESS));
|
|
1277
|
+
steps.push(step(`alter domain ${type} rename constraint ${upcoming} to ${current}`, "expand", "ddl", ACCESS));
|
|
1278
|
+
}
|
|
1279
|
+
return steps;
|
|
1280
|
+
}
|
|
740
1281
|
function enumEdits(beforeBy, afterBy, schema) {
|
|
741
1282
|
const edits = [];
|
|
742
1283
|
for (const [key, next] of afterBy) {
|
|
743
|
-
if (next.kind !== "type")
|
|
1284
|
+
if (next.kind !== "type" || isDomain(next.definition))
|
|
744
1285
|
continue;
|
|
745
1286
|
const previous = beforeBy.get(key);
|
|
746
|
-
if (previous === undefined || previous.kind !== "type")
|
|
1287
|
+
if (previous === undefined || previous.kind !== "type" || isDomain(previous.definition)) {
|
|
747
1288
|
continue;
|
|
748
|
-
|
|
1289
|
+
}
|
|
1290
|
+
const beforeLabels = previous.definition.labels;
|
|
1291
|
+
const afterLabels = next.definition.labels;
|
|
1292
|
+
if (sameEnumLabels(beforeLabels, afterLabels))
|
|
749
1293
|
continue;
|
|
750
|
-
const removed =
|
|
1294
|
+
const removed = beforeLabels.filter((label) => !afterLabels.includes(label));
|
|
751
1295
|
const columns = enumColumns(previous.identity.name, beforeBy, afterBy);
|
|
752
|
-
if (removed.length === 0 && labelSubsequence(
|
|
1296
|
+
if (removed.length === 0 && labelSubsequence(beforeLabels, afterLabels)) {
|
|
753
1297
|
edits.push({
|
|
754
1298
|
kind: "add",
|
|
755
1299
|
name: next.identity.name,
|
|
756
|
-
statements: addValueStatements(schema, next.identity.name,
|
|
1300
|
+
statements: addValueStatements(schema, next.identity.name, beforeLabels, afterLabels)
|
|
757
1301
|
});
|
|
758
1302
|
continue;
|
|
759
1303
|
}
|
|
760
1304
|
edits.push({
|
|
761
1305
|
kind: "replace",
|
|
762
1306
|
name: next.identity.name,
|
|
763
|
-
labels:
|
|
1307
|
+
labels: afterLabels,
|
|
764
1308
|
removed,
|
|
765
1309
|
columns
|
|
766
1310
|
});
|
|
@@ -801,7 +1345,7 @@ function labelSubsequence(before, after) {
|
|
|
801
1345
|
function addValueStatements(schema, name, before, after) {
|
|
802
1346
|
const present = new Set(before);
|
|
803
1347
|
const statements = [];
|
|
804
|
-
const type =
|
|
1348
|
+
const type = qualify(schema, name);
|
|
805
1349
|
for (let index = 0;index < after.length; index += 1) {
|
|
806
1350
|
const label = after[index];
|
|
807
1351
|
if (label === undefined || present.has(label))
|
|
@@ -814,9 +1358,9 @@ function addValueStatements(schema, name, before, after) {
|
|
|
814
1358
|
break;
|
|
815
1359
|
}
|
|
816
1360
|
}
|
|
817
|
-
let sql = `alter type ${type} add value ${
|
|
1361
|
+
let sql = `alter type ${type} add value ${quoteLiteral(label)}`;
|
|
818
1362
|
if (beforeNeighbor !== undefined) {
|
|
819
|
-
sql += ` before ${
|
|
1363
|
+
sql += ` before ${quoteLiteral(beforeNeighbor)}`;
|
|
820
1364
|
} else {
|
|
821
1365
|
let afterNeighbor;
|
|
822
1366
|
for (let cursor = index - 1;cursor >= 0; cursor -= 1) {
|
|
@@ -827,7 +1371,7 @@ function addValueStatements(schema, name, before, after) {
|
|
|
827
1371
|
}
|
|
828
1372
|
}
|
|
829
1373
|
if (afterNeighbor !== undefined)
|
|
830
|
-
sql += ` after ${
|
|
1374
|
+
sql += ` after ${quoteLiteral(afterNeighbor)}`;
|
|
831
1375
|
}
|
|
832
1376
|
statements.push(sql);
|
|
833
1377
|
present.add(label);
|
|
@@ -885,7 +1429,7 @@ function columnAlterable(before, after) {
|
|
|
885
1429
|
function identityChangeSql(before, after, schema) {
|
|
886
1430
|
if (stable(before.definition.identity) === stable(after.definition.identity))
|
|
887
1431
|
return;
|
|
888
|
-
const table =
|
|
1432
|
+
const table = qualify(schema, after.identity.parent.name);
|
|
889
1433
|
const name = quoteIdent(after.identity.name);
|
|
890
1434
|
const next = after.definition.identity;
|
|
891
1435
|
const previous = before.definition.identity;
|
|
@@ -905,7 +1449,53 @@ function omitOwnedSequences(keys, by, source) {
|
|
|
905
1449
|
}
|
|
906
1450
|
}
|
|
907
1451
|
function sameDefinition(left, right) {
|
|
908
|
-
|
|
1452
|
+
if (left.kind === "materializedView" && right.kind === "materializedView") {
|
|
1453
|
+
return stable(canonicalColumns(left.definition.columns)) === stable(canonicalColumns(right.definition.columns)) && left.definition.query === right.definition.query;
|
|
1454
|
+
}
|
|
1455
|
+
return stable(forComparison(left)) === stable(forComparison(right));
|
|
1456
|
+
}
|
|
1457
|
+
function forComparison(object) {
|
|
1458
|
+
switch (object.kind) {
|
|
1459
|
+
case "column":
|
|
1460
|
+
case "sequence":
|
|
1461
|
+
return { ...object.definition, dataType: canonicalTypeName(object.definition.dataType) };
|
|
1462
|
+
case "view":
|
|
1463
|
+
return { ...object.definition, columns: canonicalColumns(object.definition.columns) };
|
|
1464
|
+
case "function":
|
|
1465
|
+
return {
|
|
1466
|
+
...object.definition,
|
|
1467
|
+
returns: canonicalTypeName(object.definition.returns),
|
|
1468
|
+
arguments: object.definition.arguments.map((argument) => ({
|
|
1469
|
+
...argument,
|
|
1470
|
+
type: canonicalTypeName(argument.type)
|
|
1471
|
+
}))
|
|
1472
|
+
};
|
|
1473
|
+
case "trigger":
|
|
1474
|
+
return {
|
|
1475
|
+
...object.definition,
|
|
1476
|
+
calls: {
|
|
1477
|
+
...object.definition.calls,
|
|
1478
|
+
argTypes: object.definition.calls.argTypes.map((typeName) => canonicalTypeName(typeName))
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
1481
|
+
case "type":
|
|
1482
|
+
if (!isDomain(object.definition))
|
|
1483
|
+
return object.definition;
|
|
1484
|
+
return { ...object.definition, base: canonicalTypeName(object.definition.base) };
|
|
1485
|
+
default:
|
|
1486
|
+
return object.definition;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
function canonicalColumns(columns) {
|
|
1490
|
+
return columns.map((column) => ({ ...column, dataType: canonicalTypeName(column.dataType) }));
|
|
1491
|
+
}
|
|
1492
|
+
function alignedColumnType(before, after) {
|
|
1493
|
+
if (canonicalTypeName(before.definition.dataType) !== canonicalTypeName(after.definition.dataType)) {
|
|
1494
|
+
return before;
|
|
1495
|
+
}
|
|
1496
|
+
if (before.definition.dataType === after.definition.dataType)
|
|
1497
|
+
return before;
|
|
1498
|
+
return { ...before, definition: { ...before.definition, dataType: after.definition.dataType } };
|
|
909
1499
|
}
|
|
910
1500
|
function covered(object, tables) {
|
|
911
1501
|
if (object.kind === "column" || object.kind === "index" || object.kind === "constraint") {
|
|
@@ -951,9 +1541,9 @@ function overall(steps) {
|
|
|
951
1541
|
return "expand";
|
|
952
1542
|
}
|
|
953
1543
|
function updateSql(schema, table, column, from, to, cast = false) {
|
|
954
|
-
const value = to === null ? "null" :
|
|
1544
|
+
const value = to === null ? "null" : quoteLiteral(to);
|
|
955
1545
|
const compare = cast ? `${quoteIdent(column)}::text` : quoteIdent(column);
|
|
956
|
-
return `update ${
|
|
1546
|
+
return `update ${qualify(schema, table)} set ${quoteIdent(column)} = ${value} where ${compare} = ${quoteLiteral(from)}`;
|
|
957
1547
|
}
|
|
958
1548
|
function hasTable(source, name) {
|
|
959
1549
|
return source.objects.some((object) => object.kind === "table" && object.identity.name === name);
|
|
@@ -961,18 +1551,12 @@ function hasTable(source, name) {
|
|
|
961
1551
|
function hasColumn(source, table, name) {
|
|
962
1552
|
return source.objects.some((object) => object.kind === "column" && object.identity.parent.name === table && object.identity.name === name);
|
|
963
1553
|
}
|
|
964
|
-
function
|
|
1554
|
+
function index2(source) {
|
|
965
1555
|
return source.objects.map((object) => ({ key: identityKey2(object.identity), object }));
|
|
966
1556
|
}
|
|
967
1557
|
function step(sql, classification, action, lock, transactional = true) {
|
|
968
1558
|
return { sql, class: classification, action, lock, transactional };
|
|
969
1559
|
}
|
|
970
|
-
function qualify2(schema, name) {
|
|
971
|
-
return `${quoteIdent(schema)}.${quoteIdent(name)}`;
|
|
972
|
-
}
|
|
973
|
-
function sqlString(value) {
|
|
974
|
-
return `'${value.replaceAll("'", "''")}'`;
|
|
975
|
-
}
|
|
976
1560
|
function stable(value) {
|
|
977
1561
|
return JSON.stringify(order(value));
|
|
978
1562
|
}
|
|
@@ -989,536 +1573,4 @@ function order(value) {
|
|
|
989
1573
|
return value;
|
|
990
1574
|
}
|
|
991
1575
|
|
|
992
|
-
|
|
993
|
-
var READ = new Set([
|
|
994
|
-
"plan",
|
|
995
|
-
"status",
|
|
996
|
-
"check",
|
|
997
|
-
"drift",
|
|
998
|
-
"verify",
|
|
999
|
-
"pull",
|
|
1000
|
-
"catalog-export",
|
|
1001
|
-
"inspect"
|
|
1002
|
-
]);
|
|
1003
|
-
var ALLOWED = new Set(["expand", "reference", "provision"]);
|
|
1004
|
-
var FORWARD_ONLY = new Set(["drop", "rollback"]);
|
|
1005
|
-
function assertTargetPolicy(target, operation, allowProtected = false) {
|
|
1006
|
-
if (FORWARD_ONLY.has(operation)) {
|
|
1007
|
-
throw new OkmError2("invalid", "There is no down migration.", {
|
|
1008
|
-
fix: { summary: "Recovery is a new forward migration." }
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
if (!target.protected || allowProtected)
|
|
1012
|
-
return;
|
|
1013
|
-
if (READ.has(operation) || ALLOWED.has(operation))
|
|
1014
|
-
return;
|
|
1015
|
-
throw new OkmError2("OKM1850", `${operation} is blocked on protected target ${target.name}.`, {
|
|
1016
|
-
kind: "forbidden",
|
|
1017
|
-
fix: {
|
|
1018
|
-
summary: "Run a read-only command or an expand migration, or pass --allow-protected for this invocation."
|
|
1019
|
-
}
|
|
1020
|
-
});
|
|
1021
|
-
}
|
|
1022
|
-
function assertTargetAlias(targets) {
|
|
1023
|
-
const groups = new Map;
|
|
1024
|
-
for (const target of targets) {
|
|
1025
|
-
const key = `${target.host}
|
|
1026
|
-
${target.port}
|
|
1027
|
-
${target.database}`;
|
|
1028
|
-
const group = groups.get(key);
|
|
1029
|
-
if (group === undefined)
|
|
1030
|
-
groups.set(key, [target]);
|
|
1031
|
-
else
|
|
1032
|
-
group.push(target);
|
|
1033
|
-
}
|
|
1034
|
-
for (const group of groups.values()) {
|
|
1035
|
-
const protectedTarget = group.find((target) => target.protected);
|
|
1036
|
-
const open = group.find((target) => !target.protected);
|
|
1037
|
-
if (protectedTarget !== undefined && open !== undefined) {
|
|
1038
|
-
throw new OkmError2("OKM1852", `${open.name} and ${protectedTarget.name} resolve to the same database and disagree about protection.`, {
|
|
1039
|
-
fix: {
|
|
1040
|
-
summary: "Give those targets the same protection flag. okm check and okm doctor both report this."
|
|
1041
|
-
}
|
|
1042
|
-
});
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
function assertDirectConnection(url, allowPooler) {
|
|
1047
|
-
if (allowPooler)
|
|
1048
|
-
return;
|
|
1049
|
-
const endpoint = endpointOf(url);
|
|
1050
|
-
const parsed = new URL(url);
|
|
1051
|
-
const host = endpoint.host;
|
|
1052
|
-
if (host.includes("pooler") || host.includes("pgbouncer") || host.includes("-pooler") || endpoint.port === "6543" || parsed.searchParams.get("pgbouncer") === "true") {
|
|
1053
|
-
throw new OkmError2("invalid", `Refusing pooler host ${host}. Apply needs a direct connection.`, { fix: { summary: "Use a direct connection, or pass --allow-pooler." } });
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
function listTargets(config) {
|
|
1057
|
-
if (config.database !== undefined && config.targets !== undefined) {
|
|
1058
|
-
throw new OkmError2("invalid", "Set database or targets, not both.", {
|
|
1059
|
-
fix: { summary: "database is the one target named default. Use targets for more than one." }
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
const entries = [];
|
|
1063
|
-
if (config.database !== undefined)
|
|
1064
|
-
entries.push(["default", config.database]);
|
|
1065
|
-
if (config.targets !== undefined) {
|
|
1066
|
-
for (const name of Object.keys(config.targets).sort()) {
|
|
1067
|
-
const input = config.targets[name];
|
|
1068
|
-
if (input !== undefined)
|
|
1069
|
-
entries.push([name, input]);
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
return entries.map(([name, input]) => resolveTarget(name, input));
|
|
1073
|
-
}
|
|
1074
|
-
function selectTarget(config, name) {
|
|
1075
|
-
const targets = listTargets(config);
|
|
1076
|
-
if (targets.length === 0) {
|
|
1077
|
-
throw new OkmError2("OKM1845", "No target is configured.", {
|
|
1078
|
-
fix: { summary: "Set database or targets in okmodel.config.ts." }
|
|
1079
|
-
});
|
|
1080
|
-
}
|
|
1081
|
-
if (name === undefined) {
|
|
1082
|
-
if (targets.length === 1) {
|
|
1083
|
-
const only = targets[0];
|
|
1084
|
-
if (only !== undefined)
|
|
1085
|
-
return only;
|
|
1086
|
-
}
|
|
1087
|
-
throw new OkmError2("OKM1853", "Several targets are configured and --target was not passed.", {
|
|
1088
|
-
fix: { summary: "Pass --target <name>. The command does not guess." }
|
|
1089
|
-
});
|
|
1090
|
-
}
|
|
1091
|
-
const found = targets.find((target) => target.name === name);
|
|
1092
|
-
if (found === undefined) {
|
|
1093
|
-
throw new OkmError2("OKM1845", `Target ${name} is not configured.`, {
|
|
1094
|
-
fix: { summary: "Pass a name from targets, or database for the target named default." }
|
|
1095
|
-
});
|
|
1096
|
-
}
|
|
1097
|
-
return found;
|
|
1098
|
-
}
|
|
1099
|
-
function resolveTarget(name, input) {
|
|
1100
|
-
const url = typeof input === "string" ? input : input.url;
|
|
1101
|
-
const protectedTarget = typeof input === "string" ? false : input.protected === true;
|
|
1102
|
-
if (url === undefined || url.length === 0) {
|
|
1103
|
-
throw new OkmError2("OKM1845", `Target ${name} has no url.`, {
|
|
1104
|
-
fix: { summary: "Set the target url. A target is not inferred from the environment." }
|
|
1105
|
-
});
|
|
1106
|
-
}
|
|
1107
|
-
const endpoint = endpointOf(url);
|
|
1108
|
-
return {
|
|
1109
|
-
name,
|
|
1110
|
-
url,
|
|
1111
|
-
protected: protectedTarget,
|
|
1112
|
-
host: endpoint.host,
|
|
1113
|
-
port: endpoint.port,
|
|
1114
|
-
database: endpoint.database
|
|
1115
|
-
};
|
|
1116
|
-
}
|
|
1117
|
-
function endpointOf(url) {
|
|
1118
|
-
let parsed;
|
|
1119
|
-
try {
|
|
1120
|
-
parsed = new URL(url);
|
|
1121
|
-
} catch {
|
|
1122
|
-
throw new OkmError2("OKM1845", "The target URL is not a Postgres URL.", {
|
|
1123
|
-
fix: { summary: "Pass a postgres:// URL for the target." }
|
|
1124
|
-
});
|
|
1125
|
-
}
|
|
1126
|
-
const database = decodeURIComponent(parsed.pathname.replace(/^\//, ""));
|
|
1127
|
-
return {
|
|
1128
|
-
host: parsed.hostname.toLowerCase(),
|
|
1129
|
-
port: parsed.port.length > 0 ? parsed.port : "5432",
|
|
1130
|
-
database
|
|
1131
|
-
};
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
// src/tooling/migrate/project.ts
|
|
1135
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1136
|
-
import { basename, join } from "node:path";
|
|
1137
|
-
import { pathToFileURL } from "node:url";
|
|
1138
|
-
|
|
1139
|
-
// src/dialects/pg/declarations.ts
|
|
1140
|
-
function schemaDeclarations(source) {
|
|
1141
|
-
const renames = [];
|
|
1142
|
-
for (const item of source.tables) {
|
|
1143
|
-
const tableName = sqlTableName(item, source.casing);
|
|
1144
|
-
const renamed = tableRenamedFrom(item);
|
|
1145
|
-
if (renamed !== undefined) {
|
|
1146
|
-
renames.push({ kind: "table", from: renamed, to: tableName });
|
|
1147
|
-
}
|
|
1148
|
-
for (const [field, value] of Object.entries(item.columns)) {
|
|
1149
|
-
if (!(value instanceof ColumnBuilder2))
|
|
1150
|
-
continue;
|
|
1151
|
-
const state = value.state;
|
|
1152
|
-
const columnName = state.sqlName ?? sqlIdentifier(field, source.casing);
|
|
1153
|
-
if (state.renamedFrom !== undefined) {
|
|
1154
|
-
renames.push({
|
|
1155
|
-
kind: "column",
|
|
1156
|
-
table: tableName,
|
|
1157
|
-
from: state.renamedFrom,
|
|
1158
|
-
to: columnName
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
return { renames };
|
|
1164
|
-
}
|
|
1165
|
-
function sqlTableName(item, casing) {
|
|
1166
|
-
const options = item.options;
|
|
1167
|
-
return options?.sqlName ?? sqlIdentifier(item.name, casing);
|
|
1168
|
-
}
|
|
1169
|
-
function tableRenamedFrom(item) {
|
|
1170
|
-
const options = item.options;
|
|
1171
|
-
return options?.renamedFrom;
|
|
1172
|
-
}
|
|
1173
|
-
function sqlIdentifier(name, casing) {
|
|
1174
|
-
return casing === "snake" ? snakeCase(name) : name;
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
// src/dialects/pg/emit.ts
|
|
1178
|
-
function emitRowTypes(source) {
|
|
1179
|
-
const lines = [
|
|
1180
|
-
"/**",
|
|
1181
|
-
" * Row types emitted from the schema.",
|
|
1182
|
-
` * Mode: ${source.types}.`,
|
|
1183
|
-
" */",
|
|
1184
|
-
""
|
|
1185
|
-
];
|
|
1186
|
-
const rows = [];
|
|
1187
|
-
const inserts = [];
|
|
1188
|
-
const updates = [];
|
|
1189
|
-
for (const item of source.tables) {
|
|
1190
|
-
const name = emittedTypeName(item.name);
|
|
1191
|
-
const key = propertyName(item.name);
|
|
1192
|
-
rows.push(` readonly ${key}: ${name};`);
|
|
1193
|
-
inserts.push(` readonly ${key}: ${name}Insert;`);
|
|
1194
|
-
updates.push(` readonly ${key}: ${name}Update;`);
|
|
1195
|
-
lines.push(`export interface ${name} {`);
|
|
1196
|
-
lines.push(fields(item, "row"));
|
|
1197
|
-
lines.push("}", "");
|
|
1198
|
-
lines.push(`export interface ${name}Insert {`);
|
|
1199
|
-
lines.push(fields(item, "insert"));
|
|
1200
|
-
lines.push("}", "");
|
|
1201
|
-
lines.push(`export interface ${name}Update {`);
|
|
1202
|
-
lines.push(fields(item, "update"));
|
|
1203
|
-
lines.push("}", "");
|
|
1204
|
-
}
|
|
1205
|
-
lines.push("export interface Rows {");
|
|
1206
|
-
lines.push(rows.join(`
|
|
1207
|
-
`));
|
|
1208
|
-
lines.push("}", "");
|
|
1209
|
-
lines.push("export interface Inserts {");
|
|
1210
|
-
lines.push(inserts.join(`
|
|
1211
|
-
`));
|
|
1212
|
-
lines.push("}", "");
|
|
1213
|
-
lines.push("export interface Updates {");
|
|
1214
|
-
lines.push(updates.join(`
|
|
1215
|
-
`));
|
|
1216
|
-
lines.push("}");
|
|
1217
|
-
return `${lines.join(`
|
|
1218
|
-
`)}
|
|
1219
|
-
`;
|
|
1220
|
-
}
|
|
1221
|
-
function propertyName(name) {
|
|
1222
|
-
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : JSON.stringify(name);
|
|
1223
|
-
}
|
|
1224
|
-
function fields(item, kind) {
|
|
1225
|
-
const printed = [];
|
|
1226
|
-
for (const [field, builder] of Object.entries(item.columns)) {
|
|
1227
|
-
if (!(builder instanceof ColumnBuilder2)) {
|
|
1228
|
-
continue;
|
|
1229
|
-
}
|
|
1230
|
-
const state = builder.state;
|
|
1231
|
-
const written = insertMode(state);
|
|
1232
|
-
if (kind === "row") {
|
|
1233
|
-
if (!state.hidden) {
|
|
1234
|
-
printed.push(` readonly ${field}: ${valueType(state, "required")};`);
|
|
1235
|
-
}
|
|
1236
|
-
continue;
|
|
1237
|
-
}
|
|
1238
|
-
if (written === "omit") {
|
|
1239
|
-
continue;
|
|
1240
|
-
}
|
|
1241
|
-
const mode = kind === "update" ? "optional" : written;
|
|
1242
|
-
printed.push(` readonly ${field}: ${valueType(state, mode)};`);
|
|
1243
|
-
}
|
|
1244
|
-
return printed.join(`
|
|
1245
|
-
`);
|
|
1246
|
-
}
|
|
1247
|
-
function insertMode(state) {
|
|
1248
|
-
if (state.omitWrite || state.generated !== undefined || state.guarded) {
|
|
1249
|
-
return "omit";
|
|
1250
|
-
}
|
|
1251
|
-
if (state.hasDefault || state.nullable) {
|
|
1252
|
-
return "optional";
|
|
1253
|
-
}
|
|
1254
|
-
return "required";
|
|
1255
|
-
}
|
|
1256
|
-
function valueType(state, mode) {
|
|
1257
|
-
let label = scalarLabel(state);
|
|
1258
|
-
if (state.dims > 0) {
|
|
1259
|
-
for (let rank = 0;rank < state.dims; rank += 1) {
|
|
1260
|
-
label = `readonly (${label})[]`;
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
if (state.nullable) {
|
|
1264
|
-
label += " | null";
|
|
1265
|
-
}
|
|
1266
|
-
if (mode === "optional") {
|
|
1267
|
-
label += " | undefined";
|
|
1268
|
-
}
|
|
1269
|
-
return label;
|
|
1270
|
-
}
|
|
1271
|
-
function scalarLabel(state) {
|
|
1272
|
-
if (state.picklist !== undefined) {
|
|
1273
|
-
return state.picklist.values.map((value) => JSON.stringify(value)).join(" | ");
|
|
1274
|
-
}
|
|
1275
|
-
if (state.typeLabel !== undefined) {
|
|
1276
|
-
return state.typeLabel;
|
|
1277
|
-
}
|
|
1278
|
-
const base = baseName(state.baseType);
|
|
1279
|
-
switch (base) {
|
|
1280
|
-
case "smallint":
|
|
1281
|
-
case "integer":
|
|
1282
|
-
case "real":
|
|
1283
|
-
case "double precision":
|
|
1284
|
-
return "number";
|
|
1285
|
-
case "bigint":
|
|
1286
|
-
case "numeric":
|
|
1287
|
-
case "text":
|
|
1288
|
-
case "varchar":
|
|
1289
|
-
case "char":
|
|
1290
|
-
case "citext":
|
|
1291
|
-
case "uuid":
|
|
1292
|
-
case "inet":
|
|
1293
|
-
case "cidr":
|
|
1294
|
-
case "macaddr":
|
|
1295
|
-
case "macaddr8":
|
|
1296
|
-
case "tsvector":
|
|
1297
|
-
case "ltree":
|
|
1298
|
-
return "string";
|
|
1299
|
-
case "boolean":
|
|
1300
|
-
return "boolean";
|
|
1301
|
-
case "bytea":
|
|
1302
|
-
return "Uint8Array";
|
|
1303
|
-
case "json":
|
|
1304
|
-
case "jsonb":
|
|
1305
|
-
return "unknown";
|
|
1306
|
-
case "timestamptz":
|
|
1307
|
-
return "Temporal.Instant";
|
|
1308
|
-
case "timestamp":
|
|
1309
|
-
return "Temporal.PlainDateTime";
|
|
1310
|
-
case "date":
|
|
1311
|
-
return "Temporal.PlainDate";
|
|
1312
|
-
case "time":
|
|
1313
|
-
return "Temporal.PlainTime";
|
|
1314
|
-
case "timetz":
|
|
1315
|
-
return "{ readonly time: Temporal.PlainTime; readonly offset: string }";
|
|
1316
|
-
case "interval":
|
|
1317
|
-
return "Temporal.Duration";
|
|
1318
|
-
case "tstzrange":
|
|
1319
|
-
return rangeLabel("Temporal.Instant");
|
|
1320
|
-
case "daterange":
|
|
1321
|
-
return rangeLabel("Temporal.PlainDate");
|
|
1322
|
-
case "int4range":
|
|
1323
|
-
return rangeLabel("number");
|
|
1324
|
-
case "int8range":
|
|
1325
|
-
case "numrange":
|
|
1326
|
-
return rangeLabel("string");
|
|
1327
|
-
case "point":
|
|
1328
|
-
return "{ readonly x: number; readonly y: number }";
|
|
1329
|
-
case "line":
|
|
1330
|
-
return "{ readonly a: number; readonly b: number; readonly c: number }";
|
|
1331
|
-
default:
|
|
1332
|
-
return "unknown";
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
function rangeLabel(bound) {
|
|
1336
|
-
return `{ readonly empty: true } | { readonly empty: false; readonly lower: ${bound} | null; readonly upper: ${bound} | null; readonly lowerInclusive: boolean; readonly upperInclusive: boolean }`;
|
|
1337
|
-
}
|
|
1338
|
-
function baseName(dataType) {
|
|
1339
|
-
if (dataType.startsWith("double precision")) {
|
|
1340
|
-
return "double precision";
|
|
1341
|
-
}
|
|
1342
|
-
const space = dataType.indexOf(" ");
|
|
1343
|
-
const head = space === -1 ? dataType : dataType.slice(0, space);
|
|
1344
|
-
const paren = head.indexOf("(");
|
|
1345
|
-
return paren === -1 ? head : head.slice(0, paren);
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
// src/tooling/migrate/project.ts
|
|
1349
|
-
async function buildProject(cwd) {
|
|
1350
|
-
const config = await loadConfig(cwd);
|
|
1351
|
-
const built = await loadSchema(cwd, config.schema);
|
|
1352
|
-
return writeArtifact(cwd, config, built);
|
|
1353
|
-
}
|
|
1354
|
-
async function generateProject(cwd, name, flags) {
|
|
1355
|
-
const prepared = await prepare(cwd, name, flags);
|
|
1356
|
-
writeArtifact(cwd, prepared.config, prepared.built);
|
|
1357
|
-
if (prepared.plan.steps.length === 0)
|
|
1358
|
-
return;
|
|
1359
|
-
const directory = join(cwd, prepared.config.migrations ?? "migrations");
|
|
1360
|
-
mkdirSync(directory, { recursive: true });
|
|
1361
|
-
const base = `${nextNumber(directory)}_${safeName(name)}`;
|
|
1362
|
-
const sqlPath = join(directory, `${base}.sql`);
|
|
1363
|
-
writeFileSync(sqlPath, formatPlan(prepared.plan));
|
|
1364
|
-
writeFileSync(join(directory, `${base}.catalog.json`), serializeCatalog2(prepared.built.catalog));
|
|
1365
|
-
return sqlPath;
|
|
1366
|
-
}
|
|
1367
|
-
async function planProject(cwd, name, flags) {
|
|
1368
|
-
const prepared = await prepare(cwd, name, flags);
|
|
1369
|
-
return prepared.plan;
|
|
1370
|
-
}
|
|
1371
|
-
async function checkProject(cwd) {
|
|
1372
|
-
const opened = await openProject(cwd);
|
|
1373
|
-
assertTargetAlias(listTargets(opened.config));
|
|
1374
|
-
if (opened.config.tables !== undefined) {
|
|
1375
|
-
const directory = join(cwd, opened.config.tables);
|
|
1376
|
-
const files = existsSync(directory) ? readdirSync(directory) : [];
|
|
1377
|
-
const source = readFileSync(join(cwd, opened.config.schema), "utf8");
|
|
1378
|
-
const missing = unlistedTableFiles(source, files);
|
|
1379
|
-
if (missing.length > 0) {
|
|
1380
|
-
throw new OkmError2("OKM1024", `Table file not in schema: ${missing.join(", ")}.`, {
|
|
1381
|
-
fix: { summary: "Add the file's table to the schema() call that okm check reads." }
|
|
1382
|
-
});
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
function unlistedTableFiles(schemaSource, fileNames) {
|
|
1387
|
-
const missing = [];
|
|
1388
|
-
for (const file of fileNames) {
|
|
1389
|
-
if (!file.endsWith(".ts"))
|
|
1390
|
-
continue;
|
|
1391
|
-
const stem = basename(file, ".ts");
|
|
1392
|
-
if (stem.length === 0 || stem.startsWith("."))
|
|
1393
|
-
continue;
|
|
1394
|
-
if (!schemaSource.includes(stem))
|
|
1395
|
-
missing.push(file);
|
|
1396
|
-
}
|
|
1397
|
-
return missing;
|
|
1398
|
-
}
|
|
1399
|
-
async function projectHead(cwd) {
|
|
1400
|
-
const opened = await openProject(cwd);
|
|
1401
|
-
return { previous: opened.previous, catalog: opened.built.catalog, renames: opened.renames };
|
|
1402
|
-
}
|
|
1403
|
-
async function prepare(cwd, name, flags) {
|
|
1404
|
-
const opened = await openProject(cwd);
|
|
1405
|
-
const plan = planMigration({
|
|
1406
|
-
before: opened.previous,
|
|
1407
|
-
after: opened.built.catalog,
|
|
1408
|
-
renames: opened.renames,
|
|
1409
|
-
replacements: flags.map((flag) => parseReplace(flag)),
|
|
1410
|
-
name
|
|
1411
|
-
});
|
|
1412
|
-
return { config: opened.config, built: opened.built, plan };
|
|
1413
|
-
}
|
|
1414
|
-
async function openProject(cwd) {
|
|
1415
|
-
const config = await loadConfig(cwd);
|
|
1416
|
-
const built = await loadSchema(cwd, config.schema);
|
|
1417
|
-
const declarations = schemaDeclarations(built);
|
|
1418
|
-
const previous = readPrevious(join(cwd, config.migrations ?? "migrations"));
|
|
1419
|
-
const stale = staleRenames(previous?.catalog, declarations.renames);
|
|
1420
|
-
if (stale.length > 0) {
|
|
1421
|
-
throw new OkmError2("OKM1020", stale.join(" "), {
|
|
1422
|
-
fix: { summary: "Remove renamedFrom once the migration that used it has been applied." }
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
|
-
return {
|
|
1426
|
-
config,
|
|
1427
|
-
built,
|
|
1428
|
-
previous: previous?.catalog ?? catalog([]),
|
|
1429
|
-
renames: declarations.renames
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
function writeArtifact(cwd, config, built) {
|
|
1433
|
-
const directory = join(cwd, config.out ?? ".okm");
|
|
1434
|
-
mkdirSync(directory, { recursive: true });
|
|
1435
|
-
const text = serializeCatalog2(built.catalog);
|
|
1436
|
-
writeFileSync(join(directory, "catalog.json"), text);
|
|
1437
|
-
writeFileSync(join(directory, "catalog.hash"), `${catalogHash2(built.catalog)}
|
|
1438
|
-
`);
|
|
1439
|
-
writeFileSync(join(directory, "types.d.ts"), `${emitRowTypes(built)}${referenceAugmentation(built)}`);
|
|
1440
|
-
return directory;
|
|
1441
|
-
}
|
|
1442
|
-
async function loadConfig(cwd) {
|
|
1443
|
-
const path = join(cwd, "okmodel.config.ts");
|
|
1444
|
-
const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
|
|
1445
|
-
const record = isRecord(imported) ? imported : {};
|
|
1446
|
-
const config = record.default ?? record.config;
|
|
1447
|
-
if (!isConfig(config)) {
|
|
1448
|
-
throw new OkmError2("invalid", "okmodel.config.ts must default-export defineConfig(...).");
|
|
1449
|
-
}
|
|
1450
|
-
return config;
|
|
1451
|
-
}
|
|
1452
|
-
async function loadSchema(cwd, spec) {
|
|
1453
|
-
const path = join(cwd, spec);
|
|
1454
|
-
const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
|
|
1455
|
-
const record = isRecord(imported) ? imported : {};
|
|
1456
|
-
for (const value of Object.values(record)) {
|
|
1457
|
-
if (!isBuilt(value))
|
|
1458
|
-
continue;
|
|
1459
|
-
value.catalog;
|
|
1460
|
-
return value;
|
|
1461
|
-
}
|
|
1462
|
-
throw new OkmError2("invalid", `${spec} must export a schema().`);
|
|
1463
|
-
}
|
|
1464
|
-
function readPrevious(directory) {
|
|
1465
|
-
if (!existsSync(directory))
|
|
1466
|
-
return;
|
|
1467
|
-
const catalogs = readdirSync(directory).filter((file) => file.endsWith(".catalog.json")).sort();
|
|
1468
|
-
const latest = catalogs.at(-1);
|
|
1469
|
-
if (latest === undefined)
|
|
1470
|
-
return;
|
|
1471
|
-
const text = readFileSync(join(directory, latest), "utf8");
|
|
1472
|
-
return { catalog: parseCatalog2(text) };
|
|
1473
|
-
}
|
|
1474
|
-
function referenceAugmentation(source) {
|
|
1475
|
-
const names = source.tables.map((item) => JSON.stringify(item.name));
|
|
1476
|
-
const union = names.length === 0 ? "never" : names.join(" | ");
|
|
1477
|
-
return [
|
|
1478
|
-
'import type { ColumnFlags, ReferenceOptions } from "okmodel/pg";',
|
|
1479
|
-
"",
|
|
1480
|
-
'declare module "okmodel/pg" {',
|
|
1481
|
-
" interface ColumnBuilder<TValue, TFlags extends ColumnFlags> {",
|
|
1482
|
-
" references(",
|
|
1483
|
-
` table: ${union},`,
|
|
1484
|
-
" options?: ReferenceOptions,",
|
|
1485
|
-
" ): ColumnBuilder<TValue, TFlags>;",
|
|
1486
|
-
" }",
|
|
1487
|
-
"}",
|
|
1488
|
-
"",
|
|
1489
|
-
`export type TableName = ${union};`,
|
|
1490
|
-
""
|
|
1491
|
-
].join(`
|
|
1492
|
-
`);
|
|
1493
|
-
}
|
|
1494
|
-
function nextNumber(directory) {
|
|
1495
|
-
let max = 0;
|
|
1496
|
-
if (existsSync(directory)) {
|
|
1497
|
-
for (const file of readdirSync(directory)) {
|
|
1498
|
-
const match = /^(\d+)_/.exec(file);
|
|
1499
|
-
const value = match?.[1];
|
|
1500
|
-
if (value !== undefined)
|
|
1501
|
-
max = Math.max(max, Number(value));
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
return String(max + 1).padStart(4, "0");
|
|
1505
|
-
}
|
|
1506
|
-
function safeName(name) {
|
|
1507
|
-
if (!/^[A-Za-z0-9_-]+$/.test(name)) {
|
|
1508
|
-
throw new OkmError2("invalid", `Migration name ${name} must be letters, digits, _ or -.`);
|
|
1509
|
-
}
|
|
1510
|
-
return name;
|
|
1511
|
-
}
|
|
1512
|
-
function isBuilt(value) {
|
|
1513
|
-
if (!isRecord(value) || !Array.isArray(value.tables))
|
|
1514
|
-
return false;
|
|
1515
|
-
return "catalog" in value;
|
|
1516
|
-
}
|
|
1517
|
-
function isConfig(value) {
|
|
1518
|
-
return isRecord(value) && typeof value.schema === "string";
|
|
1519
|
-
}
|
|
1520
|
-
function isRecord(value) {
|
|
1521
|
-
return typeof value === "object" && value !== null;
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
export { quoteIdent, planMigration, formatPlan, parsePlan, assertTargetPolicy, assertDirectConnection, listTargets, selectTarget, buildProject, generateProject, planProject, checkProject, projectHead, loadConfig };
|
|
1576
|
+
export { createdRoleName, changesRole, assertExtensionsAvailable, omitManagedObjects, parseReplace, planMigration, formatPlan, parsePlan, staleRenames };
|