okmodel 0.2.1 → 0.4.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 +882 -48
- package/dist/contracts/catalog/document.d.ts +6 -2
- package/dist/contracts/catalog/enum.d.ts +31 -4
- package/dist/contracts/catalog/extension.d.ts +61 -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 +164 -10
- package/dist/contracts/catalog/view.d.ts +73 -0
- package/dist/dialects/pg/column.d.ts +3 -1
- 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/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.js +104 -19
- package/dist/dialects/pg/introspect.d.ts +10 -1
- package/dist/dialects/pg/operators.d.ts +8 -1
- 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 +51 -0
- package/dist/dialects/pg/table.d.ts +24 -1
- package/dist/dialects/pg/tenancy.d.ts +9 -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 +22 -0
- package/dist/dialects/pg/where.d.ts +2 -2
- package/dist/runtime/catalog-cache.d.ts +17 -0
- package/dist/runtime/compat-words.d.ts +23 -0
- package/dist/runtime/drift-detail.d.ts +20 -4
- package/dist/runtime/drift.d.ts +4 -2
- package/dist/runtime/pg/bun.js +3 -3
- package/dist/runtime/pg/pg.js +3 -3
- package/dist/runtime/pg/pglite.js +3 -3
- package/dist/runtime/pg/postgresjs.js +3 -3
- package/dist/runtime/tenancy/index.js +6 -3
- package/dist/runtime/traits/index.d.ts +7 -2
- package/dist/runtime/traits/index.js +151 -13
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/validate/index.js +4 -4
- package/dist/shared/{xkz8g6by.js → 1g0drakv.js} +2 -2
- package/dist/shared/2321avmn.js +418 -0
- package/dist/shared/{7mht49yd.js → 3p4b1fg0.js} +57 -18
- package/dist/shared/41kv2xdm.js +318 -0
- package/dist/shared/{97g2jnrw.js → 4ef7mmqf.js} +3 -3
- package/dist/shared/{sacqha1y.js → 4m9fn52z.js} +2 -2
- package/dist/shared/5mtf6cj3.js +94 -0
- package/dist/shared/{1dp5knt7.js → 5vdm4sgp.js} +2 -2
- package/dist/shared/{a1gdp7j9.js → 6693j9rs.js} +1 -1
- package/dist/shared/{38crxbgn.js → 6p5k20sm.js} +3 -3
- package/dist/shared/7nk68fj8.js +751 -0
- package/dist/shared/84ptn35q.js +71 -0
- package/dist/shared/{d2whjz4k.js → 8jb4ymh2.js} +581 -28
- package/dist/shared/8ktwd3rh.js +97 -0
- package/dist/shared/94ez8kzh.js +19 -0
- package/dist/shared/{3a5ne750.js → 9m80mkac.js} +2 -2
- package/dist/shared/9x25fxhc.js +150 -0
- package/dist/shared/9yn6a2vd.js +11 -0
- package/dist/shared/ady1ms74.js +72 -0
- package/dist/shared/{4jzzghr6.js → awrsqrn7.js} +6 -6
- package/dist/shared/{4b9da9d7.js → axhk06q1.js} +19 -1
- package/dist/shared/{d04j53v8.js → c5g81ngb.js} +82 -14
- package/dist/shared/{n47rd821.js → c8aghh05.js} +1 -1
- package/dist/shared/{dd4f8mnx.js → c9z5g0fm.js} +4 -2
- package/dist/shared/{ps43134h.js → dyj86cmj.js} +4 -4
- package/dist/shared/{0qkxbm1f.js → fa9vt245.js} +1 -1
- package/dist/shared/{qa5dsc7z.js → fb00y8fp.js} +3 -3
- package/dist/shared/{qxa0yb9j.js → g7ez901n.js} +30 -1
- package/dist/shared/{py3nh7jy.js → gdh9mrtp.js} +2 -2
- package/dist/shared/{yw5c610b.js → gjthds9j.js} +2 -2
- package/dist/shared/hcmdtwgh.js +102 -0
- package/dist/shared/hqjsxnz7.js +13 -0
- package/dist/shared/htnaj77v.js +17 -0
- package/dist/shared/{v18jva5b.js → jgxzjzcz.js} +1 -1
- package/dist/shared/{x9v1qgeb.js → jsd07prz.js} +3 -3
- package/dist/shared/{g9f0zqce.js → kafaa5zv.js} +1 -1
- package/dist/shared/kj680nxv.js +19 -0
- package/dist/shared/{mxmcdsh5.js → kkzwp3d2.js} +9 -9
- package/dist/shared/{g1b5zzj9.js → n0dgs0pb.js} +1 -1
- package/dist/shared/n5y9zyj1.js +2431 -0
- package/dist/shared/{zt8y0wzb.js → n7mcc1gk.js} +1 -1
- package/dist/shared/{ge03bfca.js → nxb42q7h.js} +2 -2
- package/dist/shared/q6zsyh31.js +2192 -0
- package/dist/shared/{sfh4pk75.js → q795afwa.js} +26 -21
- package/dist/shared/{10tjenmd.js → qn0srwwq.js} +1 -1
- package/dist/shared/s4mb6a4b.js +545 -0
- package/dist/shared/{ytg3xf9p.js → sf8m6tfy.js} +3 -3
- package/dist/shared/{zd93g3rq.js → sr5ms7b3.js} +1 -1
- package/dist/shared/{xatfg0gn.js → tmbdywx0.js} +4 -19
- package/dist/shared/tny2t3sm.js +100 -0
- package/dist/shared/{e7a268fy.js → v7tpnhnz.js} +1 -1
- package/dist/shared/{ecdzej21.js → wqem4jed.js} +7 -7
- package/dist/shared/xghkt7a9.js +1144 -0
- package/dist/shared/y005b761.js +112 -0
- package/dist/shared/ycatkxmq.js +84 -0
- package/dist/shared/ygem90rn.js +900 -0
- package/dist/shared/{eaxwrgby.js → yjw02enp.js} +6 -3
- package/dist/shared/{mrq77sgw.js → zg24x0hm.js} +1 -1
- package/dist/shared/{cstgkzdx.js → zvbjc949.js} +1 -1
- package/dist/tooling/cli.js +66 -13
- package/dist/tooling/migrate/apply.d.ts +75 -5
- package/dist/tooling/migrate/backfill.d.ts +136 -0
- package/dist/tooling/migrate/check.d.ts +65 -0
- package/dist/tooling/migrate/classify.d.ts +108 -0
- package/dist/tooling/migrate/commands.d.ts +2 -1
- package/dist/tooling/migrate/config.d.ts +30 -0
- package/dist/tooling/migrate/doctor.d.ts +28 -0
- package/dist/tooling/migrate/drift.d.ts +35 -0
- package/dist/tooling/migrate/estimate.d.ts +50 -0
- package/dist/tooling/migrate/ext.d.ts +17 -0
- package/dist/tooling/migrate/extensions.d.ts +53 -0
- package/dist/tooling/migrate/files.d.ts +12 -0
- package/dist/tooling/migrate/index.d.ts +3 -2
- package/dist/tooling/migrate/index.js +67 -1
- package/dist/tooling/migrate/lint-rules.d.ts +51 -0
- package/dist/tooling/migrate/lint.d.ts +71 -0
- package/dist/tooling/migrate/managed.d.ts +17 -0
- package/dist/tooling/migrate/plan.d.ts +78 -10
- package/dist/tooling/migrate/project.d.ts +64 -5
- package/dist/tooling/migrate/provision.d.ts +18 -0
- package/dist/tooling/migrate/reference.d.ts +40 -0
- package/dist/tooling/migrate/runner.d.ts +108 -0
- package/dist/tooling/migrate/safe.d.ts +77 -0
- package/dist/tooling/migrate/seed.d.ts +20 -0
- package/dist/tooling/migrate/snapshot.d.ts +78 -0
- package/dist/tooling/migrate/status.d.ts +25 -6
- package/dist/tooling/migrate/type-name.d.ts +19 -0
- package/dist/tooling/testing/factories.d.ts +117 -0
- package/dist/tooling/testing/facts.d.ts +72 -0
- package/dist/tooling/testing/index.d.ts +89 -0
- package/dist/tooling/testing/index.js +8 -0
- package/dist/tooling/testing/isolation.d.ts +30 -0
- package/dist/tooling/testing/random.d.ts +40 -0
- package/dist/tooling/testing/record.d.ts +38 -0
- package/package.json +26 -2
- package/dist/shared/5z4q2zda.js +0 -2073
- package/dist/shared/eqmtn1bj.js +0 -399
- package/dist/shared/rtnaqre0.js +0 -37
- package/dist/shared/zwmqxb0g.js +0 -79
- package/dist/shared/{m9tv7xn7.js → radd3z4g.js} +3 -3
- /package/dist/shared/{9k6vetvf.js → rrstp3kb.js} +0 -0
|
@@ -0,0 +1,2431 @@
|
|
|
1
|
+
import {
|
|
2
|
+
creationOrder2,
|
|
3
|
+
renameColumn2,
|
|
4
|
+
renameTable2
|
|
5
|
+
} from "./8jb4ymh2.js";
|
|
6
|
+
import {
|
|
7
|
+
quoteIdent,
|
|
8
|
+
ownedByView,
|
|
9
|
+
dropObjectSql,
|
|
10
|
+
createObjectSql,
|
|
11
|
+
functionSql,
|
|
12
|
+
viewSql,
|
|
13
|
+
refreshMaterializedViewSql,
|
|
14
|
+
alterColumnSql,
|
|
15
|
+
identitySequence,
|
|
16
|
+
createTableSql,
|
|
17
|
+
qualify
|
|
18
|
+
} from "./41kv2xdm.js";
|
|
19
|
+
import {
|
|
20
|
+
isExactVersion,
|
|
21
|
+
compareExtensionVersions,
|
|
22
|
+
sameExtensionDefinition
|
|
23
|
+
} from "./8ktwd3rh.js";
|
|
24
|
+
import {
|
|
25
|
+
isDomain,
|
|
26
|
+
sameEnumLabels
|
|
27
|
+
} from "./g7ez901n.js";
|
|
28
|
+
import {
|
|
29
|
+
quoteLiteral
|
|
30
|
+
} from "./94ez8kzh.js";
|
|
31
|
+
import {
|
|
32
|
+
staticNamespace2,
|
|
33
|
+
identityKey2,
|
|
34
|
+
identityLabel2,
|
|
35
|
+
compareText
|
|
36
|
+
} from "./t4tbsy9h.js";
|
|
37
|
+
import {
|
|
38
|
+
fitIdentifier2
|
|
39
|
+
} from "./nfzc5z0w.js";
|
|
40
|
+
import {
|
|
41
|
+
OkmError2
|
|
42
|
+
} from "./52szzakm.js";
|
|
43
|
+
|
|
44
|
+
// src/tooling/migrate/type-name.ts
|
|
45
|
+
function canonicalTypeName(typeName) {
|
|
46
|
+
const suffix = arraySuffix(typeName);
|
|
47
|
+
const scalar = suffix.length === 0 ? typeName : typeName.slice(0, -suffix.length);
|
|
48
|
+
return canonicalScalar(scalar) + suffix;
|
|
49
|
+
}
|
|
50
|
+
function arraySuffix(typeName) {
|
|
51
|
+
let end = typeName.length;
|
|
52
|
+
let suffix = "";
|
|
53
|
+
while (end >= 2 && typeName.endsWith("[]", end)) {
|
|
54
|
+
suffix = `[]${suffix}`;
|
|
55
|
+
end -= 2;
|
|
56
|
+
}
|
|
57
|
+
return suffix;
|
|
58
|
+
}
|
|
59
|
+
function canonicalScalar(typeName) {
|
|
60
|
+
const varying = /^(?:varchar|character varying)(\(\d+\))$/.exec(typeName);
|
|
61
|
+
if (varying !== null)
|
|
62
|
+
return `character varying${varying[1] ?? ""}`;
|
|
63
|
+
const fixed = /^(?:char|character)(\(\d+\))$/.exec(typeName);
|
|
64
|
+
if (fixed !== null)
|
|
65
|
+
return `character${fixed[1] ?? ""}`;
|
|
66
|
+
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);
|
|
67
|
+
if (timed !== null)
|
|
68
|
+
return canonicalTime(timed[1] ?? "", timed[2] ?? "");
|
|
69
|
+
const numeric = /^(?:decimal|numeric)(\(\d+(?:,\d+)?\))?$/.exec(typeName);
|
|
70
|
+
if (numeric !== null)
|
|
71
|
+
return `numeric${numeric[1] ?? ""}`;
|
|
72
|
+
switch (typeName) {
|
|
73
|
+
case "int":
|
|
74
|
+
case "int4":
|
|
75
|
+
case "integer":
|
|
76
|
+
return "integer";
|
|
77
|
+
case "int8":
|
|
78
|
+
case "bigint":
|
|
79
|
+
return "bigint";
|
|
80
|
+
case "int2":
|
|
81
|
+
case "smallint":
|
|
82
|
+
return "smallint";
|
|
83
|
+
case "bool":
|
|
84
|
+
case "boolean":
|
|
85
|
+
return "boolean";
|
|
86
|
+
case "float8":
|
|
87
|
+
case "double precision":
|
|
88
|
+
return "double precision";
|
|
89
|
+
case "float4":
|
|
90
|
+
case "real":
|
|
91
|
+
return "real";
|
|
92
|
+
default:
|
|
93
|
+
return typeName;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function canonicalTime(name, precision) {
|
|
97
|
+
if (name === "timestamptz" || name === "timestamp with time zone") {
|
|
98
|
+
return `timestamp${precision} with time zone`;
|
|
99
|
+
}
|
|
100
|
+
if (name === "timestamp" || name === "timestamp without time zone") {
|
|
101
|
+
return `timestamp${precision} without time zone`;
|
|
102
|
+
}
|
|
103
|
+
if (name === "timetz" || name === "time with time zone") {
|
|
104
|
+
return `time${precision} with time zone`;
|
|
105
|
+
}
|
|
106
|
+
return `time${precision} without time zone`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/dialects/pg/role/sql.ts
|
|
110
|
+
var PRIVILEGE = /^(select|insert|update|delete|execute|usage)$/;
|
|
111
|
+
function privilegeSql(before, after, schema, recreated = new Set) {
|
|
112
|
+
const beforeBy = index(before);
|
|
113
|
+
const afterBy = index(after);
|
|
114
|
+
const revoke = [];
|
|
115
|
+
const roles = [];
|
|
116
|
+
const defaults = [];
|
|
117
|
+
const grant = [];
|
|
118
|
+
for (const [key, object] of beforeBy) {
|
|
119
|
+
if (afterBy.has(key))
|
|
120
|
+
continue;
|
|
121
|
+
if (object.kind === "grant") {
|
|
122
|
+
revoke.push({ sql: grantStatement(object, schema, true), kind: "revoke" });
|
|
123
|
+
}
|
|
124
|
+
if (object.kind === "defaultPrivilege") {
|
|
125
|
+
revoke.push({ sql: defaultStatement(object, true), kind: "revoke-default" });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const [key, object] of afterBy) {
|
|
129
|
+
const previous = beforeBy.get(key);
|
|
130
|
+
if (object.kind === "role") {
|
|
131
|
+
if (object.owner !== "managed")
|
|
132
|
+
continue;
|
|
133
|
+
if (previous === undefined) {
|
|
134
|
+
roles.push({ sql: createRoleSql(object), kind: "create-role" });
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (previous.kind !== "role" || previous.owner !== "managed")
|
|
138
|
+
continue;
|
|
139
|
+
if (previous.definition.login !== object.definition.login || previous.definition.inherit !== object.definition.inherit) {
|
|
140
|
+
roles.push({ sql: alterRoleSql(object), kind: "alter-role" });
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (object.kind === "grant") {
|
|
145
|
+
const target = grantTargetKey(object.identity.object);
|
|
146
|
+
if (previous !== undefined && (target === undefined || !recreated.has(target)))
|
|
147
|
+
continue;
|
|
148
|
+
grant.push({ sql: grantStatement(object, schema, false), kind: "grant" });
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (previous !== undefined)
|
|
152
|
+
continue;
|
|
153
|
+
if (object.kind === "defaultPrivilege") {
|
|
154
|
+
defaults.push({ sql: defaultStatement(object, false), kind: "grant-default" });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { revoke, prepare: [...roles, ...defaults], grant };
|
|
158
|
+
}
|
|
159
|
+
function createdRoleName(sql) {
|
|
160
|
+
const match = /^\s*create\s+role\s+(?:"([^"]+)"|([A-Za-z_][\w$]*))/i.exec(sql);
|
|
161
|
+
const quoted = match?.[1];
|
|
162
|
+
if (quoted !== undefined)
|
|
163
|
+
return quoted.replaceAll('""', '"');
|
|
164
|
+
return match?.[2];
|
|
165
|
+
}
|
|
166
|
+
function changesRole(sql) {
|
|
167
|
+
return /^\s*(create|alter)\s+role\b/i.test(sql);
|
|
168
|
+
}
|
|
169
|
+
function index(objects) {
|
|
170
|
+
const by = new Map;
|
|
171
|
+
for (const object of objects) {
|
|
172
|
+
if (object.kind !== "role" && object.kind !== "grant" && object.kind !== "defaultPrivilege") {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
by.set(identityKey2(object.identity), object);
|
|
176
|
+
}
|
|
177
|
+
return by;
|
|
178
|
+
}
|
|
179
|
+
function createRoleSql(object) {
|
|
180
|
+
return `create role ${quoteIdent(object.identity.name)} with nosuperuser nocreatedb nocreaterole ${inherit(object)} ${login(object)}`;
|
|
181
|
+
}
|
|
182
|
+
function alterRoleSql(object) {
|
|
183
|
+
return `alter role ${quoteIdent(object.identity.name)} with ${inherit(object)} ${login(object)}`;
|
|
184
|
+
}
|
|
185
|
+
function login(object) {
|
|
186
|
+
return object.definition.login ? "login" : "nologin";
|
|
187
|
+
}
|
|
188
|
+
function inherit(object) {
|
|
189
|
+
return object.definition.inherit ? "inherit" : "noinherit";
|
|
190
|
+
}
|
|
191
|
+
function grantStatement(object, schema, revoke) {
|
|
192
|
+
const privilege = object.identity.privilege.toLowerCase();
|
|
193
|
+
assertPrivilege(privilege);
|
|
194
|
+
const verb = revoke ? "revoke" : "grant";
|
|
195
|
+
const direction = revoke ? "from" : "to";
|
|
196
|
+
return `${verb} ${privilege} on ${grantTarget(object.identity.object, schema)} ${direction} ${quoteIdent(object.identity.role)}`;
|
|
197
|
+
}
|
|
198
|
+
function defaultStatement(object, revoke) {
|
|
199
|
+
const privilege = object.identity.privilege.toLowerCase();
|
|
200
|
+
assertPrivilege(privilege);
|
|
201
|
+
const verb = revoke ? "revoke" : "grant";
|
|
202
|
+
const direction = revoke ? "from" : "to";
|
|
203
|
+
const namespace = object.identity.namespace.form === "static" ? object.identity.namespace.name : "public";
|
|
204
|
+
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)}`;
|
|
205
|
+
}
|
|
206
|
+
function grantTargetKey(object) {
|
|
207
|
+
if (object.kind === "table" || object.kind === "view" || object.kind === "materializedView" || object.kind === "sequence") {
|
|
208
|
+
return identityKey2({ kind: object.kind, namespace: object.namespace, name: object.name });
|
|
209
|
+
}
|
|
210
|
+
if (object.kind !== "function")
|
|
211
|
+
return;
|
|
212
|
+
const open = object.name.lastIndexOf("(");
|
|
213
|
+
if (open < 0 || !object.name.endsWith(")"))
|
|
214
|
+
return;
|
|
215
|
+
const name = object.name.slice(0, open);
|
|
216
|
+
const args = object.name.slice(open + 1, -1);
|
|
217
|
+
return identityKey2({
|
|
218
|
+
kind: "function",
|
|
219
|
+
namespace: object.namespace,
|
|
220
|
+
name,
|
|
221
|
+
argTypes: args.length === 0 ? [] : args.split(",")
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function grantTarget(object, schema) {
|
|
225
|
+
const name = object.namespace.form === "static" ? object.namespace.name : schema;
|
|
226
|
+
if (object.kind === "sequence")
|
|
227
|
+
return `sequence ${qualify(name, object.name)}`;
|
|
228
|
+
if (object.kind === "function")
|
|
229
|
+
return functionTarget(object.name, name);
|
|
230
|
+
if (object.kind === "namespace")
|
|
231
|
+
return `schema ${quoteIdent(object.name)}`;
|
|
232
|
+
return `table ${qualify(name, object.name)}`;
|
|
233
|
+
}
|
|
234
|
+
function functionTarget(name, schema) {
|
|
235
|
+
const open = name.lastIndexOf("(");
|
|
236
|
+
if (open < 0 || !name.endsWith(")"))
|
|
237
|
+
return `function ${qualify(schema, name)}()`;
|
|
238
|
+
const fn = name.slice(0, open);
|
|
239
|
+
const args = name.slice(open + 1, -1);
|
|
240
|
+
return `function ${qualify(schema, fn)}(${args})`;
|
|
241
|
+
}
|
|
242
|
+
function objectKindSql(kind) {
|
|
243
|
+
if (kind === "function")
|
|
244
|
+
return "functions";
|
|
245
|
+
if (kind === "sequence")
|
|
246
|
+
return "sequences";
|
|
247
|
+
return "tables";
|
|
248
|
+
}
|
|
249
|
+
function assertPrivilege(privilege) {
|
|
250
|
+
if (!PRIVILEGE.test(privilege)) {
|
|
251
|
+
throw new OkmError2("invalid", `Privilege ${privilege} is not emitted.`, {
|
|
252
|
+
fix: {
|
|
253
|
+
summary: "Grants in this version are select, insert, update, delete, execute, and usage."
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/tooling/migrate/backfill.ts
|
|
260
|
+
var DEFAULT_BACKFILL_BATCH_SIZE = 1000;
|
|
261
|
+
var DEFAULT_BACKFILL_PAUSE_MS = 0;
|
|
262
|
+
var DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS = 30000;
|
|
263
|
+
var HEADER = /^-- backfill table=(\S+) key=(\S+) batch=(\d+)$/;
|
|
264
|
+
var CAST_TYPE = /^[A-Za-z_][\w$ ]*(?:\([^)]*\))?(?:\[\])*$/;
|
|
265
|
+
function batchSizeField(batchSize) {
|
|
266
|
+
return batchSize === undefined ? {} : { batchSize };
|
|
267
|
+
}
|
|
268
|
+
function resolveBatchSize(requested) {
|
|
269
|
+
const batch = requested ?? DEFAULT_BACKFILL_BATCH_SIZE;
|
|
270
|
+
if (!Number.isInteger(batch) || batch < 1) {
|
|
271
|
+
throw new OkmError2("invalid", "backfill.batchSize must be an integer of at least 1.", {
|
|
272
|
+
fix: {
|
|
273
|
+
summary: "Set defineConfig({ backfill: { batchSize } }) to a positive integer."
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return batch;
|
|
278
|
+
}
|
|
279
|
+
function formatBackfillHeader(spec) {
|
|
280
|
+
return `-- backfill table=${spec.table} key=${spec.key.join(",")} batch=${String(spec.batch)}`;
|
|
281
|
+
}
|
|
282
|
+
function parseBackfillHeader(line) {
|
|
283
|
+
if (!line.startsWith("-- backfill "))
|
|
284
|
+
return;
|
|
285
|
+
const match = HEADER.exec(line);
|
|
286
|
+
const table = match?.[1];
|
|
287
|
+
const keyText = match?.[2];
|
|
288
|
+
const batchText = match?.[3];
|
|
289
|
+
const key = keyText === undefined ? [] : splitKey(keyText);
|
|
290
|
+
const batch = batchText === undefined ? Number.NaN : Number(batchText);
|
|
291
|
+
if (table === undefined || key.length === 0 || !Number.isInteger(batch) || batch < 1) {
|
|
292
|
+
throw new OkmError2("invalid", "A backfill header needs table=<table> key=<col> batch=<n>.", {
|
|
293
|
+
fix: {
|
|
294
|
+
summary: "Write -- backfill table=<table> key=<col[,col]> batch=<n> with a batch of at least 1."
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return { table, key, batch };
|
|
299
|
+
}
|
|
300
|
+
function primaryKeyColumns(catalogs, table) {
|
|
301
|
+
for (const source of catalogs) {
|
|
302
|
+
const columns = keyOn(source, table);
|
|
303
|
+
if (columns !== undefined)
|
|
304
|
+
return columns;
|
|
305
|
+
}
|
|
306
|
+
throw new OkmError2("OKM1546", `Table ${table} has no primary key, so it cannot be backfilled.`, {
|
|
307
|
+
fix: {
|
|
308
|
+
summary: "Add a primary key. A backfill walks that key and does not scan the table."
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
function keyRangePredicate(columns) {
|
|
313
|
+
for (const column of columns)
|
|
314
|
+
assertCastType(column.dataType);
|
|
315
|
+
const lower = compareKey(columns, "$1", ">");
|
|
316
|
+
const upper = compareKey(columns, "$2", "<=");
|
|
317
|
+
return `($1::text is null or ${lower}) and ($2::text is null or ${upper})`;
|
|
318
|
+
}
|
|
319
|
+
function aboutBatchCount(rows, batch) {
|
|
320
|
+
if (!Number.isFinite(rows) || batch < 1)
|
|
321
|
+
return 0;
|
|
322
|
+
const rounded = Math.max(0, Math.round(rows));
|
|
323
|
+
if (rounded === 0)
|
|
324
|
+
return 0;
|
|
325
|
+
return Math.ceil(rounded / batch);
|
|
326
|
+
}
|
|
327
|
+
function keyOn(source, table) {
|
|
328
|
+
const constraint = source.objects.find((object) => object.kind === "constraint" && object.definition.constraintKind === "primaryKey" && object.identity.parent.name === table);
|
|
329
|
+
if (constraint === undefined || constraint.kind !== "constraint")
|
|
330
|
+
return;
|
|
331
|
+
const columns = [];
|
|
332
|
+
for (const name of constraint.definition.columns) {
|
|
333
|
+
const column = source.objects.find((object) => object.kind === "column" && object.identity.parent.name === table && object.identity.name === name);
|
|
334
|
+
if (column === undefined || column.kind !== "column") {
|
|
335
|
+
throw new OkmError2("OKM1546", `Table ${table} primary key column ${name} is not in the catalog.`, { fix: { summary: "Keep the primary key columns on the table." } });
|
|
336
|
+
}
|
|
337
|
+
columns.push({ quoted: quoteIdent(name), dataType: column.definition.dataType });
|
|
338
|
+
}
|
|
339
|
+
if (columns.length === 0)
|
|
340
|
+
return;
|
|
341
|
+
return columns;
|
|
342
|
+
}
|
|
343
|
+
function boundaryQuery(table, columns) {
|
|
344
|
+
for (const column of columns)
|
|
345
|
+
checkedCast(column.dataType);
|
|
346
|
+
const order = columns.map((column) => column.quoted).join(", ");
|
|
347
|
+
const first = columns[0];
|
|
348
|
+
const projected = columns.length === 1 && first !== undefined ? `${first.quoted}::text` : `jsonb_build_array(${columns.map((column) => `${column.quoted}::text`).join(", ")})::text`;
|
|
349
|
+
const lower = `($1::text is null or ${compareKey(columns, "$1", ">")})`;
|
|
350
|
+
return `select ${projected} as okm_key from ${table} where ${lower} order by ${order} offset $2::bigint limit 1`;
|
|
351
|
+
}
|
|
352
|
+
function checkedCast(dataType) {
|
|
353
|
+
assertCastType(dataType);
|
|
354
|
+
return dataType;
|
|
355
|
+
}
|
|
356
|
+
function compareKey(columns, param, operator) {
|
|
357
|
+
const first = columns[0];
|
|
358
|
+
if (first === undefined)
|
|
359
|
+
return "false";
|
|
360
|
+
if (columns.length === 1) {
|
|
361
|
+
return `${first.quoted} ${operator} ${param}::${first.dataType}`;
|
|
362
|
+
}
|
|
363
|
+
const left = columns.map((column) => column.quoted).join(", ");
|
|
364
|
+
const right = columns.map((column, index) => `((${param}::jsonb)->>${String(index)})::${column.dataType}`).join(", ");
|
|
365
|
+
return `(${left}) ${operator} (${right})`;
|
|
366
|
+
}
|
|
367
|
+
function assertCastType(dataType) {
|
|
368
|
+
if (CAST_TYPE.test(dataType))
|
|
369
|
+
return;
|
|
370
|
+
throw new OkmError2("OKM1546", `Primary key type ${dataType} cannot be used as a backfill bound.`, {
|
|
371
|
+
fix: {
|
|
372
|
+
summary: "Use an orderable scalar key. A backfill casts each bound to the key type."
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
function splitKey(text) {
|
|
377
|
+
const names = [];
|
|
378
|
+
let index = 0;
|
|
379
|
+
while (index < text.length) {
|
|
380
|
+
if (text[index] === '"') {
|
|
381
|
+
let value = "";
|
|
382
|
+
index += 1;
|
|
383
|
+
let closed = false;
|
|
384
|
+
while (index < text.length) {
|
|
385
|
+
const char = text[index];
|
|
386
|
+
if (char === '"' && text[index + 1] === '"') {
|
|
387
|
+
value += '"';
|
|
388
|
+
index += 2;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (char === '"') {
|
|
392
|
+
index += 1;
|
|
393
|
+
closed = true;
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
value += char ?? "";
|
|
397
|
+
index += 1;
|
|
398
|
+
}
|
|
399
|
+
if (!closed)
|
|
400
|
+
return [];
|
|
401
|
+
names.push(quoteIdent(value));
|
|
402
|
+
if (text[index] === ",")
|
|
403
|
+
index += 1;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const comma = text.indexOf(",", index);
|
|
407
|
+
const end = comma === -1 ? text.length : comma;
|
|
408
|
+
const raw = text.slice(index, end);
|
|
409
|
+
if (raw.length === 0)
|
|
410
|
+
return [];
|
|
411
|
+
names.push(quoteIdent(raw));
|
|
412
|
+
index = comma === -1 ? text.length : comma + 1;
|
|
413
|
+
}
|
|
414
|
+
return names;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/tooling/migrate/classify.ts
|
|
418
|
+
var STEP_CLASS = {
|
|
419
|
+
"create-table": "expand",
|
|
420
|
+
"drop-table": "contract",
|
|
421
|
+
"add-column": "expand",
|
|
422
|
+
"add-column-required": "contract",
|
|
423
|
+
"drop-column": "contract",
|
|
424
|
+
"set-column-type": "contract",
|
|
425
|
+
"set-not-null": "contract",
|
|
426
|
+
"drop-not-null": "expand",
|
|
427
|
+
"set-default": "expand",
|
|
428
|
+
"drop-default": "contract",
|
|
429
|
+
"add-identity": "expand",
|
|
430
|
+
"drop-identity": "contract",
|
|
431
|
+
"set-identity-always": "contract",
|
|
432
|
+
"set-identity-by-default": "expand",
|
|
433
|
+
"add-constraint": "expand",
|
|
434
|
+
"validate-constraint": "expand",
|
|
435
|
+
"drop-constraint": "contract",
|
|
436
|
+
"drop-not-null-check": "expand",
|
|
437
|
+
"rename-constraint": "contract",
|
|
438
|
+
"widen-check": "expand",
|
|
439
|
+
"narrow-check": "contract",
|
|
440
|
+
"create-index": "expand",
|
|
441
|
+
"drop-index": "contract",
|
|
442
|
+
"rename-index": "contract",
|
|
443
|
+
"create-sequence": "expand",
|
|
444
|
+
"drop-sequence": "contract",
|
|
445
|
+
"create-enum": "expand",
|
|
446
|
+
"recreate-enum": "contract",
|
|
447
|
+
"add-enum-value": "expand",
|
|
448
|
+
"rename-enum": "contract",
|
|
449
|
+
"drop-enum": "contract",
|
|
450
|
+
"set-enum-column": "contract",
|
|
451
|
+
"create-domain": "expand",
|
|
452
|
+
"drop-domain": "contract",
|
|
453
|
+
"add-domain-check": "expand",
|
|
454
|
+
"validate-domain-check": "expand",
|
|
455
|
+
"drop-domain-check": "contract",
|
|
456
|
+
"rename-domain-check": "expand",
|
|
457
|
+
"create-extension": "expand",
|
|
458
|
+
"drop-extension": "contract",
|
|
459
|
+
"update-extension": "expand",
|
|
460
|
+
"move-extension": "contract",
|
|
461
|
+
"create-function": "expand",
|
|
462
|
+
"drop-function": "contract",
|
|
463
|
+
"replace-function": "expand",
|
|
464
|
+
"create-trigger": "expand",
|
|
465
|
+
"drop-trigger": "contract",
|
|
466
|
+
"create-view": "expand",
|
|
467
|
+
"drop-view": "contract",
|
|
468
|
+
"replace-view": "expand",
|
|
469
|
+
"create-matview": "expand",
|
|
470
|
+
"drop-matview": "contract",
|
|
471
|
+
"refresh-matview": "expand",
|
|
472
|
+
"create-role": "expand",
|
|
473
|
+
"alter-role": "contract",
|
|
474
|
+
grant: "expand",
|
|
475
|
+
revoke: "contract",
|
|
476
|
+
"grant-default": "expand",
|
|
477
|
+
"revoke-default": "contract",
|
|
478
|
+
"rename-table": "contract",
|
|
479
|
+
"rename-column": "contract",
|
|
480
|
+
"rename-sequence": "contract",
|
|
481
|
+
"backfill-expand": "expand",
|
|
482
|
+
"backfill-contract": "contract",
|
|
483
|
+
"raw-sql": "unclassified"
|
|
484
|
+
};
|
|
485
|
+
var STEP_KINDS = Object.keys(STEP_CLASS);
|
|
486
|
+
function classOf(kind) {
|
|
487
|
+
return STEP_CLASS[kind];
|
|
488
|
+
}
|
|
489
|
+
function strictestClass(classes) {
|
|
490
|
+
if (classes.includes("contract"))
|
|
491
|
+
return "contract";
|
|
492
|
+
if (classes.includes("unclassified"))
|
|
493
|
+
return "unclassified";
|
|
494
|
+
return "expand";
|
|
495
|
+
}
|
|
496
|
+
function isStepKind(value) {
|
|
497
|
+
return Object.hasOwn(STEP_CLASS, value);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/tooling/migrate/extensions.ts
|
|
501
|
+
var CREATE_EXTENSION = /^\s*create\s+extension\s+(?:"([^"]+)"|([A-Za-z_][\w$]*))/i;
|
|
502
|
+
var UPDATE_EXTENSION = /^\s*alter\s+extension\s+(?:"([^"]+)"|([A-Za-z_][\w$]*))\s+update\s+to\s+'([^']*)'/i;
|
|
503
|
+
function extensionAlterSteps(beforeBy, afterBy, dropKeys) {
|
|
504
|
+
refuseDrops(beforeBy, afterBy, dropKeys);
|
|
505
|
+
const steps = [];
|
|
506
|
+
for (const after of afterBy.values()) {
|
|
507
|
+
if (after.kind !== "extension")
|
|
508
|
+
continue;
|
|
509
|
+
const before = beforeBy.get(identityKey2(after.identity));
|
|
510
|
+
if (before === undefined || before.kind !== "extension")
|
|
511
|
+
continue;
|
|
512
|
+
if (sameExtensionDefinition(before, after))
|
|
513
|
+
continue;
|
|
514
|
+
steps.push(...alterExtension(before, after));
|
|
515
|
+
}
|
|
516
|
+
return steps;
|
|
517
|
+
}
|
|
518
|
+
async function assertExtensionsAvailable(connection, migrations) {
|
|
519
|
+
const creates = new Set;
|
|
520
|
+
const updates = [];
|
|
521
|
+
for (const migration of migrations) {
|
|
522
|
+
for (const step of migration.steps) {
|
|
523
|
+
const created = CREATE_EXTENSION.exec(step.sql);
|
|
524
|
+
const name = created?.[1] ?? created?.[2];
|
|
525
|
+
if (name !== undefined)
|
|
526
|
+
creates.add(name.replaceAll('""', '"'));
|
|
527
|
+
const updated = UPDATE_EXTENSION.exec(step.sql);
|
|
528
|
+
const updateName = updated?.[1] ?? updated?.[2];
|
|
529
|
+
const version = updated?.[3];
|
|
530
|
+
if (updateName !== undefined && version !== undefined) {
|
|
531
|
+
updates.push({ name: updateName.replaceAll('""', '"'), version });
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (creates.size === 0 && updates.length === 0)
|
|
536
|
+
return;
|
|
537
|
+
const names = [...creates, ...updates.map((item) => item.name)];
|
|
538
|
+
const available = await availableExtensions(connection, names);
|
|
539
|
+
for (const name of creates) {
|
|
540
|
+
if (!available.has(name)) {
|
|
541
|
+
throw new OkmError2("OKM1811", `Extension ${name} is not available on the server. Nothing was changed.`, { fix: { summary: "Install the extension, or remove the declaration." } });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
for (const update of updates) {
|
|
545
|
+
if (!available.has(update.name)) {
|
|
546
|
+
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." } });
|
|
547
|
+
}
|
|
548
|
+
const installed = await installedVersion(connection, update.name);
|
|
549
|
+
if (installed === undefined) {
|
|
550
|
+
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." } });
|
|
551
|
+
}
|
|
552
|
+
const path = await updatePath(connection, update.name, installed, update.version);
|
|
553
|
+
if (!path) {
|
|
554
|
+
throw new OkmError2("OKM1814", `Extension ${update.name} has no update path from ${installed} to ${update.version}. Nothing was changed.`, {
|
|
555
|
+
fix: {
|
|
556
|
+
summary: "Drop or move the dependents first. Postgres cannot lower an extension version."
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function refuseDrops(beforeBy, afterBy, dropKeys) {
|
|
563
|
+
for (const key of dropKeys) {
|
|
564
|
+
const object = beforeBy.get(key);
|
|
565
|
+
if (object?.kind !== "extension")
|
|
566
|
+
continue;
|
|
567
|
+
for (const after of afterBy.values()) {
|
|
568
|
+
const used = after.dependencies.some((edge) => edge.target.kind === "extension" && edge.target.name === object.identity.name);
|
|
569
|
+
if (!used)
|
|
570
|
+
continue;
|
|
571
|
+
throw new OkmError2("OKM1814", `Extension ${object.identity.name} still has dependents. Nothing was dropped.`, {
|
|
572
|
+
fix: {
|
|
573
|
+
summary: "Drop or move the dependents first. Postgres cannot lower an extension version."
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function alterExtension(before, after) {
|
|
580
|
+
const steps = [];
|
|
581
|
+
const name = quoteIdent(after.identity.name);
|
|
582
|
+
if (before.definition.schema !== after.definition.schema) {
|
|
583
|
+
if (!after.definition.relocatable) {
|
|
584
|
+
throw new OkmError2("OKM1814", `Extension ${after.identity.name} is not relocatable, so it cannot move to ${after.definition.schema}.`, {
|
|
585
|
+
fix: {
|
|
586
|
+
summary: "Drop or move the dependents first. Postgres cannot lower an extension version."
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
steps.push(extensionStep(`alter extension ${name} set schema ${quoteIdent(after.definition.schema)}`, "move-extension"));
|
|
591
|
+
}
|
|
592
|
+
const previous = before.definition.version;
|
|
593
|
+
const next = after.definition.version;
|
|
594
|
+
if (!isExactVersion(previous) || !isExactVersion(next) || previous === next)
|
|
595
|
+
return steps;
|
|
596
|
+
if (compareExtensionVersions(next, previous) < 0) {
|
|
597
|
+
throw new OkmError2("OKM1814", `Extension ${after.identity.name} cannot move from ${previous} to ${next}. Postgres cannot lower an extension version.`, {
|
|
598
|
+
fix: {
|
|
599
|
+
summary: "Drop or move the dependents first. Postgres cannot lower an extension version."
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
steps.push(extensionStep(`alter extension ${name} update to ${quoteLiteral(next)}`, "update-extension", {
|
|
604
|
+
path: "unverified"
|
|
605
|
+
}));
|
|
606
|
+
return steps;
|
|
607
|
+
}
|
|
608
|
+
function extensionStep(sql, kind, extra) {
|
|
609
|
+
const classification = classOf(kind);
|
|
610
|
+
if (classification === "unclassified") {
|
|
611
|
+
throw new OkmError2("invalid", `Extension step ${kind} has no class.`, {
|
|
612
|
+
fix: { summary: "Classify the extension step as expand or contract." }
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
sql,
|
|
617
|
+
kind,
|
|
618
|
+
class: classification,
|
|
619
|
+
action: "ddl",
|
|
620
|
+
lock: "ACCESS EXCLUSIVE",
|
|
621
|
+
transactional: true,
|
|
622
|
+
...extra?.path !== undefined ? { path: extra.path } : {}
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
async function availableExtensions(connection, names) {
|
|
626
|
+
const listed = await connection.execute(`select name from pg_available_extensions where name in (${names.map((_, index) => `$${String(index + 1)}`).join(", ")})`, names);
|
|
627
|
+
const found = new Set;
|
|
628
|
+
for (const row of listed.rows) {
|
|
629
|
+
const name = row[0];
|
|
630
|
+
if (typeof name === "string")
|
|
631
|
+
found.add(name);
|
|
632
|
+
}
|
|
633
|
+
return found;
|
|
634
|
+
}
|
|
635
|
+
async function installedVersion(connection, name) {
|
|
636
|
+
const result = await connection.execute("select extversion from pg_extension where extname = $1", [name]);
|
|
637
|
+
const version = result.rows[0]?.[0];
|
|
638
|
+
return typeof version === "string" ? version : undefined;
|
|
639
|
+
}
|
|
640
|
+
async function updatePath(connection, name, source, target) {
|
|
641
|
+
const result = await connection.execute("select path from pg_extension_update_paths($1) where source = $2 and target = $3", [name, source, target]);
|
|
642
|
+
const path = result.rows[0]?.[0];
|
|
643
|
+
return typeof path === "string" && path.length > 0;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// src/tooling/migrate/managed.ts
|
|
647
|
+
var MANAGED_TABLES = new Set(["okm_meta", "okm_history", "okm_backfill"]);
|
|
648
|
+
function omitManagedObjects(source) {
|
|
649
|
+
let dropped = false;
|
|
650
|
+
const objects = [];
|
|
651
|
+
for (const object of source.objects) {
|
|
652
|
+
if (ownedByMigration(object)) {
|
|
653
|
+
dropped = true;
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
objects.push(object);
|
|
657
|
+
}
|
|
658
|
+
if (!dropped)
|
|
659
|
+
return source;
|
|
660
|
+
return { version: source.version, objects };
|
|
661
|
+
}
|
|
662
|
+
function ownedByMigration(object) {
|
|
663
|
+
switch (object.kind) {
|
|
664
|
+
case "table":
|
|
665
|
+
case "view":
|
|
666
|
+
case "materializedView":
|
|
667
|
+
return MANAGED_TABLES.has(object.identity.name);
|
|
668
|
+
case "column":
|
|
669
|
+
case "index":
|
|
670
|
+
case "constraint":
|
|
671
|
+
case "trigger":
|
|
672
|
+
return MANAGED_TABLES.has(object.identity.parent.name);
|
|
673
|
+
case "sequence":
|
|
674
|
+
return managedSequence(object.identity.name);
|
|
675
|
+
case "grant": {
|
|
676
|
+
const target = object.identity.object;
|
|
677
|
+
if (target.kind === "namespace")
|
|
678
|
+
return false;
|
|
679
|
+
if (target.kind === "sequence" || target.kind === "function") {
|
|
680
|
+
return managedSequence(target.name);
|
|
681
|
+
}
|
|
682
|
+
return MANAGED_TABLES.has(target.name);
|
|
683
|
+
}
|
|
684
|
+
default:
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function managedSequence(name) {
|
|
689
|
+
return name === "okm_meta" || name === "okm_history" || name === "okm_backfill" || name.startsWith("okm_meta_") || name.startsWith("okm_history_") || name.startsWith("okm_backfill_");
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/tooling/migrate/safe.ts
|
|
693
|
+
var ACCESS = "ACCESS EXCLUSIVE";
|
|
694
|
+
var SHARE_UPDATE = "SHARE UPDATE EXCLUSIVE";
|
|
695
|
+
var ROW = "ROW EXCLUSIVE";
|
|
696
|
+
var VOLATILE_BUILTIN = new Set([
|
|
697
|
+
"clock_timestamp",
|
|
698
|
+
"gen_random_uuid",
|
|
699
|
+
"nextval",
|
|
700
|
+
"random",
|
|
701
|
+
"random_normal",
|
|
702
|
+
"setval",
|
|
703
|
+
"timeofday",
|
|
704
|
+
"uuidv7"
|
|
705
|
+
]);
|
|
706
|
+
var STABLE_BUILTIN = new Set([
|
|
707
|
+
"current_catalog",
|
|
708
|
+
"current_date",
|
|
709
|
+
"current_role",
|
|
710
|
+
"current_schema",
|
|
711
|
+
"current_time",
|
|
712
|
+
"current_timestamp",
|
|
713
|
+
"current_user",
|
|
714
|
+
"localtime",
|
|
715
|
+
"localtimestamp",
|
|
716
|
+
"now",
|
|
717
|
+
"session_user",
|
|
718
|
+
"statement_timestamp",
|
|
719
|
+
"transaction_timestamp"
|
|
720
|
+
]);
|
|
721
|
+
function stepsForExistingTable(object, schema, catalogs, batchSize) {
|
|
722
|
+
if (object.kind === "index")
|
|
723
|
+
return [concurrentIndexStep(object, schema)];
|
|
724
|
+
if (object.kind === "constraint")
|
|
725
|
+
return constraintSteps(object, schema);
|
|
726
|
+
if (object.kind === "column" && volatileDefault(object.definition.defaultExpression, catalogs)) {
|
|
727
|
+
return volatileColumnSteps(object, schema, catalogs, batchSize);
|
|
728
|
+
}
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
function notNullSteps(column, schema, setKind, catalogs) {
|
|
732
|
+
const tableName = column.identity.parent.name;
|
|
733
|
+
const columnName = column.identity.name;
|
|
734
|
+
const table = qualify(schema, tableName);
|
|
735
|
+
const quotedColumn = quoteIdent(columnName);
|
|
736
|
+
const constraint = quoteIdent(notNullConstraintName(tableName, columnName, catalogs));
|
|
737
|
+
return [
|
|
738
|
+
{
|
|
739
|
+
sql: `alter table ${table} add constraint ${constraint} check (${quotedColumn} is not null) not valid`,
|
|
740
|
+
kind: "add-constraint",
|
|
741
|
+
action: "ddl",
|
|
742
|
+
lock: ACCESS,
|
|
743
|
+
transactional: true
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
sql: `alter table ${table} validate constraint ${constraint}`,
|
|
747
|
+
kind: "validate-constraint",
|
|
748
|
+
action: "ddl",
|
|
749
|
+
lock: SHARE_UPDATE,
|
|
750
|
+
transactional: true
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
sql: `alter table ${table} alter column ${quotedColumn} set not null`,
|
|
754
|
+
kind: setKind,
|
|
755
|
+
action: "ddl",
|
|
756
|
+
lock: ACCESS,
|
|
757
|
+
transactional: true
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
sql: `alter table ${table} drop constraint ${constraint}`,
|
|
761
|
+
kind: "drop-not-null-check",
|
|
762
|
+
action: "ddl",
|
|
763
|
+
lock: ACCESS,
|
|
764
|
+
transactional: true
|
|
765
|
+
}
|
|
766
|
+
];
|
|
767
|
+
}
|
|
768
|
+
function concurrentIndexSql(sql) {
|
|
769
|
+
const unique = "create unique index ";
|
|
770
|
+
const plain = "create index ";
|
|
771
|
+
if (sql.startsWith(unique))
|
|
772
|
+
return `create unique index concurrently ${sql.slice(unique.length)}`;
|
|
773
|
+
if (sql.startsWith(plain))
|
|
774
|
+
return `create index concurrently ${sql.slice(plain.length)}`;
|
|
775
|
+
return sql;
|
|
776
|
+
}
|
|
777
|
+
function concurrentDropIndexSql(sql) {
|
|
778
|
+
const prefix = "drop index ";
|
|
779
|
+
if (!sql.startsWith(prefix))
|
|
780
|
+
return sql;
|
|
781
|
+
return `drop index concurrently ${sql.slice(prefix.length)}`;
|
|
782
|
+
}
|
|
783
|
+
function volatileDefault(expression, catalogs) {
|
|
784
|
+
if (expression === undefined)
|
|
785
|
+
return false;
|
|
786
|
+
const known = catalogVolatility(catalogs);
|
|
787
|
+
for (const name of functionCalls(expression)) {
|
|
788
|
+
const lower = name.toLowerCase();
|
|
789
|
+
if (VOLATILE_BUILTIN.has(lower))
|
|
790
|
+
return true;
|
|
791
|
+
if (STABLE_BUILTIN.has(lower))
|
|
792
|
+
continue;
|
|
793
|
+
if (known.get(lower) === "volatile")
|
|
794
|
+
return true;
|
|
795
|
+
}
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
function concurrentIndexStep(object, schema) {
|
|
799
|
+
const plain = createObjectSql(object, schema) ?? "";
|
|
800
|
+
return {
|
|
801
|
+
sql: concurrentIndexSql(plain),
|
|
802
|
+
kind: "create-index",
|
|
803
|
+
action: "ddl",
|
|
804
|
+
lock: SHARE_UPDATE,
|
|
805
|
+
transactional: false
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
function constraintSteps(object, schema) {
|
|
809
|
+
const kind = object.definition.constraintKind;
|
|
810
|
+
if (kind === "unique" || kind === "primaryKey")
|
|
811
|
+
return usingIndexSteps(object, schema);
|
|
812
|
+
return notValidSteps(object, schema);
|
|
813
|
+
}
|
|
814
|
+
function notValidSteps(object, schema) {
|
|
815
|
+
const plain = createObjectSql(object, schema);
|
|
816
|
+
if (plain === undefined)
|
|
817
|
+
return [];
|
|
818
|
+
const table = qualify(schema, object.identity.parent.name);
|
|
819
|
+
const name = quoteIdent(object.identity.name);
|
|
820
|
+
const referenced = object.definition.references;
|
|
821
|
+
const referencedLock = referenced === undefined ? undefined : qualify(schema, referenced.parent.name);
|
|
822
|
+
return [
|
|
823
|
+
{
|
|
824
|
+
sql: `${plain} not valid`,
|
|
825
|
+
kind: "add-constraint",
|
|
826
|
+
action: "ddl",
|
|
827
|
+
lock: referencedLock === undefined ? ACCESS : `${ACCESS}; SHARE ROW EXCLUSIVE on ${referencedLock}`,
|
|
828
|
+
transactional: true
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
sql: `alter table ${table} validate constraint ${name}`,
|
|
832
|
+
kind: "validate-constraint",
|
|
833
|
+
action: "ddl",
|
|
834
|
+
lock: referencedLock === undefined ? SHARE_UPDATE : `${SHARE_UPDATE}; ROW SHARE on ${referencedLock}`,
|
|
835
|
+
transactional: true
|
|
836
|
+
}
|
|
837
|
+
];
|
|
838
|
+
}
|
|
839
|
+
function usingIndexSteps(object, schema) {
|
|
840
|
+
const table = qualify(schema, object.identity.parent.name);
|
|
841
|
+
const name = quoteIdent(object.identity.name);
|
|
842
|
+
const columns = object.definition.columns.map((column) => quoteIdent(column)).join(", ");
|
|
843
|
+
const nulls = object.definition.nullsNotDistinct ? " nulls not distinct" : "";
|
|
844
|
+
const verb = object.definition.constraintKind === "primaryKey" ? "primary key" : "unique";
|
|
845
|
+
const defer = object.definition.deferrable ? ` deferrable initially ${object.definition.initially}` : "";
|
|
846
|
+
return [
|
|
847
|
+
{
|
|
848
|
+
sql: `create unique index concurrently ${name} on ${table} (${columns})${nulls}`,
|
|
849
|
+
kind: "create-index",
|
|
850
|
+
action: "ddl",
|
|
851
|
+
lock: SHARE_UPDATE,
|
|
852
|
+
transactional: false
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
sql: `alter table ${table} add constraint ${name} ${verb} using index ${name}${defer}`,
|
|
856
|
+
kind: "add-constraint",
|
|
857
|
+
action: "ddl",
|
|
858
|
+
lock: ACCESS,
|
|
859
|
+
transactional: true
|
|
860
|
+
}
|
|
861
|
+
];
|
|
862
|
+
}
|
|
863
|
+
function volatileColumnSteps(column, schema, catalogs, batchSize) {
|
|
864
|
+
const tableName = column.identity.parent.name;
|
|
865
|
+
const table = qualify(schema, tableName);
|
|
866
|
+
const name = quoteIdent(column.identity.name);
|
|
867
|
+
const collate = column.definition.collation === undefined ? "" : ` collate ${quoteIdent(column.definition.collation)}`;
|
|
868
|
+
const expression = column.definition.defaultExpression ?? "";
|
|
869
|
+
const key = primaryKeyColumns(catalogs, tableName);
|
|
870
|
+
const backfill = {
|
|
871
|
+
table,
|
|
872
|
+
key: key.map((item) => item.quoted),
|
|
873
|
+
batch: batchSize
|
|
874
|
+
};
|
|
875
|
+
const steps = [
|
|
876
|
+
{
|
|
877
|
+
sql: `alter table ${table} add column ${name} ${column.definition.dataType}${collate}`,
|
|
878
|
+
kind: "add-column",
|
|
879
|
+
action: "ddl",
|
|
880
|
+
lock: ACCESS,
|
|
881
|
+
transactional: true
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
sql: `alter table ${table} alter column ${name} set default ${expression}`,
|
|
885
|
+
kind: "set-default",
|
|
886
|
+
action: "ddl",
|
|
887
|
+
lock: ACCESS,
|
|
888
|
+
transactional: true
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
sql: `update ${table} set ${name} = ${expression} where ${name} is null and ${keyRangePredicate(key)}`,
|
|
892
|
+
kind: "backfill-expand",
|
|
893
|
+
action: "backfill",
|
|
894
|
+
lock: ROW,
|
|
895
|
+
transactional: false,
|
|
896
|
+
backfill
|
|
897
|
+
}
|
|
898
|
+
];
|
|
899
|
+
if (!column.definition.nullable) {
|
|
900
|
+
steps.push(...notNullSteps(column, schema, "add-column", catalogs));
|
|
901
|
+
}
|
|
902
|
+
return steps;
|
|
903
|
+
}
|
|
904
|
+
function notNullConstraintName(table, column, catalogs) {
|
|
905
|
+
const taken = new Set;
|
|
906
|
+
for (const source of catalogs) {
|
|
907
|
+
for (const object of source.objects) {
|
|
908
|
+
if (object.kind === "constraint" || object.kind === "index")
|
|
909
|
+
taken.add(object.identity.name);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
const base = fitIdentifier2(`${table}_${column}_notnull`);
|
|
913
|
+
if (!taken.has(base))
|
|
914
|
+
return base;
|
|
915
|
+
return fitIdentifier2(`${table}_${column}_notnull_okm`);
|
|
916
|
+
}
|
|
917
|
+
function catalogVolatility(catalogs) {
|
|
918
|
+
const map = new Map;
|
|
919
|
+
for (const source of catalogs) {
|
|
920
|
+
for (const object of source.objects) {
|
|
921
|
+
if (object.kind !== "function")
|
|
922
|
+
continue;
|
|
923
|
+
const name = object.identity.name.toLowerCase();
|
|
924
|
+
const next = object.definition.volatility;
|
|
925
|
+
const current = map.get(name);
|
|
926
|
+
if (current === "volatile" || next === "volatile")
|
|
927
|
+
map.set(name, "volatile");
|
|
928
|
+
else if (current === undefined)
|
|
929
|
+
map.set(name, next);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return map;
|
|
933
|
+
}
|
|
934
|
+
function functionCalls(expression) {
|
|
935
|
+
const names = [];
|
|
936
|
+
let index = 0;
|
|
937
|
+
while (index < expression.length) {
|
|
938
|
+
const char = expression[index];
|
|
939
|
+
if (char === "'") {
|
|
940
|
+
index += 1;
|
|
941
|
+
while (index < expression.length) {
|
|
942
|
+
if (expression[index] === "'" && expression[index + 1] === "'") {
|
|
943
|
+
index += 2;
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
if (expression[index] === "'") {
|
|
947
|
+
index += 1;
|
|
948
|
+
break;
|
|
949
|
+
}
|
|
950
|
+
index += 1;
|
|
951
|
+
}
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
if (char !== undefined && /[A-Za-z_]/.test(char)) {
|
|
955
|
+
const start = index;
|
|
956
|
+
index += 1;
|
|
957
|
+
while (index < expression.length && /[\w$]/.test(expression[index] ?? ""))
|
|
958
|
+
index += 1;
|
|
959
|
+
let name = expression.slice(start, index);
|
|
960
|
+
if (expression[index] === ".") {
|
|
961
|
+
index += 1;
|
|
962
|
+
const qualifier = index;
|
|
963
|
+
while (index < expression.length && /[\w$]/.test(expression[index] ?? ""))
|
|
964
|
+
index += 1;
|
|
965
|
+
name = expression.slice(qualifier, index);
|
|
966
|
+
}
|
|
967
|
+
while (expression[index] === " ")
|
|
968
|
+
index += 1;
|
|
969
|
+
if (expression[index] === "(")
|
|
970
|
+
names.push(name);
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
index += 1;
|
|
974
|
+
}
|
|
975
|
+
return names;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// src/tooling/migrate/values.ts
|
|
979
|
+
function parseReplace(flag) {
|
|
980
|
+
const eq = flag.indexOf("=");
|
|
981
|
+
if (eq <= 0) {
|
|
982
|
+
throw new OkmError2("OKM1541", `--replace ${flag} must be table.column.old=new.`, {
|
|
983
|
+
fix: {
|
|
984
|
+
summary: "Pass --replace <table>.<column>.<old>=<new>, or =null on a nullable column."
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
const left = flag.slice(0, eq);
|
|
989
|
+
const raw = flag.slice(eq + 1);
|
|
990
|
+
const first = left.indexOf(".");
|
|
991
|
+
const second = first < 0 ? -1 : left.indexOf(".", first + 1);
|
|
992
|
+
if (first <= 0 || second <= first + 1 || second === left.length - 1) {
|
|
993
|
+
throw new OkmError2("OKM1541", `--replace ${flag} must be table.column.old=new.`, {
|
|
994
|
+
fix: {
|
|
995
|
+
summary: "Pass --replace <table>.<column>.<old>=<new>, or =null on a nullable column."
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
const table = left.slice(0, first);
|
|
1000
|
+
const column = left.slice(first + 1, second);
|
|
1001
|
+
const from = left.slice(second + 1);
|
|
1002
|
+
return { table, column, from, to: raw === "null" ? null : raw };
|
|
1003
|
+
}
|
|
1004
|
+
function assertNoChains(replacements) {
|
|
1005
|
+
const removed = new Set(replacements.map((item) => pair(item.table, item.column, item.from)));
|
|
1006
|
+
for (const item of replacements) {
|
|
1007
|
+
if (item.to !== null && removed.has(pair(item.table, item.column, item.to))) {
|
|
1008
|
+
const flag = `--replace ${item.table}.${item.column}.${item.from}=${item.to}`;
|
|
1009
|
+
throw new OkmError2("OKM1541", `${flag} chains through another removed value.`, {
|
|
1010
|
+
fix: { summary: "The replacement must be in the new list. No chains." }
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function pair(table, column, value) {
|
|
1016
|
+
return `${table}\x00${column}\x00${value}`;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/tooling/migrate/plan.ts
|
|
1020
|
+
var ACCESS2 = "ACCESS EXCLUSIVE";
|
|
1021
|
+
var SHARE = "SHARE";
|
|
1022
|
+
var SHARE_UPDATE2 = "SHARE UPDATE EXCLUSIVE";
|
|
1023
|
+
var ROW2 = "ROW EXCLUSIVE";
|
|
1024
|
+
function planMigration(input) {
|
|
1025
|
+
const request = {
|
|
1026
|
+
...input,
|
|
1027
|
+
before: omitManagedObjects(input.before),
|
|
1028
|
+
after: omitManagedObjects(input.after)
|
|
1029
|
+
};
|
|
1030
|
+
const schema = request.schema ?? "public";
|
|
1031
|
+
const batchSize = resolveBatchSize(request.batchSize);
|
|
1032
|
+
const renames = request.renames ?? [];
|
|
1033
|
+
const replacements = request.replacements ?? [];
|
|
1034
|
+
assertNoChains(replacements);
|
|
1035
|
+
const renamed = applyRenames(request.before, renames);
|
|
1036
|
+
const beforeList = index2(renamed);
|
|
1037
|
+
const afterList = index2(request.after);
|
|
1038
|
+
const beforeBy = new Map(beforeList.map((item) => [item.key, item.object]));
|
|
1039
|
+
const afterBy = new Map(afterList.map((item) => [item.key, item.object]));
|
|
1040
|
+
assertUnambiguous(beforeBy, afterBy);
|
|
1041
|
+
const dropKeys = new Set;
|
|
1042
|
+
const createKeys = new Set;
|
|
1043
|
+
const replaces = [];
|
|
1044
|
+
const alters = [];
|
|
1045
|
+
for (const item of beforeList) {
|
|
1046
|
+
if (!afterBy.has(item.key)) {
|
|
1047
|
+
if (item.object.kind === "role")
|
|
1048
|
+
continue;
|
|
1049
|
+
dropKeys.add(item.key);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
for (const item of afterList) {
|
|
1053
|
+
if (!beforeBy.has(item.key))
|
|
1054
|
+
createKeys.add(item.key);
|
|
1055
|
+
}
|
|
1056
|
+
for (const item of afterList) {
|
|
1057
|
+
const previous = beforeBy.get(item.key);
|
|
1058
|
+
if (previous === undefined || sameDefinition(previous, item.object))
|
|
1059
|
+
continue;
|
|
1060
|
+
if (previous.kind === "extension" && item.object.kind === "extension")
|
|
1061
|
+
continue;
|
|
1062
|
+
if (previous.kind === "role" && item.object.kind === "role")
|
|
1063
|
+
continue;
|
|
1064
|
+
if (previous.kind === "function" && item.object.kind === "function" && canonicalTypeName(previous.definition.returns) === canonicalTypeName(item.object.definition.returns)) {
|
|
1065
|
+
replaces.push({ kind: "function", before: previous, after: item.object });
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
if (previous.kind === "view" && item.object.kind === "view") {
|
|
1069
|
+
if (columnsAppended(previous.definition.columns, item.object.definition.columns)) {
|
|
1070
|
+
replaces.push({ kind: "view", before: previous, after: item.object });
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (previous.kind === "type" && item.object.kind === "type") {
|
|
1075
|
+
refuseDomainBaseChange(previous, item.object);
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
if (previous.kind === "column" && item.object.kind === "column" && columnAlterable(previous, item.object)) {
|
|
1079
|
+
alters.push({ before: previous, after: item.object });
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
dropKeys.add(item.key);
|
|
1083
|
+
createKeys.add(item.key);
|
|
1084
|
+
}
|
|
1085
|
+
const keptNames = pairSameShape(beforeBy, afterBy, dropKeys, createKeys);
|
|
1086
|
+
const picklists = picklistChanges(beforeBy, afterBy, request.after);
|
|
1087
|
+
const enums = enumEdits(beforeBy, afterBy, schema);
|
|
1088
|
+
requireReplacements(picklists, enums, replacements);
|
|
1089
|
+
recreateDependents(beforeList, afterList, alters, dropKeys, createKeys, picklists);
|
|
1090
|
+
recreateRoutineDependents(afterList, dropKeys, createKeys);
|
|
1091
|
+
recreateViewDependents(afterList, dropKeys, createKeys);
|
|
1092
|
+
refuseRoutineDrops(beforeBy, afterBy, dropKeys);
|
|
1093
|
+
omitOwnedSequences(createKeys, afterBy, request.after);
|
|
1094
|
+
omitOwnedSequences(dropKeys, beforeBy, renamed);
|
|
1095
|
+
const recreated = new Set;
|
|
1096
|
+
for (const key of createKeys) {
|
|
1097
|
+
if (dropKeys.has(key))
|
|
1098
|
+
recreated.add(key);
|
|
1099
|
+
}
|
|
1100
|
+
const privileges = privilegeSql(renamed.objects, request.after.objects, schema, recreated);
|
|
1101
|
+
const steps = [];
|
|
1102
|
+
steps.push(...renameSteps(request.before, renames, schema));
|
|
1103
|
+
for (const item of privileges.revoke)
|
|
1104
|
+
steps.push(step(item.sql, item.kind, "ddl", ACCESS2));
|
|
1105
|
+
steps.push(...keptNames.map((item) => renameShapeStep(item, schema)));
|
|
1106
|
+
steps.push(...enumAddSteps(enums));
|
|
1107
|
+
steps.push(...domainCheckSteps(beforeBy, afterBy, schema));
|
|
1108
|
+
steps.push(...expandBackfills(picklists, enums, replacements, schema, [renamed, request.after], batchSize));
|
|
1109
|
+
const droppedTables = new Set([...dropKeys].map((key) => beforeBy.get(key)).filter((object) => object?.kind === "table").map((object) => object.identity.name));
|
|
1110
|
+
const deferred = new Set(picklists.map((change) => identityKey2(change.before.identity)));
|
|
1111
|
+
const typeDrops = [];
|
|
1112
|
+
const extensionDrops = [];
|
|
1113
|
+
const reverse = [...creationOrder2(renamed)].reverse();
|
|
1114
|
+
const emitDrop = (object) => {
|
|
1115
|
+
const key = identityKey2(object.identity);
|
|
1116
|
+
if (!dropKeys.has(key) || deferred.has(key) || covered(object, droppedTables))
|
|
1117
|
+
return;
|
|
1118
|
+
const sql = dropObjectSql(object, schema);
|
|
1119
|
+
const kind = dropKind(object);
|
|
1120
|
+
if (sql === undefined || kind === undefined)
|
|
1121
|
+
return;
|
|
1122
|
+
if (object.kind === "index" && hasTable(renamed, object.identity.parent.name)) {
|
|
1123
|
+
steps.push({
|
|
1124
|
+
...step(concurrentDropIndexSql(sql), "drop-index", "ddl", SHARE_UPDATE2, false, [
|
|
1125
|
+
object.identity.parent.name
|
|
1126
|
+
]),
|
|
1127
|
+
safeRewrite: true
|
|
1128
|
+
});
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
steps.push(step(sql, kind, "ddl", ACCESS2));
|
|
1132
|
+
};
|
|
1133
|
+
for (const object of reverse)
|
|
1134
|
+
if (object.kind === "trigger")
|
|
1135
|
+
emitDrop(object);
|
|
1136
|
+
for (const object of reverse)
|
|
1137
|
+
if (object.kind === "function")
|
|
1138
|
+
emitDrop(object);
|
|
1139
|
+
for (const object of reverse)
|
|
1140
|
+
if (ownedByView(object))
|
|
1141
|
+
emitDrop(object);
|
|
1142
|
+
for (const object of reverse) {
|
|
1143
|
+
if (object.kind === "view" || object.kind === "materializedView")
|
|
1144
|
+
emitDrop(object);
|
|
1145
|
+
}
|
|
1146
|
+
for (const object of reverse) {
|
|
1147
|
+
if (object.kind === "trigger" || object.kind === "function" || object.kind === "view" || object.kind === "materializedView" || ownedByView(object)) {
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
const key = identityKey2(object.identity);
|
|
1151
|
+
if (!dropKeys.has(key) || deferred.has(key) || covered(object, droppedTables))
|
|
1152
|
+
continue;
|
|
1153
|
+
if (object.kind === "type") {
|
|
1154
|
+
typeDrops.push(object);
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
if (object.kind === "extension") {
|
|
1158
|
+
extensionDrops.push(object);
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
emitDrop(object);
|
|
1162
|
+
}
|
|
1163
|
+
for (const object of typeDrops)
|
|
1164
|
+
pushDrop(steps, object, schema);
|
|
1165
|
+
for (const object of extensionDrops)
|
|
1166
|
+
pushDrop(steps, object, schema);
|
|
1167
|
+
steps.push(...extensionAlterSteps(beforeBy, afterBy, dropKeys));
|
|
1168
|
+
for (const change of alters) {
|
|
1169
|
+
const aligned = alignedColumnType(change.before, change.after);
|
|
1170
|
+
const statements = alterColumnSql(aligned, change.after, schema);
|
|
1171
|
+
const kinds = columnAlterKinds(aligned, change.after);
|
|
1172
|
+
for (let index = 0;index < statements.length; index += 1) {
|
|
1173
|
+
const sql = statements[index];
|
|
1174
|
+
const kind = kinds[index];
|
|
1175
|
+
if (sql === undefined || kind === undefined)
|
|
1176
|
+
continue;
|
|
1177
|
+
if (kind === "set-not-null") {
|
|
1178
|
+
pushSafe(steps, notNullSteps(change.after, schema, "set-not-null", [renamed, request.after]));
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1181
|
+
steps.push(step(sql, kind, "ddl", ACCESS2));
|
|
1182
|
+
}
|
|
1183
|
+
const identitySql = identityChangeSql(change.before, change.after, schema);
|
|
1184
|
+
const identity = identityKind(change.before, change.after);
|
|
1185
|
+
if (identitySql !== undefined && identity !== undefined) {
|
|
1186
|
+
steps.push(step(identitySql, identity, "ddl", ACCESS2));
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
const createdTables = new Set([...createKeys].map((key) => afterBy.get(key)).filter((object) => object?.kind === "table").map((object) => object.identity.name));
|
|
1190
|
+
const emitted = new Set;
|
|
1191
|
+
for (const item of privileges.prepare)
|
|
1192
|
+
steps.push(step(item.sql, item.kind, "ddl", ACCESS2));
|
|
1193
|
+
for (const object of creationOrder2(request.after)) {
|
|
1194
|
+
if (object.kind !== "extension")
|
|
1195
|
+
continue;
|
|
1196
|
+
const key = identityKey2(object.identity);
|
|
1197
|
+
if (!createKeys.has(key) || emitted.has(key))
|
|
1198
|
+
continue;
|
|
1199
|
+
emitted.add(key);
|
|
1200
|
+
const sql = createObjectSql(object, schema);
|
|
1201
|
+
const kind = createKind(object, false);
|
|
1202
|
+
if (sql === undefined || kind === undefined)
|
|
1203
|
+
continue;
|
|
1204
|
+
steps.push(step(sql, kind, "ddl", ACCESS2));
|
|
1205
|
+
}
|
|
1206
|
+
for (const object of creationOrder2(request.after)) {
|
|
1207
|
+
if (object.kind !== "type")
|
|
1208
|
+
continue;
|
|
1209
|
+
const key = identityKey2(object.identity);
|
|
1210
|
+
if (!createKeys.has(key) || emitted.has(key))
|
|
1211
|
+
continue;
|
|
1212
|
+
emitted.add(key);
|
|
1213
|
+
const sql = createObjectSql(object, schema);
|
|
1214
|
+
const kind = createKind(object, false);
|
|
1215
|
+
if (sql === undefined || kind === undefined)
|
|
1216
|
+
continue;
|
|
1217
|
+
steps.push(step(sql, kind, "ddl", ACCESS2));
|
|
1218
|
+
}
|
|
1219
|
+
for (const object of creationOrder2(request.after)) {
|
|
1220
|
+
if (object.kind === "type" || object.kind === "extension" || object.kind === "function" || object.kind === "trigger" || object.kind === "view" || object.kind === "materializedView" || ownedByView(object)) {
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
const key = identityKey2(object.identity);
|
|
1224
|
+
if (deferred.has(key))
|
|
1225
|
+
continue;
|
|
1226
|
+
const parent = anchoredParent(object);
|
|
1227
|
+
const tableIsNew = parent !== undefined && createdTables.has(parent);
|
|
1228
|
+
if (tableIsNew && folded(object, request.after))
|
|
1229
|
+
continue;
|
|
1230
|
+
const wanted = createKeys.has(key) || tableIsNew && !folded(object, request.after);
|
|
1231
|
+
if (!wanted || emitted.has(key))
|
|
1232
|
+
continue;
|
|
1233
|
+
emitted.add(key);
|
|
1234
|
+
if (object.kind === "table") {
|
|
1235
|
+
steps.push(step(createTableSql(object, request.after, schema), "create-table", "ddl", ACCESS2));
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
const sql = createObjectSql(object, schema);
|
|
1239
|
+
const kind = createKind(object, tableIsNew);
|
|
1240
|
+
if (sql === undefined || kind === undefined)
|
|
1241
|
+
continue;
|
|
1242
|
+
if (!tableIsNew && parent !== undefined && hasTable(renamed, parent)) {
|
|
1243
|
+
const safe = stepsForExistingTable(object, schema, [renamed, request.after], batchSize);
|
|
1244
|
+
if (safe !== undefined) {
|
|
1245
|
+
pushSafe(steps, safe);
|
|
1246
|
+
continue;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
steps.push(step(sql, kind, "ddl", object.kind === "index" ? SHARE : ACCESS2));
|
|
1250
|
+
}
|
|
1251
|
+
for (const change of replaces) {
|
|
1252
|
+
if (sameDefinition(change.before, change.after))
|
|
1253
|
+
continue;
|
|
1254
|
+
const key = identityKey2(change.after.identity);
|
|
1255
|
+
if (dropKeys.has(key) && createKeys.has(key))
|
|
1256
|
+
continue;
|
|
1257
|
+
const sql = change.kind === "function" ? functionSql(change.after, schema, true) : viewSql(change.after, schema, true);
|
|
1258
|
+
const kind = change.kind === "function" ? "replace-function" : "replace-view";
|
|
1259
|
+
steps.push({
|
|
1260
|
+
sql,
|
|
1261
|
+
kind,
|
|
1262
|
+
class: classOf(kind),
|
|
1263
|
+
action: "ddl",
|
|
1264
|
+
lock: ACCESS2,
|
|
1265
|
+
transactional: true,
|
|
1266
|
+
behavior: "change"
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
for (const kind of ["function", "trigger", "view", "materializedView"]) {
|
|
1270
|
+
for (const object of creationOrder2(request.after)) {
|
|
1271
|
+
if (object.kind !== kind)
|
|
1272
|
+
continue;
|
|
1273
|
+
const key = identityKey2(object.identity);
|
|
1274
|
+
if (!createKeys.has(key) || emitted.has(key))
|
|
1275
|
+
continue;
|
|
1276
|
+
emitted.add(key);
|
|
1277
|
+
const sql = createObjectSql(object, schema);
|
|
1278
|
+
const created = createKind(object, false);
|
|
1279
|
+
if (sql === undefined || created === undefined)
|
|
1280
|
+
continue;
|
|
1281
|
+
steps.push(step(sql, created, "ddl", ACCESS2));
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
for (const object of creationOrder2(request.after)) {
|
|
1285
|
+
if (!ownedByView(object))
|
|
1286
|
+
continue;
|
|
1287
|
+
const key = identityKey2(object.identity);
|
|
1288
|
+
if (!createKeys.has(key) || emitted.has(key))
|
|
1289
|
+
continue;
|
|
1290
|
+
emitted.add(key);
|
|
1291
|
+
const sql = createObjectSql(object, schema);
|
|
1292
|
+
const kind = createKind(object, false);
|
|
1293
|
+
if (sql === undefined || kind === undefined)
|
|
1294
|
+
continue;
|
|
1295
|
+
steps.push(step(sql, kind, "ddl", SHARE));
|
|
1296
|
+
}
|
|
1297
|
+
for (const object of creationOrder2(request.after)) {
|
|
1298
|
+
if (object.kind !== "materializedView")
|
|
1299
|
+
continue;
|
|
1300
|
+
const key = identityKey2(object.identity);
|
|
1301
|
+
if (!createKeys.has(key))
|
|
1302
|
+
continue;
|
|
1303
|
+
assertConcurrentRefresh(object, request.after);
|
|
1304
|
+
steps.push(step(refreshMaterializedViewSql(object, schema, false), "refresh-matview", "backfill", ACCESS2));
|
|
1305
|
+
}
|
|
1306
|
+
for (const item of privileges.grant)
|
|
1307
|
+
steps.push(step(item.sql, item.kind, "ddl", ACCESS2));
|
|
1308
|
+
steps.push(...contractSwaps(picklists, enums, replacements, schema, [renamed, request.after], batchSize));
|
|
1309
|
+
return {
|
|
1310
|
+
name: request.name ?? "migration",
|
|
1311
|
+
class: overall(steps),
|
|
1312
|
+
steps
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
function formatPlan(plan, lockText) {
|
|
1316
|
+
const lines = [`-- class: ${plan.class}`, `-- name: ${plan.name}`, ""];
|
|
1317
|
+
if (plan.steps.length === 0) {
|
|
1318
|
+
lines.push("-- no steps");
|
|
1319
|
+
lines.push("");
|
|
1320
|
+
return lines.join(`
|
|
1321
|
+
`);
|
|
1322
|
+
}
|
|
1323
|
+
for (const item of plan.steps) {
|
|
1324
|
+
lines.push(`-- class: ${item.class}`);
|
|
1325
|
+
if (item.kind !== undefined)
|
|
1326
|
+
lines.push(`-- kind: ${item.kind}`);
|
|
1327
|
+
lines.push(`-- action: ${item.action}`);
|
|
1328
|
+
lines.push(`-- lock: ${lockText === undefined ? item.lock : lockText(item)}`);
|
|
1329
|
+
if (item.backfill !== undefined)
|
|
1330
|
+
lines.push(formatBackfillHeader(item.backfill));
|
|
1331
|
+
if (!item.transactional)
|
|
1332
|
+
lines.push("-- transactional: false");
|
|
1333
|
+
if (item.path !== undefined)
|
|
1334
|
+
lines.push(`-- path: ${item.path}`);
|
|
1335
|
+
if (item.behavior === "change")
|
|
1336
|
+
lines.push("-- behavior: change");
|
|
1337
|
+
for (const allow of item.allows ?? []) {
|
|
1338
|
+
lines.push(`-- okm-allow ${allow.code}: ${allow.reason}`);
|
|
1339
|
+
}
|
|
1340
|
+
lines.push(`${item.sql};`);
|
|
1341
|
+
lines.push("");
|
|
1342
|
+
}
|
|
1343
|
+
return lines.join(`
|
|
1344
|
+
`);
|
|
1345
|
+
}
|
|
1346
|
+
function parsePlan(text) {
|
|
1347
|
+
const lines = text.split(`
|
|
1348
|
+
`);
|
|
1349
|
+
let name = "migration";
|
|
1350
|
+
let planClass = "expand";
|
|
1351
|
+
let index = 0;
|
|
1352
|
+
while (index < lines.length && lines[index] !== "") {
|
|
1353
|
+
const line = lines[index] ?? "";
|
|
1354
|
+
if (line.startsWith("-- name: "))
|
|
1355
|
+
name = line.slice("-- name: ".length);
|
|
1356
|
+
if (line.startsWith("-- class: "))
|
|
1357
|
+
planClass = readClass(line.slice("-- class: ".length));
|
|
1358
|
+
index += 1;
|
|
1359
|
+
}
|
|
1360
|
+
const steps = [];
|
|
1361
|
+
while (index < lines.length) {
|
|
1362
|
+
if ((lines[index] ?? "").trim() === "") {
|
|
1363
|
+
index += 1;
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
if (lines[index] === "-- no steps")
|
|
1367
|
+
break;
|
|
1368
|
+
let stepClass = "expand";
|
|
1369
|
+
let sawClass = false;
|
|
1370
|
+
let stepKind;
|
|
1371
|
+
let action = "ddl";
|
|
1372
|
+
let lock = "";
|
|
1373
|
+
let transactional = true;
|
|
1374
|
+
let backfill;
|
|
1375
|
+
let path;
|
|
1376
|
+
let behavior;
|
|
1377
|
+
const allows = [];
|
|
1378
|
+
const sql = [];
|
|
1379
|
+
while (index < lines.length && (lines[index] ?? "") !== "") {
|
|
1380
|
+
const line = lines[index] ?? "";
|
|
1381
|
+
index += 1;
|
|
1382
|
+
if (line.startsWith("-- class: ")) {
|
|
1383
|
+
stepClass = readClass(line.slice("-- class: ".length));
|
|
1384
|
+
sawClass = true;
|
|
1385
|
+
} else if (line.startsWith("-- kind: ")) {
|
|
1386
|
+
const kind = line.slice("-- kind: ".length);
|
|
1387
|
+
if (isStepKind(kind))
|
|
1388
|
+
stepKind = kind;
|
|
1389
|
+
} else if (line.startsWith("-- action: "))
|
|
1390
|
+
action = line.endsWith("backfill") ? "backfill" : "ddl";
|
|
1391
|
+
else if (line.startsWith("-- lock: "))
|
|
1392
|
+
lock = line.slice("-- lock: ".length);
|
|
1393
|
+
else if (line.startsWith("-- backfill "))
|
|
1394
|
+
backfill = parseBackfillHeader(line);
|
|
1395
|
+
else if (line === "-- transactional: false")
|
|
1396
|
+
transactional = false;
|
|
1397
|
+
else if (line === "-- path: unverified")
|
|
1398
|
+
path = "unverified";
|
|
1399
|
+
else if (line === "-- behavior: change")
|
|
1400
|
+
behavior = "change";
|
|
1401
|
+
else if (line.startsWith("-- okm-allow"))
|
|
1402
|
+
allows.push(readAllow(line));
|
|
1403
|
+
else if (!line.startsWith("--"))
|
|
1404
|
+
sql.push(line);
|
|
1405
|
+
}
|
|
1406
|
+
const statement = sql.join(`
|
|
1407
|
+
`).replace(/;\s*$/, "");
|
|
1408
|
+
if (statement.length > 0) {
|
|
1409
|
+
const kind = sawClass ? stepKind : "raw-sql";
|
|
1410
|
+
const classification = sawClass ? stepClass : "unclassified";
|
|
1411
|
+
steps.push({
|
|
1412
|
+
sql: statement,
|
|
1413
|
+
class: classification,
|
|
1414
|
+
...kind !== undefined ? { kind } : {},
|
|
1415
|
+
action,
|
|
1416
|
+
lock,
|
|
1417
|
+
transactional,
|
|
1418
|
+
...backfill !== undefined ? { backfill } : {},
|
|
1419
|
+
...path !== undefined ? { path } : {},
|
|
1420
|
+
...behavior !== undefined ? { behavior } : {},
|
|
1421
|
+
...allows.length > 0 ? { allows } : {}
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return { name, class: steps.length === 0 ? planClass : overall(steps), steps };
|
|
1426
|
+
}
|
|
1427
|
+
var ALLOW = /^-- okm-allow\s+(OKM\d+)\s*(?::\s*(.*))?$/;
|
|
1428
|
+
function readAllow(line) {
|
|
1429
|
+
const match = ALLOW.exec(line);
|
|
1430
|
+
if (match === null)
|
|
1431
|
+
return { code: "", reason: "" };
|
|
1432
|
+
return { code: match[1] ?? "", reason: (match[2] ?? "").trim() };
|
|
1433
|
+
}
|
|
1434
|
+
function readClass(value) {
|
|
1435
|
+
if (value === "contract" || value === "unclassified")
|
|
1436
|
+
return value;
|
|
1437
|
+
return "expand";
|
|
1438
|
+
}
|
|
1439
|
+
function staleRenames(previous, renames) {
|
|
1440
|
+
if (previous === undefined)
|
|
1441
|
+
return [];
|
|
1442
|
+
const messages = [];
|
|
1443
|
+
for (const rename of renames) {
|
|
1444
|
+
if (rename.kind === "table") {
|
|
1445
|
+
if (!hasTable(previous, rename.from)) {
|
|
1446
|
+
messages.push(`Table renamedFrom ${rename.from} is not in the previous snapshot.`);
|
|
1447
|
+
}
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
const tableRename = renames.find((item) => item.kind === "table" && item.to === rename.table);
|
|
1451
|
+
const previousTable = tableRename?.kind === "table" ? tableRename.from : rename.table;
|
|
1452
|
+
if (!hasColumn(previous, previousTable, rename.from)) {
|
|
1453
|
+
messages.push(`Column ${rename.table}.${rename.from} renamedFrom is not in the previous snapshot.`);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
return messages;
|
|
1457
|
+
}
|
|
1458
|
+
function applyRenames(source, renames) {
|
|
1459
|
+
let current = source;
|
|
1460
|
+
const namespace = staticNamespace2("public");
|
|
1461
|
+
for (const rename of renames) {
|
|
1462
|
+
if (rename.kind !== "table")
|
|
1463
|
+
continue;
|
|
1464
|
+
if (!hasTable(current, rename.from))
|
|
1465
|
+
continue;
|
|
1466
|
+
current = renameTable2(current, { namespace, from: rename.from, to: rename.to });
|
|
1467
|
+
}
|
|
1468
|
+
for (const rename of renames) {
|
|
1469
|
+
if (rename.kind !== "column")
|
|
1470
|
+
continue;
|
|
1471
|
+
const parent = { namespace, name: rename.table };
|
|
1472
|
+
if (!hasColumn(current, rename.table, rename.from))
|
|
1473
|
+
continue;
|
|
1474
|
+
current = renameColumn2(current, { parent, from: rename.from, to: rename.to });
|
|
1475
|
+
}
|
|
1476
|
+
return current;
|
|
1477
|
+
}
|
|
1478
|
+
function renameSteps(source, renames, schema) {
|
|
1479
|
+
const steps = [];
|
|
1480
|
+
const tables = new Map;
|
|
1481
|
+
for (const rename of renames) {
|
|
1482
|
+
if (rename.kind !== "table" || !hasTable(source, rename.from))
|
|
1483
|
+
continue;
|
|
1484
|
+
tables.set(rename.to, rename.from);
|
|
1485
|
+
steps.push(step(`alter table ${qualify(schema, rename.from)} rename to ${quoteIdent(rename.to)}`, "rename-table", "ddl", ACCESS2));
|
|
1486
|
+
steps.push(...sequenceRenames(source, rename, schema));
|
|
1487
|
+
}
|
|
1488
|
+
for (const rename of renames) {
|
|
1489
|
+
if (rename.kind !== "column")
|
|
1490
|
+
continue;
|
|
1491
|
+
const previousTable = tables.get(rename.table) ?? rename.table;
|
|
1492
|
+
if (!hasColumn(source, previousTable, rename.from))
|
|
1493
|
+
continue;
|
|
1494
|
+
steps.push(step(`alter table ${qualify(schema, rename.table)} rename column ${quoteIdent(rename.from)} to ${quoteIdent(rename.to)}`, "rename-column", "ddl", ACCESS2));
|
|
1495
|
+
}
|
|
1496
|
+
return steps;
|
|
1497
|
+
}
|
|
1498
|
+
function sequenceRenames(source, rename, schema) {
|
|
1499
|
+
const steps = [];
|
|
1500
|
+
for (const object of source.objects) {
|
|
1501
|
+
if (object.kind !== "column" || object.definition.identity === undefined)
|
|
1502
|
+
continue;
|
|
1503
|
+
if (object.identity.parent.name !== rename.from)
|
|
1504
|
+
continue;
|
|
1505
|
+
const fromName = fitIdentifier2(`${rename.from}_${object.identity.name}_seq`);
|
|
1506
|
+
const toName = fitIdentifier2(`${rename.to}_${object.identity.name}_seq`);
|
|
1507
|
+
if (fromName === toName)
|
|
1508
|
+
continue;
|
|
1509
|
+
const owned = source.objects.some((item) => item.kind === "sequence" && item.identity.name === fromName);
|
|
1510
|
+
if (!owned)
|
|
1511
|
+
continue;
|
|
1512
|
+
steps.push(step(`alter sequence ${qualify(schema, fromName)} rename to ${quoteIdent(toName)}`, "rename-sequence", "ddl", ACCESS2));
|
|
1513
|
+
}
|
|
1514
|
+
return steps;
|
|
1515
|
+
}
|
|
1516
|
+
function pairSameShape(beforeBy, afterBy, dropKeys, createKeys) {
|
|
1517
|
+
const dropped = [...dropKeys].map((key) => beforeBy.get(key)).filter((object) => object !== undefined);
|
|
1518
|
+
const created = [...createKeys].map((key) => afterBy.get(key)).filter((object) => object !== undefined);
|
|
1519
|
+
const pairs = [];
|
|
1520
|
+
const used = new Set;
|
|
1521
|
+
for (const left of dropped) {
|
|
1522
|
+
if (left.kind !== "index" && left.kind !== "constraint")
|
|
1523
|
+
continue;
|
|
1524
|
+
const leftKey = identityKey2(left.identity);
|
|
1525
|
+
const hits = created.filter((right) => {
|
|
1526
|
+
const key = identityKey2(right.identity);
|
|
1527
|
+
return key !== leftKey && !used.has(key) && sameShape(left, right);
|
|
1528
|
+
});
|
|
1529
|
+
const right = hits.length === 1 ? hits[0] : undefined;
|
|
1530
|
+
if (right === undefined)
|
|
1531
|
+
continue;
|
|
1532
|
+
const rivals = dropped.filter((item) => sameShape(item, right));
|
|
1533
|
+
if (rivals.length !== 1)
|
|
1534
|
+
continue;
|
|
1535
|
+
const rightKey = identityKey2(right.identity);
|
|
1536
|
+
used.add(rightKey);
|
|
1537
|
+
dropKeys.delete(identityKey2(left.identity));
|
|
1538
|
+
createKeys.delete(rightKey);
|
|
1539
|
+
if (anchoredName(left) !== anchoredName(right))
|
|
1540
|
+
pairs.push({ from: left, to: right });
|
|
1541
|
+
}
|
|
1542
|
+
return pairs;
|
|
1543
|
+
}
|
|
1544
|
+
function sameShape(left, right) {
|
|
1545
|
+
if (left.kind !== right.kind)
|
|
1546
|
+
return false;
|
|
1547
|
+
if (left.kind !== "index" && left.kind !== "constraint" || right.kind !== "index" && right.kind !== "constraint") {
|
|
1548
|
+
return false;
|
|
1549
|
+
}
|
|
1550
|
+
if (left.identity.parent.name !== right.identity.parent.name)
|
|
1551
|
+
return false;
|
|
1552
|
+
return stable(withoutNameKey(left.definition)) === stable(withoutNameKey(right.definition));
|
|
1553
|
+
}
|
|
1554
|
+
function withoutNameKey(value) {
|
|
1555
|
+
const copy = { ...value };
|
|
1556
|
+
delete copy.nameKey;
|
|
1557
|
+
return copy;
|
|
1558
|
+
}
|
|
1559
|
+
function renameShapeStep(pair, schema) {
|
|
1560
|
+
if (pair.from.kind === "index" && pair.to.kind === "index") {
|
|
1561
|
+
return step(`alter index ${qualify(schema, pair.from.identity.name)} rename to ${quoteIdent(pair.to.identity.name)}`, "rename-index", "ddl", ACCESS2);
|
|
1562
|
+
}
|
|
1563
|
+
if (pair.from.kind !== "constraint" || pair.to.kind !== "constraint") {
|
|
1564
|
+
throw new OkmError2("invalid", "A same-shape rename is an index or a constraint.", {
|
|
1565
|
+
fix: { summary: "The planner only renames an index or a constraint in place." }
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
return step(`alter table ${qualify(schema, pair.from.identity.parent.name)} rename constraint ${quoteIdent(pair.from.identity.name)} to ${quoteIdent(pair.to.identity.name)}`, "rename-constraint", "ddl", ACCESS2);
|
|
1569
|
+
}
|
|
1570
|
+
function assertUnambiguous(beforeBy, afterBy) {
|
|
1571
|
+
const dropped = new Map;
|
|
1572
|
+
const added = new Map;
|
|
1573
|
+
for (const [key, object] of beforeBy) {
|
|
1574
|
+
if (afterBy.has(key))
|
|
1575
|
+
continue;
|
|
1576
|
+
const group = groupOf(object);
|
|
1577
|
+
if (group === undefined)
|
|
1578
|
+
continue;
|
|
1579
|
+
const list = dropped.get(group) ?? [];
|
|
1580
|
+
const name = anchoredName(object);
|
|
1581
|
+
if (name === undefined)
|
|
1582
|
+
continue;
|
|
1583
|
+
list.push(name);
|
|
1584
|
+
dropped.set(group, list);
|
|
1585
|
+
}
|
|
1586
|
+
for (const [key, object] of afterBy) {
|
|
1587
|
+
if (beforeBy.has(key))
|
|
1588
|
+
continue;
|
|
1589
|
+
const group = groupOf(object);
|
|
1590
|
+
if (group === undefined)
|
|
1591
|
+
continue;
|
|
1592
|
+
const list = added.get(group) ?? [];
|
|
1593
|
+
const name = anchoredName(object);
|
|
1594
|
+
if (name === undefined)
|
|
1595
|
+
continue;
|
|
1596
|
+
list.push(name);
|
|
1597
|
+
added.set(group, list);
|
|
1598
|
+
}
|
|
1599
|
+
for (const [group, names] of added) {
|
|
1600
|
+
const gone = dropped.get(group);
|
|
1601
|
+
if (gone === undefined || gone.length === 0 || names.length === 0)
|
|
1602
|
+
continue;
|
|
1603
|
+
const from = gone[0] ?? "";
|
|
1604
|
+
const to = names[0] ?? "";
|
|
1605
|
+
const line = group.startsWith("table:") ? `Add renamedFrom: "${from}" on ${to}.` : `Add .renamedFrom("${from}") on ${to}.`;
|
|
1606
|
+
throw new OkmError2("OKM1530", `Drop ${from} and add ${to} could be a rename. ${line}`, {
|
|
1607
|
+
fix: { summary: line }
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
function anchoredName(object) {
|
|
1612
|
+
switch (object.kind) {
|
|
1613
|
+
case "grant":
|
|
1614
|
+
case "defaultPrivilege":
|
|
1615
|
+
return;
|
|
1616
|
+
default:
|
|
1617
|
+
return object.identity.name;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
function groupOf(object) {
|
|
1621
|
+
if (object.kind === "table")
|
|
1622
|
+
return "table:public";
|
|
1623
|
+
if (object.kind === "column")
|
|
1624
|
+
return `column:${object.identity.parent.name}`;
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
function recreateDependents(beforeList, afterList, alters, dropKeys, createKeys, picklists) {
|
|
1628
|
+
const changed = new Set(alters.filter((item) => canonicalTypeName(item.before.definition.dataType) !== canonicalTypeName(item.after.definition.dataType)).map((item) => identityKey2(item.after.identity)));
|
|
1629
|
+
if (changed.size === 0)
|
|
1630
|
+
return;
|
|
1631
|
+
const deferred = new Set(picklists.map((change) => identityKey2(change.before.identity)));
|
|
1632
|
+
const afterBy = new Map(afterList.map((item) => [item.key, item.object]));
|
|
1633
|
+
for (const item of beforeList) {
|
|
1634
|
+
const key = identityKey2(item.object.identity);
|
|
1635
|
+
if (deferred.has(key) || !afterBy.has(key))
|
|
1636
|
+
continue;
|
|
1637
|
+
if (!dependsOnChanged(item.object, changed))
|
|
1638
|
+
continue;
|
|
1639
|
+
dropKeys.add(key);
|
|
1640
|
+
createKeys.add(key);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
function recreateRoutineDependents(afterList, dropKeys, createKeys) {
|
|
1644
|
+
const recreated = new Set;
|
|
1645
|
+
for (const item of afterList) {
|
|
1646
|
+
if (!dropKeys.has(item.key) || !createKeys.has(item.key))
|
|
1647
|
+
continue;
|
|
1648
|
+
if (item.object.kind === "function" || item.object.kind === "column" || item.object.kind === "table") {
|
|
1649
|
+
recreated.add(item.key);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
if (recreated.size === 0)
|
|
1653
|
+
return;
|
|
1654
|
+
for (const item of afterList) {
|
|
1655
|
+
if (item.object.kind !== "trigger" && item.object.kind !== "function")
|
|
1656
|
+
continue;
|
|
1657
|
+
const hit = item.object.dependencies.some((edge) => recreated.has(identityKey2(edge.target)));
|
|
1658
|
+
if (!hit)
|
|
1659
|
+
continue;
|
|
1660
|
+
dropKeys.add(item.key);
|
|
1661
|
+
createKeys.add(item.key);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
function refuseRoutineDrops(beforeBy, afterBy, dropKeys) {
|
|
1665
|
+
for (const after of afterBy.values()) {
|
|
1666
|
+
const key = identityKey2(after.identity);
|
|
1667
|
+
if (dropKeys.has(key))
|
|
1668
|
+
continue;
|
|
1669
|
+
if (after.kind !== "trigger" && after.kind !== "function" && after.kind !== "view" && after.kind !== "materializedView") {
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
for (const edge of after.dependencies) {
|
|
1673
|
+
const targetKey = identityKey2(edge.target);
|
|
1674
|
+
if (!dropKeys.has(targetKey))
|
|
1675
|
+
continue;
|
|
1676
|
+
const target = beforeBy.get(targetKey);
|
|
1677
|
+
if (target === undefined)
|
|
1678
|
+
continue;
|
|
1679
|
+
if (target.kind !== "function" && target.kind !== "table" && target.kind !== "column" && target.kind !== "view" && target.kind !== "materializedView") {
|
|
1680
|
+
continue;
|
|
1681
|
+
}
|
|
1682
|
+
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." } });
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
function dependsOnChanged(object, columns) {
|
|
1687
|
+
if (object.kind !== "index" && object.kind !== "constraint" && object.kind !== "view" && object.kind !== "materializedView") {
|
|
1688
|
+
return false;
|
|
1689
|
+
}
|
|
1690
|
+
return object.dependencies.some((edge) => columns.has(identityKey2(edge.target)));
|
|
1691
|
+
}
|
|
1692
|
+
function recreateViewDependents(afterList, dropKeys, createKeys) {
|
|
1693
|
+
let changed = true;
|
|
1694
|
+
while (changed) {
|
|
1695
|
+
changed = false;
|
|
1696
|
+
for (const item of afterList) {
|
|
1697
|
+
if (dropKeys.has(item.key) && createKeys.has(item.key))
|
|
1698
|
+
continue;
|
|
1699
|
+
const kind = item.object.kind;
|
|
1700
|
+
if (kind !== "view" && kind !== "materializedView" && kind !== "index")
|
|
1701
|
+
continue;
|
|
1702
|
+
const hit = item.object.dependencies.some((edge) => {
|
|
1703
|
+
const key = identityKey2(edge.target);
|
|
1704
|
+
return dropKeys.has(key) && createKeys.has(key);
|
|
1705
|
+
});
|
|
1706
|
+
if (!hit)
|
|
1707
|
+
continue;
|
|
1708
|
+
dropKeys.add(item.key);
|
|
1709
|
+
createKeys.add(item.key);
|
|
1710
|
+
changed = true;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
function columnsAppended(before, after) {
|
|
1715
|
+
if (after.length < before.length)
|
|
1716
|
+
return false;
|
|
1717
|
+
for (let index = 0;index < before.length; index += 1) {
|
|
1718
|
+
const left = before[index];
|
|
1719
|
+
const right = after[index];
|
|
1720
|
+
if (left === undefined || right === undefined)
|
|
1721
|
+
return false;
|
|
1722
|
+
if (left.name !== right.name || canonicalTypeName(left.dataType) !== canonicalTypeName(right.dataType)) {
|
|
1723
|
+
return false;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return true;
|
|
1727
|
+
}
|
|
1728
|
+
function assertConcurrentRefresh(object, source) {
|
|
1729
|
+
if (object.definition.refresh !== "concurrently")
|
|
1730
|
+
return;
|
|
1731
|
+
const unique = source.objects.some((item) => item.kind === "index" && item.definition.unique && item.identity.parent.name === object.identity.name);
|
|
1732
|
+
if (unique)
|
|
1733
|
+
return;
|
|
1734
|
+
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." } });
|
|
1735
|
+
}
|
|
1736
|
+
function enumAddSteps(enums) {
|
|
1737
|
+
const steps = [];
|
|
1738
|
+
for (const change of enums) {
|
|
1739
|
+
if (change.kind !== "add")
|
|
1740
|
+
continue;
|
|
1741
|
+
for (const sql of change.statements) {
|
|
1742
|
+
steps.push(step(sql, "add-enum-value", "ddl", ACCESS2, false));
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
return steps;
|
|
1746
|
+
}
|
|
1747
|
+
function expandBackfills(picklists, enums, replacements, schema, catalogs, batchSize) {
|
|
1748
|
+
return dataSteps(picklists, enums, replacements, schema, catalogs, batchSize, "expand");
|
|
1749
|
+
}
|
|
1750
|
+
function contractSwaps(picklists, enums, replacements, schema, catalogs, batchSize) {
|
|
1751
|
+
const steps = [];
|
|
1752
|
+
steps.push(...dataSteps(picklists, enums, replacements, schema, catalogs, batchSize, "contract"));
|
|
1753
|
+
for (const change of picklists) {
|
|
1754
|
+
const table = qualify(schema, change.after.identity.parent.name);
|
|
1755
|
+
const name = quoteIdent(change.after.identity.name);
|
|
1756
|
+
const kind = change.removed.length > 0 ? "narrow-check" : "widen-check";
|
|
1757
|
+
steps.push(step(`alter table ${table} drop constraint ${name}`, kind, "ddl", ACCESS2));
|
|
1758
|
+
const expression = change.after.definition.expression ?? "true";
|
|
1759
|
+
steps.push(step(`alter table ${table} add constraint ${name} check (${expression}) not valid`, kind, "ddl", ACCESS2));
|
|
1760
|
+
steps.push(step(`alter table ${table} validate constraint ${name}`, kind, "ddl", SHARE_UPDATE2));
|
|
1761
|
+
}
|
|
1762
|
+
for (const change of enums) {
|
|
1763
|
+
if (change.kind !== "replace")
|
|
1764
|
+
continue;
|
|
1765
|
+
const type = qualify(schema, change.name);
|
|
1766
|
+
const old = quoteIdent(`${change.name}_old`);
|
|
1767
|
+
const labels = change.labels.map((label) => quoteLiteral(label)).join(", ");
|
|
1768
|
+
steps.push(step(`alter type ${type} rename to ${old}`, "rename-enum", "ddl", ACCESS2));
|
|
1769
|
+
steps.push(step(`create type ${type} as enum (${labels})`, "recreate-enum", "ddl", ACCESS2));
|
|
1770
|
+
for (const column of change.columns) {
|
|
1771
|
+
const table = qualify(schema, column.table);
|
|
1772
|
+
const name = quoteIdent(column.column);
|
|
1773
|
+
steps.push(step(`alter table ${table} alter column ${name} type ${type} using ${name}::text::${type}`, "set-enum-column", "ddl", ACCESS2));
|
|
1774
|
+
}
|
|
1775
|
+
steps.push(step(`drop type ${qualify(schema, `${change.name}_old`)}`, "drop-enum", "ddl", ACCESS2));
|
|
1776
|
+
}
|
|
1777
|
+
return steps;
|
|
1778
|
+
}
|
|
1779
|
+
function dataSteps(picklists, enums, replacements, schema, catalogs, batchSize, phase) {
|
|
1780
|
+
const steps = [];
|
|
1781
|
+
const kind = phase === "expand" ? "backfill-expand" : "backfill-contract";
|
|
1782
|
+
for (const change of picklists) {
|
|
1783
|
+
if (change.removed.length === 0)
|
|
1784
|
+
continue;
|
|
1785
|
+
const table = change.after.identity.parent.name;
|
|
1786
|
+
for (const value of change.removed) {
|
|
1787
|
+
const replacement = findReplacement(replacements, table, change.column, value);
|
|
1788
|
+
if (replacement === undefined)
|
|
1789
|
+
continue;
|
|
1790
|
+
steps.push(backfillStep(updateSql(schema, table, change.column, value, replacement.to, catalogs, batchSize), kind));
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
for (const change of enums) {
|
|
1794
|
+
if (change.kind !== "replace" || change.removed.length === 0)
|
|
1795
|
+
continue;
|
|
1796
|
+
for (const column of change.columns) {
|
|
1797
|
+
if (!column.existed)
|
|
1798
|
+
continue;
|
|
1799
|
+
for (const value of change.removed) {
|
|
1800
|
+
const replacement = findReplacement(replacements, column.table, column.column, value);
|
|
1801
|
+
if (replacement === undefined)
|
|
1802
|
+
continue;
|
|
1803
|
+
steps.push(backfillStep(updateSql(schema, column.table, column.column, value, replacement.to, catalogs, batchSize, true), kind));
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
return steps;
|
|
1808
|
+
}
|
|
1809
|
+
function requireReplacements(picklists, enums, replacements) {
|
|
1810
|
+
const missing = [];
|
|
1811
|
+
for (const change of picklists) {
|
|
1812
|
+
for (const value of change.removed) {
|
|
1813
|
+
const table = change.after.identity.parent.name;
|
|
1814
|
+
const found = findReplacement(replacements, table, change.column, value);
|
|
1815
|
+
if (found === undefined) {
|
|
1816
|
+
missing.push(`--replace ${table}.${change.column}.${value}=<new>`);
|
|
1817
|
+
continue;
|
|
1818
|
+
}
|
|
1819
|
+
assertReplacement(found, change.after.definition.expression, change.nullable, parseValues(change.after.definition.expression));
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
for (const change of enums) {
|
|
1823
|
+
if (change.kind !== "replace")
|
|
1824
|
+
continue;
|
|
1825
|
+
for (const column of change.columns) {
|
|
1826
|
+
if (!column.existed)
|
|
1827
|
+
continue;
|
|
1828
|
+
for (const value of change.removed) {
|
|
1829
|
+
const found = findReplacement(replacements, column.table, column.column, value);
|
|
1830
|
+
if (found === undefined) {
|
|
1831
|
+
missing.push(`--replace ${column.table}.${column.column}.${value}=<new>`);
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
assertReplacement(found, undefined, column.nullable, change.labels);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
if (missing.length > 0) {
|
|
1839
|
+
throw new OkmError2("OKM1541", `Removed values need a replacement: ${missing.join(", ")}.`, {
|
|
1840
|
+
fix: { summary: missing.join(" ") }
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
function assertReplacement(replacement, expression, nullable, allowed) {
|
|
1845
|
+
if (replacement.to === null) {
|
|
1846
|
+
if (!nullable) {
|
|
1847
|
+
throw new OkmError2("OKM1541", `--replace ${replacement.table}.${replacement.column}.${replacement.from}=null needs a nullable column.`, { fix: { summary: "Pass a value that is in the new list, or make the column nullable." } });
|
|
1848
|
+
}
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
const list = allowed ?? (expression === undefined ? [] : parseValues(expression) ?? []);
|
|
1852
|
+
if (!list.includes(replacement.to)) {
|
|
1853
|
+
const flag = `--replace ${replacement.table}.${replacement.column}.${replacement.from}=${replacement.to}`;
|
|
1854
|
+
throw new OkmError2("OKM1541", `${flag} is not in the new list.`, {
|
|
1855
|
+
fix: { summary: "The replacement must be in the new list. No chains." }
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
function parseValues(expression) {
|
|
1860
|
+
if (expression === undefined)
|
|
1861
|
+
return;
|
|
1862
|
+
return parseInList(expression)?.values;
|
|
1863
|
+
}
|
|
1864
|
+
function findReplacement(replacements, table, column, from) {
|
|
1865
|
+
return replacements.find((item) => item.table === table && item.column === column && item.from === from);
|
|
1866
|
+
}
|
|
1867
|
+
function picklistChanges(beforeBy, afterBy, after) {
|
|
1868
|
+
const changes = [];
|
|
1869
|
+
for (const [key, object] of beforeBy) {
|
|
1870
|
+
if (object.kind !== "constraint" || object.definition.constraintKind !== "check")
|
|
1871
|
+
continue;
|
|
1872
|
+
const next = afterBy.get(key);
|
|
1873
|
+
if (next === undefined || next.kind !== "constraint")
|
|
1874
|
+
continue;
|
|
1875
|
+
const left = parseInList(object.definition.expression ?? "");
|
|
1876
|
+
const right = parseInList(next.definition.expression ?? "");
|
|
1877
|
+
if (left === undefined || right === undefined || left.column !== right.column)
|
|
1878
|
+
continue;
|
|
1879
|
+
const removed = left.values.filter((value) => !right.values.includes(value));
|
|
1880
|
+
const added = right.values.filter((value) => !left.values.includes(value));
|
|
1881
|
+
if (removed.length === 0 && added.length === 0)
|
|
1882
|
+
continue;
|
|
1883
|
+
changes.push({
|
|
1884
|
+
before: object,
|
|
1885
|
+
after: next,
|
|
1886
|
+
column: right.column,
|
|
1887
|
+
removed,
|
|
1888
|
+
added,
|
|
1889
|
+
nullable: columnNullable(after, next.identity.parent.name, right.column)
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
return changes;
|
|
1893
|
+
}
|
|
1894
|
+
function refuseDomainBaseChange(before, after) {
|
|
1895
|
+
if (before.kind !== "type" || after.kind !== "type")
|
|
1896
|
+
return;
|
|
1897
|
+
if (!isDomain(before.definition) || !isDomain(after.definition))
|
|
1898
|
+
return;
|
|
1899
|
+
if (before.definition.base === after.definition.base)
|
|
1900
|
+
return;
|
|
1901
|
+
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.`, {
|
|
1902
|
+
fix: {
|
|
1903
|
+
summary: "Keep the base type. Add a new domain when the column needs another type."
|
|
1904
|
+
}
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
function domainCheckSteps(beforeBy, afterBy, schema) {
|
|
1908
|
+
const steps = [];
|
|
1909
|
+
for (const [key, next] of afterBy) {
|
|
1910
|
+
if (next.kind !== "type" || !isDomain(next.definition))
|
|
1911
|
+
continue;
|
|
1912
|
+
const previous = beforeBy.get(key);
|
|
1913
|
+
if (previous === undefined || previous.kind !== "type" || !isDomain(previous.definition)) {
|
|
1914
|
+
continue;
|
|
1915
|
+
}
|
|
1916
|
+
if (previous.definition.check === next.definition.check)
|
|
1917
|
+
continue;
|
|
1918
|
+
const name = next.identity.name;
|
|
1919
|
+
const type = qualify(schema, name);
|
|
1920
|
+
const current = quoteIdent(fitIdentifier2(`${name}_check`));
|
|
1921
|
+
const upcoming = quoteIdent(fitIdentifier2(`${name}_check_next`));
|
|
1922
|
+
const expression = next.definition.check;
|
|
1923
|
+
steps.push(step(`alter domain ${type} add constraint ${upcoming} check (${expression}) not valid`, "add-domain-check", "ddl", ACCESS2));
|
|
1924
|
+
steps.push(step(`alter domain ${type} validate constraint ${upcoming}`, "validate-domain-check", "ddl", ACCESS2));
|
|
1925
|
+
steps.push(step(`alter domain ${type} drop constraint ${current}`, "drop-domain-check", "ddl", ACCESS2));
|
|
1926
|
+
steps.push(step(`alter domain ${type} rename constraint ${upcoming} to ${current}`, "rename-domain-check", "ddl", ACCESS2));
|
|
1927
|
+
}
|
|
1928
|
+
return steps;
|
|
1929
|
+
}
|
|
1930
|
+
function enumEdits(beforeBy, afterBy, schema) {
|
|
1931
|
+
const edits = [];
|
|
1932
|
+
for (const [key, next] of afterBy) {
|
|
1933
|
+
if (next.kind !== "type" || isDomain(next.definition))
|
|
1934
|
+
continue;
|
|
1935
|
+
const previous = beforeBy.get(key);
|
|
1936
|
+
if (previous === undefined || previous.kind !== "type" || isDomain(previous.definition)) {
|
|
1937
|
+
continue;
|
|
1938
|
+
}
|
|
1939
|
+
const beforeLabels = previous.definition.labels;
|
|
1940
|
+
const afterLabels = next.definition.labels;
|
|
1941
|
+
if (sameEnumLabels(beforeLabels, afterLabels))
|
|
1942
|
+
continue;
|
|
1943
|
+
const removed = beforeLabels.filter((label) => !afterLabels.includes(label));
|
|
1944
|
+
const columns = enumColumns(previous.identity.name, beforeBy, afterBy);
|
|
1945
|
+
if (removed.length === 0 && labelSubsequence(beforeLabels, afterLabels)) {
|
|
1946
|
+
edits.push({
|
|
1947
|
+
kind: "add",
|
|
1948
|
+
name: next.identity.name,
|
|
1949
|
+
statements: addValueStatements(schema, next.identity.name, beforeLabels, afterLabels)
|
|
1950
|
+
});
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
edits.push({
|
|
1954
|
+
kind: "replace",
|
|
1955
|
+
name: next.identity.name,
|
|
1956
|
+
labels: afterLabels,
|
|
1957
|
+
removed,
|
|
1958
|
+
columns
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
return edits;
|
|
1962
|
+
}
|
|
1963
|
+
function enumColumns(typeName, beforeBy, afterBy) {
|
|
1964
|
+
const columns = [];
|
|
1965
|
+
for (const object of afterBy.values()) {
|
|
1966
|
+
if (object.kind !== "column")
|
|
1967
|
+
continue;
|
|
1968
|
+
if (!usesType(object, typeName))
|
|
1969
|
+
continue;
|
|
1970
|
+
columns.push({
|
|
1971
|
+
table: object.identity.parent.name,
|
|
1972
|
+
column: object.identity.name,
|
|
1973
|
+
nullable: object.definition.nullable,
|
|
1974
|
+
existed: beforeBy.has(identityKey2(object.identity))
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
columns.sort((left, right) => {
|
|
1978
|
+
const table = compareText(left.table, right.table);
|
|
1979
|
+
return table === 0 ? compareText(left.column, right.column) : table;
|
|
1980
|
+
});
|
|
1981
|
+
return columns;
|
|
1982
|
+
}
|
|
1983
|
+
function usesType(object, typeName) {
|
|
1984
|
+
return object.dependencies.some((edge) => edge.target.kind === "type" && edge.target.name === typeName);
|
|
1985
|
+
}
|
|
1986
|
+
function labelSubsequence(before, after) {
|
|
1987
|
+
let index = 0;
|
|
1988
|
+
for (const label of after) {
|
|
1989
|
+
if (before[index] === label)
|
|
1990
|
+
index += 1;
|
|
1991
|
+
}
|
|
1992
|
+
return index === before.length;
|
|
1993
|
+
}
|
|
1994
|
+
function addValueStatements(schema, name, before, after) {
|
|
1995
|
+
const present = new Set(before);
|
|
1996
|
+
const statements = [];
|
|
1997
|
+
const type = qualify(schema, name);
|
|
1998
|
+
for (let index = 0;index < after.length; index += 1) {
|
|
1999
|
+
const label = after[index];
|
|
2000
|
+
if (label === undefined || present.has(label))
|
|
2001
|
+
continue;
|
|
2002
|
+
let beforeNeighbor;
|
|
2003
|
+
for (let cursor = index + 1;cursor < after.length; cursor += 1) {
|
|
2004
|
+
const next = after[cursor];
|
|
2005
|
+
if (next !== undefined && present.has(next)) {
|
|
2006
|
+
beforeNeighbor = next;
|
|
2007
|
+
break;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
let sql = `alter type ${type} add value ${quoteLiteral(label)}`;
|
|
2011
|
+
if (beforeNeighbor !== undefined) {
|
|
2012
|
+
sql += ` before ${quoteLiteral(beforeNeighbor)}`;
|
|
2013
|
+
} else {
|
|
2014
|
+
let afterNeighbor;
|
|
2015
|
+
for (let cursor = index - 1;cursor >= 0; cursor -= 1) {
|
|
2016
|
+
const previous = after[cursor];
|
|
2017
|
+
if (previous !== undefined && present.has(previous)) {
|
|
2018
|
+
afterNeighbor = previous;
|
|
2019
|
+
break;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
if (afterNeighbor !== undefined)
|
|
2023
|
+
sql += ` after ${quoteLiteral(afterNeighbor)}`;
|
|
2024
|
+
}
|
|
2025
|
+
statements.push(sql);
|
|
2026
|
+
present.add(label);
|
|
2027
|
+
}
|
|
2028
|
+
return statements;
|
|
2029
|
+
}
|
|
2030
|
+
function parseInList(expression) {
|
|
2031
|
+
const match = /^\(\s*([A-Za-z_][A-Za-z0-9_]*)\s+IN\s*\((.*)\)\s*\)$/i.exec(expression.trim());
|
|
2032
|
+
const column = match?.[1];
|
|
2033
|
+
const body = match?.[2];
|
|
2034
|
+
if (column === undefined || body === undefined)
|
|
2035
|
+
return;
|
|
2036
|
+
const values = [];
|
|
2037
|
+
let index = 0;
|
|
2038
|
+
while (index < body.length) {
|
|
2039
|
+
const char = body[index] ?? "";
|
|
2040
|
+
if (char === " " || char === ",") {
|
|
2041
|
+
index += 1;
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
if (char !== "'")
|
|
2045
|
+
return;
|
|
2046
|
+
let end = index + 1;
|
|
2047
|
+
let value = "";
|
|
2048
|
+
while (end < body.length) {
|
|
2049
|
+
const next = body[end] ?? "";
|
|
2050
|
+
if (next === "'" && body[end + 1] === "'") {
|
|
2051
|
+
value += "'";
|
|
2052
|
+
end += 2;
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
if (next === "'") {
|
|
2056
|
+
end += 1;
|
|
2057
|
+
break;
|
|
2058
|
+
}
|
|
2059
|
+
value += next;
|
|
2060
|
+
end += 1;
|
|
2061
|
+
}
|
|
2062
|
+
values.push(value);
|
|
2063
|
+
index = end;
|
|
2064
|
+
}
|
|
2065
|
+
return { column, values };
|
|
2066
|
+
}
|
|
2067
|
+
function columnNullable(source, table, column) {
|
|
2068
|
+
for (const object of source.objects) {
|
|
2069
|
+
if (object.kind === "column" && object.identity.parent.name === table && object.identity.name === column) {
|
|
2070
|
+
return object.definition.nullable;
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
return false;
|
|
2074
|
+
}
|
|
2075
|
+
function columnAlterable(before, after) {
|
|
2076
|
+
return stable(before.definition.generated) === stable(after.definition.generated);
|
|
2077
|
+
}
|
|
2078
|
+
function identityChangeSql(before, after, schema) {
|
|
2079
|
+
if (stable(before.definition.identity) === stable(after.definition.identity))
|
|
2080
|
+
return;
|
|
2081
|
+
const table = qualify(schema, after.identity.parent.name);
|
|
2082
|
+
const name = quoteIdent(after.identity.name);
|
|
2083
|
+
const next = after.definition.identity;
|
|
2084
|
+
const previous = before.definition.identity;
|
|
2085
|
+
if (next === undefined)
|
|
2086
|
+
return `alter table ${table} alter column ${name} drop identity`;
|
|
2087
|
+
if (previous === undefined) {
|
|
2088
|
+
const generated = next.always ? "always" : "by default";
|
|
2089
|
+
return `alter table ${table} alter column ${name} add generated ${generated} as identity`;
|
|
2090
|
+
}
|
|
2091
|
+
return `alter table ${table} alter column ${name} set generated ${next.always ? "always" : "by default"}`;
|
|
2092
|
+
}
|
|
2093
|
+
function omitOwnedSequences(keys, by, source) {
|
|
2094
|
+
for (const key of keys) {
|
|
2095
|
+
const object = by.get(key);
|
|
2096
|
+
if (object?.kind === "sequence" && folded(object, source))
|
|
2097
|
+
keys.delete(key);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
var TRAILING_CAST = /^(.*)::((?:timestamp|time)(?:\(\d+\))? (?:with|without) time zone|character varying(?:\(\d+\))?|[A-Za-z_][\w$]*(?:\([^)]*\))?)$/;
|
|
2101
|
+
function canonicalDefault(expression, dataType) {
|
|
2102
|
+
if (expression === undefined)
|
|
2103
|
+
return {};
|
|
2104
|
+
const match = TRAILING_CAST.exec(expression.trim());
|
|
2105
|
+
const body = match?.[1]?.trim();
|
|
2106
|
+
const cast = match?.[2];
|
|
2107
|
+
if (body === undefined || body.length === 0 || cast === undefined)
|
|
2108
|
+
return { defaultExpression: expression };
|
|
2109
|
+
if (!defaultCastMatches(cast, dataType))
|
|
2110
|
+
return { defaultExpression: expression };
|
|
2111
|
+
return { defaultExpression: body };
|
|
2112
|
+
}
|
|
2113
|
+
function defaultCastMatches(cast, dataType) {
|
|
2114
|
+
if (canonicalTypeName(cast) === canonicalTypeName(dataType))
|
|
2115
|
+
return true;
|
|
2116
|
+
const castBase = reprintedDefaultBase(cast);
|
|
2117
|
+
const columnBase = reprintedDefaultBase(dataType);
|
|
2118
|
+
return castBase !== undefined && castBase === columnBase;
|
|
2119
|
+
}
|
|
2120
|
+
function reprintedDefaultBase(typeName) {
|
|
2121
|
+
const raw = typeName.trim();
|
|
2122
|
+
if (/^(?:bpchar|character|char)(?:\(\d+\))?$/.test(raw))
|
|
2123
|
+
return "character";
|
|
2124
|
+
const canonical = canonicalTypeName(raw);
|
|
2125
|
+
if (/^character varying(?:\(\d+\))?$/.test(canonical))
|
|
2126
|
+
return "character varying";
|
|
2127
|
+
if (/^character(?:\(\d+\))?$/.test(canonical))
|
|
2128
|
+
return "character";
|
|
2129
|
+
const time = /^(timestamp|time)(?:\(\d+\))? (with time zone|without time zone)$/.exec(canonical);
|
|
2130
|
+
if (time !== null)
|
|
2131
|
+
return `${time[1] ?? ""} ${time[2] ?? ""}`;
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
function sameDefinition(left, right) {
|
|
2135
|
+
if (left.kind === "materializedView" && right.kind === "materializedView") {
|
|
2136
|
+
return stable(canonicalColumns(left.definition.columns)) === stable(canonicalColumns(right.definition.columns)) && left.definition.query === right.definition.query;
|
|
2137
|
+
}
|
|
2138
|
+
return stable(forComparison(left)) === stable(forComparison(right));
|
|
2139
|
+
}
|
|
2140
|
+
function forComparison(object) {
|
|
2141
|
+
switch (object.kind) {
|
|
2142
|
+
case "column":
|
|
2143
|
+
return {
|
|
2144
|
+
...object.definition,
|
|
2145
|
+
dataType: canonicalTypeName(object.definition.dataType),
|
|
2146
|
+
...canonicalDefault(object.definition.defaultExpression, object.definition.dataType)
|
|
2147
|
+
};
|
|
2148
|
+
case "sequence":
|
|
2149
|
+
return { ...object.definition, dataType: canonicalTypeName(object.definition.dataType) };
|
|
2150
|
+
case "view":
|
|
2151
|
+
return { ...object.definition, columns: canonicalColumns(object.definition.columns) };
|
|
2152
|
+
case "function":
|
|
2153
|
+
return {
|
|
2154
|
+
...object.definition,
|
|
2155
|
+
returns: canonicalTypeName(object.definition.returns),
|
|
2156
|
+
arguments: object.definition.arguments.map((argument) => ({
|
|
2157
|
+
...argument,
|
|
2158
|
+
type: canonicalTypeName(argument.type)
|
|
2159
|
+
}))
|
|
2160
|
+
};
|
|
2161
|
+
case "trigger":
|
|
2162
|
+
return {
|
|
2163
|
+
...object.definition,
|
|
2164
|
+
calls: {
|
|
2165
|
+
...object.definition.calls,
|
|
2166
|
+
argTypes: object.definition.calls.argTypes.map((typeName) => canonicalTypeName(typeName))
|
|
2167
|
+
}
|
|
2168
|
+
};
|
|
2169
|
+
case "type":
|
|
2170
|
+
if (!isDomain(object.definition))
|
|
2171
|
+
return object.definition;
|
|
2172
|
+
return { ...object.definition, base: canonicalTypeName(object.definition.base) };
|
|
2173
|
+
default:
|
|
2174
|
+
return object.definition;
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
function canonicalColumns(columns) {
|
|
2178
|
+
return columns.map((column) => ({ ...column, dataType: canonicalTypeName(column.dataType) }));
|
|
2179
|
+
}
|
|
2180
|
+
function alignedColumnType(before, after) {
|
|
2181
|
+
if (canonicalTypeName(before.definition.dataType) !== canonicalTypeName(after.definition.dataType)) {
|
|
2182
|
+
return before;
|
|
2183
|
+
}
|
|
2184
|
+
if (before.definition.dataType === after.definition.dataType)
|
|
2185
|
+
return before;
|
|
2186
|
+
return { ...before, definition: { ...before.definition, dataType: after.definition.dataType } };
|
|
2187
|
+
}
|
|
2188
|
+
function covered(object, tables) {
|
|
2189
|
+
if (object.kind === "column" || object.kind === "index" || object.kind === "constraint") {
|
|
2190
|
+
return tables.has(object.identity.parent.name);
|
|
2191
|
+
}
|
|
2192
|
+
if (object.kind === "sequence") {
|
|
2193
|
+
return object.dependencies.some((edge) => edge.target.kind === "table" && tables.has(edge.target.name));
|
|
2194
|
+
}
|
|
2195
|
+
return false;
|
|
2196
|
+
}
|
|
2197
|
+
function folded(object, source) {
|
|
2198
|
+
if (object.kind === "column")
|
|
2199
|
+
return true;
|
|
2200
|
+
if (object.kind === "constraint" && object.definition.constraintKind === "primaryKey")
|
|
2201
|
+
return true;
|
|
2202
|
+
if (object.kind !== "sequence")
|
|
2203
|
+
return false;
|
|
2204
|
+
if (identitySequence(object, source))
|
|
2205
|
+
return true;
|
|
2206
|
+
const name = object.identity.name;
|
|
2207
|
+
return source.objects.some((column) => {
|
|
2208
|
+
if (column.kind !== "column" || column.definition.identity === undefined)
|
|
2209
|
+
return false;
|
|
2210
|
+
return name === `${column.identity.parent.name}_${column.identity.name}_seq`;
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2213
|
+
function anchoredParent(object) {
|
|
2214
|
+
if (object.kind === "column" || object.kind === "index" || object.kind === "constraint") {
|
|
2215
|
+
return object.identity.parent.name;
|
|
2216
|
+
}
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
function overall(steps) {
|
|
2220
|
+
return strictestClass(steps.map((item) => item.class));
|
|
2221
|
+
}
|
|
2222
|
+
function updateSql(schema, table, column, from, to, catalogs, batchSize, cast = false) {
|
|
2223
|
+
const key = primaryKeyColumns(catalogs, table);
|
|
2224
|
+
const qualified = qualify(schema, table);
|
|
2225
|
+
const value = to === null ? "null" : quoteLiteral(to);
|
|
2226
|
+
const compare = cast ? `${quoteIdent(column)}::text` : quoteIdent(column);
|
|
2227
|
+
const where = `${compare} = ${quoteLiteral(from)}`;
|
|
2228
|
+
const backfill = {
|
|
2229
|
+
table: qualified,
|
|
2230
|
+
key: key.map((item) => item.quoted),
|
|
2231
|
+
batch: batchSize
|
|
2232
|
+
};
|
|
2233
|
+
return {
|
|
2234
|
+
sql: `update ${qualified} set ${quoteIdent(column)} = ${value} where ${where} and ${keyRangePredicate(key)}`,
|
|
2235
|
+
backfill
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
function backfillStep(planned, kind) {
|
|
2239
|
+
return {
|
|
2240
|
+
...step(planned.sql, kind, "backfill", ROW2, false),
|
|
2241
|
+
backfill: planned.backfill
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2244
|
+
function hasTable(source, name) {
|
|
2245
|
+
return source.objects.some((object) => object.kind === "table" && object.identity.name === name);
|
|
2246
|
+
}
|
|
2247
|
+
function hasColumn(source, table, name) {
|
|
2248
|
+
return source.objects.some((object) => object.kind === "column" && object.identity.parent.name === table && object.identity.name === name);
|
|
2249
|
+
}
|
|
2250
|
+
function index2(source) {
|
|
2251
|
+
return source.objects.map((object) => ({ key: identityKey2(object.identity), object }));
|
|
2252
|
+
}
|
|
2253
|
+
function step(sql, kind, action, lock, transactional = true, extraTables) {
|
|
2254
|
+
const tables = touchedTables(sql, lock, extraTables);
|
|
2255
|
+
return {
|
|
2256
|
+
sql,
|
|
2257
|
+
kind,
|
|
2258
|
+
class: classOf(kind),
|
|
2259
|
+
action,
|
|
2260
|
+
lock,
|
|
2261
|
+
transactional,
|
|
2262
|
+
...tables.length > 0 ? { tables } : {}
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
function pushSafe(steps, safe) {
|
|
2266
|
+
for (const item of safe) {
|
|
2267
|
+
steps.push({
|
|
2268
|
+
...step(item.sql, item.kind, item.action, item.lock, item.transactional),
|
|
2269
|
+
safeRewrite: true,
|
|
2270
|
+
...item.backfill !== undefined ? { backfill: item.backfill } : {}
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
var TABLE_PREFIX = /\b(?:create|alter|drop)\s+table(?:\s+if\s+exists)?\s+|\bupdate\s+|\binsert\s+into\s+|\breferences\s+|\bon\s+table\s+|\bon\s+/gi;
|
|
2275
|
+
function touchedTables(sql, lock, extra) {
|
|
2276
|
+
const names = [];
|
|
2277
|
+
const text = `${sql}
|
|
2278
|
+
${lock}`;
|
|
2279
|
+
TABLE_PREFIX.lastIndex = 0;
|
|
2280
|
+
for (let match = TABLE_PREFIX.exec(text);match !== null; match = TABLE_PREFIX.exec(text)) {
|
|
2281
|
+
const found = readQualified(text, match.index + match[0].length);
|
|
2282
|
+
if (found !== undefined)
|
|
2283
|
+
pushTable(names, found.name);
|
|
2284
|
+
}
|
|
2285
|
+
for (const name of extra ?? [])
|
|
2286
|
+
pushTable(names, name);
|
|
2287
|
+
return names;
|
|
2288
|
+
}
|
|
2289
|
+
function pushTable(names, name) {
|
|
2290
|
+
if (!names.includes(name))
|
|
2291
|
+
names.push(name);
|
|
2292
|
+
}
|
|
2293
|
+
function readQualified(text, start) {
|
|
2294
|
+
const schema = readIdent(text, start);
|
|
2295
|
+
if (schema === undefined)
|
|
2296
|
+
return;
|
|
2297
|
+
if (text[schema.end] !== "." || text[schema.end + 1] !== '"')
|
|
2298
|
+
return;
|
|
2299
|
+
const name = readIdent(text, schema.end + 1);
|
|
2300
|
+
if (name === undefined || text[name.end] === ".")
|
|
2301
|
+
return;
|
|
2302
|
+
return name;
|
|
2303
|
+
}
|
|
2304
|
+
function readIdent(text, start) {
|
|
2305
|
+
if (text[start] !== '"')
|
|
2306
|
+
return;
|
|
2307
|
+
let value = "";
|
|
2308
|
+
let index = start + 1;
|
|
2309
|
+
while (index < text.length) {
|
|
2310
|
+
const char = text[index];
|
|
2311
|
+
if (char === '"') {
|
|
2312
|
+
if (text[index + 1] === '"') {
|
|
2313
|
+
value += '"';
|
|
2314
|
+
index += 2;
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
return { name: value, end: index + 1 };
|
|
2318
|
+
}
|
|
2319
|
+
value += char ?? "";
|
|
2320
|
+
index += 1;
|
|
2321
|
+
}
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
function pushDrop(steps, object, schema) {
|
|
2325
|
+
const sql = dropObjectSql(object, schema);
|
|
2326
|
+
const kind = dropKind(object);
|
|
2327
|
+
if (sql === undefined || kind === undefined)
|
|
2328
|
+
return;
|
|
2329
|
+
steps.push(step(sql, kind, "ddl", ACCESS2));
|
|
2330
|
+
}
|
|
2331
|
+
function createKind(object, tableIsNew) {
|
|
2332
|
+
switch (object.kind) {
|
|
2333
|
+
case "table":
|
|
2334
|
+
return "create-table";
|
|
2335
|
+
case "column":
|
|
2336
|
+
return requiredColumn(object) && !tableIsNew ? "add-column-required" : "add-column";
|
|
2337
|
+
case "index":
|
|
2338
|
+
return "create-index";
|
|
2339
|
+
case "constraint":
|
|
2340
|
+
return "add-constraint";
|
|
2341
|
+
case "sequence":
|
|
2342
|
+
return "create-sequence";
|
|
2343
|
+
case "type":
|
|
2344
|
+
return isDomain(object.definition) ? "create-domain" : "create-enum";
|
|
2345
|
+
case "extension":
|
|
2346
|
+
return "create-extension";
|
|
2347
|
+
case "function":
|
|
2348
|
+
return "create-function";
|
|
2349
|
+
case "trigger":
|
|
2350
|
+
return "create-trigger";
|
|
2351
|
+
case "view":
|
|
2352
|
+
return "create-view";
|
|
2353
|
+
case "materializedView":
|
|
2354
|
+
return "create-matview";
|
|
2355
|
+
default:
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
function dropKind(object) {
|
|
2360
|
+
switch (object.kind) {
|
|
2361
|
+
case "table":
|
|
2362
|
+
return "drop-table";
|
|
2363
|
+
case "column":
|
|
2364
|
+
return "drop-column";
|
|
2365
|
+
case "index":
|
|
2366
|
+
return "drop-index";
|
|
2367
|
+
case "constraint":
|
|
2368
|
+
return "drop-constraint";
|
|
2369
|
+
case "sequence":
|
|
2370
|
+
return "drop-sequence";
|
|
2371
|
+
case "type":
|
|
2372
|
+
return isDomain(object.definition) ? "drop-domain" : "drop-enum";
|
|
2373
|
+
case "extension":
|
|
2374
|
+
return "drop-extension";
|
|
2375
|
+
case "function":
|
|
2376
|
+
return "drop-function";
|
|
2377
|
+
case "trigger":
|
|
2378
|
+
return "drop-trigger";
|
|
2379
|
+
case "view":
|
|
2380
|
+
return "drop-view";
|
|
2381
|
+
case "materializedView":
|
|
2382
|
+
return "drop-matview";
|
|
2383
|
+
default:
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
function requiredColumn(column) {
|
|
2388
|
+
const definition = column.definition;
|
|
2389
|
+
return !definition.nullable && definition.defaultExpression === undefined && definition.identity === undefined && definition.generated === undefined;
|
|
2390
|
+
}
|
|
2391
|
+
function columnAlterKinds(before, after) {
|
|
2392
|
+
const kinds = [];
|
|
2393
|
+
if (before.definition.dataType !== after.definition.dataType || before.definition.collation !== after.definition.collation) {
|
|
2394
|
+
kinds.push("set-column-type");
|
|
2395
|
+
}
|
|
2396
|
+
if (before.definition.nullable !== after.definition.nullable) {
|
|
2397
|
+
kinds.push(after.definition.nullable ? "drop-not-null" : "set-not-null");
|
|
2398
|
+
}
|
|
2399
|
+
if (before.definition.defaultExpression !== after.definition.defaultExpression) {
|
|
2400
|
+
kinds.push(after.definition.defaultExpression === undefined ? "drop-default" : "set-default");
|
|
2401
|
+
}
|
|
2402
|
+
return kinds;
|
|
2403
|
+
}
|
|
2404
|
+
function identityKind(before, after) {
|
|
2405
|
+
if (stable(before.definition.identity) === stable(after.definition.identity))
|
|
2406
|
+
return;
|
|
2407
|
+
const next = after.definition.identity;
|
|
2408
|
+
const previous = before.definition.identity;
|
|
2409
|
+
if (next === undefined)
|
|
2410
|
+
return "drop-identity";
|
|
2411
|
+
if (previous === undefined)
|
|
2412
|
+
return "add-identity";
|
|
2413
|
+
return next.always ? "set-identity-always" : "set-identity-by-default";
|
|
2414
|
+
}
|
|
2415
|
+
function stable(value) {
|
|
2416
|
+
return JSON.stringify(order(value));
|
|
2417
|
+
}
|
|
2418
|
+
function order(value) {
|
|
2419
|
+
if (Array.isArray(value))
|
|
2420
|
+
return value.map((item) => order(item));
|
|
2421
|
+
if (value !== null && typeof value === "object") {
|
|
2422
|
+
const record = value;
|
|
2423
|
+
const next = {};
|
|
2424
|
+
for (const key of Object.keys(record).sort())
|
|
2425
|
+
next[key] = order(record[key]);
|
|
2426
|
+
return next;
|
|
2427
|
+
}
|
|
2428
|
+
return value;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
export { canonicalTypeName, createdRoleName, changesRole, DEFAULT_BACKFILL_PAUSE_MS, DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS, batchSizeField, aboutBatchCount, boundaryQuery, checkedCast, classOf, strictestClass, assertExtensionsAvailable, omitManagedObjects, parseReplace, planMigration, formatPlan, parsePlan, staleRenames };
|