okmodel 0.1.1 → 0.3.0

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