okmodel 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/README.md +9 -1
  2. package/dist/adapters/pg/bunsql-extra.d.ts +5 -5
  3. package/dist/adapters/pg/bunsql.d.ts +6 -2
  4. package/dist/adapters/pg/nodepostgres.d.ts +16 -1
  5. package/dist/adapters/pg/postgres-extra.d.ts +3 -17
  6. package/dist/adapters/pg/postgresjs.d.ts +2 -1
  7. package/dist/contracts/driver.d.ts +3 -2
  8. package/dist/contracts/error.d.ts +1 -1
  9. package/dist/dialects/pg/errors.d.ts +2 -0
  10. package/dist/dialects/pg/ext/fuzzystrmatch.js +1 -1
  11. package/dist/dialects/pg/ext/hstore.js +1 -1
  12. package/dist/dialects/pg/ext/ltree.js +1 -1
  13. package/dist/dialects/pg/ext/pgcrypto.js +1 -1
  14. package/dist/dialects/pg/ext/unaccent.js +1 -1
  15. package/dist/dialects/pg/filters.d.ts +2 -2
  16. package/dist/dialects/pg/fn/index.js +1 -1
  17. package/dist/dialects/pg/index.js +3 -3
  18. package/dist/dialects/pg/introspect.d.ts +12 -0
  19. package/dist/dialects/pg/view/index.js +2 -2
  20. package/dist/runtime/param-limit.d.ts +20 -0
  21. package/dist/runtime/pg/bun.js +3 -3
  22. package/dist/runtime/pg/neon.js +3 -3
  23. package/dist/runtime/pg/pg.js +3 -3
  24. package/dist/runtime/pg/pglite.js +2 -2
  25. package/dist/runtime/pg/postgresjs.js +10 -10
  26. package/dist/runtime/tenancy/index.js +1 -1
  27. package/dist/runtime/traits/index.js +2 -2
  28. package/dist/runtime/validate/index.js +2 -2
  29. package/dist/runtime/write.d.ts +1 -1
  30. package/dist/shared/{5kyb3tqx.js → 1h5h1rf5.js} +1 -1
  31. package/dist/shared/{4vbf83xx.js → 2vcnpf0n.js} +1 -1
  32. package/dist/shared/{8n688yjg.js → 3ap5hxvj.js} +2 -1
  33. package/dist/shared/{wvtjg5m3.js → 3d7a80fq.js} +1 -1
  34. package/dist/shared/{bphrgwz9.js → 3dr444ny.js} +57 -78
  35. package/dist/shared/{yvabyg9k.js → 3vvpcphd.js} +7 -6
  36. package/dist/shared/{760ppr76.js → 4a3mmhhe.js} +2 -2
  37. package/dist/shared/{90sg0w5a.js → 4pkz5nvw.js} +65 -10
  38. package/dist/shared/{mazpbcdj.js → 4te76xc5.js} +1 -1
  39. package/dist/shared/{xwhdcrm4.js → 60y4ahmw.js} +1 -1
  40. package/dist/shared/{9dtgn1z2.js → 63qjmhnp.js} +100 -17
  41. package/dist/shared/{s7he5e5d.js → 67w3kn71.js} +1 -1
  42. package/dist/shared/{n306gqkd.js → 6vfvsdkt.js} +9 -7
  43. package/dist/shared/{jpybhf98.js → 6xqvesa5.js} +1 -1
  44. package/dist/shared/{n5whk3mt.js → 8gdbty5v.js} +8 -8
  45. package/dist/shared/{ccsmhv9r.js → 8ma6drzr.js} +194 -31
  46. package/dist/shared/{3456242y.js → 8p2bgye0.js} +110 -18
  47. package/dist/shared/{ebhz8vh6.js → 8qwqrxed.js} +11 -3
  48. package/dist/shared/{ss2kv2rs.js → 8v7vzpgt.js} +10 -7
  49. package/dist/shared/{8qzkd9f3.js → ak61ye1f.js} +3 -3
  50. package/dist/shared/{6nae0spm.js → bdekbvmt.js} +69 -7
  51. package/dist/shared/{tw1nvvfj.js → bxjq6xqv.js} +2 -2
  52. package/dist/shared/{rbxt795q.js → c7dqd3gk.js} +81 -41
  53. package/dist/shared/ddh72ym5.js +20 -0
  54. package/dist/shared/e1c55v04.js +10 -0
  55. package/dist/shared/{8jesqsrk.js → ekhq3ytz.js} +1 -1
  56. package/dist/shared/{7ba5h6pb.js → esy4dyxe.js} +5 -5
  57. package/dist/shared/{gcefca6p.js → f9t9g54z.js} +22 -9
  58. package/dist/shared/fkjne6gx.js +8 -0
  59. package/dist/shared/{ds229x4r.js → fne90dvz.js} +4 -1
  60. package/dist/shared/{cexb7z6b.js → gr9sn4za.js} +1 -1
  61. package/dist/shared/{mvmm3xpb.js → gzgfsspx.js} +1 -1
  62. package/dist/shared/{fdsz3w80.js → hgt69zjf.js} +2 -2
  63. package/dist/shared/{dd5pmnky.js → jvxcp1qk.js} +70 -86
  64. package/dist/shared/{yybhre6n.js → k73e8n9d.js} +3 -3
  65. package/dist/shared/{mgaq2sd3.js → kzgrcy8s.js} +8 -7
  66. package/dist/shared/{myk8fswx.js → mz48rs74.js} +14 -3
  67. package/dist/shared/{62b603gr.js → n6je5b28.js} +10 -23
  68. package/dist/shared/{2szgv6yj.js → ns35jqz0.js} +71 -45
  69. package/dist/shared/{xtsydxjg.js → nstwgzn8.js} +1 -1
  70. package/dist/shared/{stfa9xcw.js → py2zrrm6.js} +7 -6
  71. package/dist/shared/{9dr2qnx7.js → qqm3ze6q.js} +1 -1
  72. package/dist/shared/{cf8zdz5p.js → rcjm0yn7.js} +27 -34
  73. package/dist/shared/{ckz05z15.js → rged92v6.js} +7 -4
  74. package/dist/shared/{qvwwp1mp.js → rw4tpcd0.js} +30 -21
  75. package/dist/shared/{bsek72fg.js → s5qe47a6.js} +3 -0
  76. package/dist/shared/{1d9q9cv2.js → smxzjz60.js} +2 -2
  77. package/dist/shared/{c3mvnq7a.js → th5rnaxw.js} +1 -1
  78. package/dist/shared/{1mxm04fy.js → tyqapcgk.js} +71 -8
  79. package/dist/shared/{zdqf8k5d.js → vsna85qf.js} +1 -1
  80. package/dist/shared/{63qtkrca.js → vy47ryqa.js} +55 -5
  81. package/dist/shared/{xv8qj97g.js → wbdh9qbg.js} +3 -1
  82. package/dist/shared/{2zdxkt7b.js → wp7y8q3y.js} +41 -9
  83. package/dist/shared/{pg4ezgyf.js → xbd9xs6p.js} +2 -2
  84. package/dist/shared/{npdftanb.js → xbvz446k.js} +16 -13
  85. package/dist/shared/{aksb89mn.js → y6tfjk9q.js} +2 -2
  86. package/dist/shared/{3bb7wsqt.js → ycnft340.js} +1 -1
  87. package/dist/shared/{z7s6fpxv.js → ymenf259.js} +1 -1
  88. package/dist/shared/{71n0jmhc.js → ysdw2fg3.js} +10 -2
  89. package/dist/shared/{xqacq37x.js → zecrwdk9.js} +1 -1
  90. package/dist/shared/{3t6kw83m.js → zm2rzqba.js} +2 -1
  91. package/dist/tooling/cli.js +122 -44
  92. package/dist/tooling/driver-run.js +107 -24
  93. package/dist/tooling/github/comment.d.ts +8 -2
  94. package/dist/tooling/migrate/apply.d.ts +17 -2
  95. package/dist/tooling/migrate/drift.d.ts +12 -0
  96. package/dist/tooling/migrate/driver-url.d.ts +17 -0
  97. package/dist/tooling/migrate/index.js +9 -7
  98. package/dist/tooling/migrate/policy.d.ts +17 -5
  99. package/dist/tooling/migrate/pooler-probe.d.ts +23 -0
  100. package/dist/tooling/migrate/pull.d.ts +3 -0
  101. package/dist/tooling/migrate/runner.d.ts +24 -1
  102. package/dist/tooling/shared/{0j0jtse1.js → 09a6vsys.js} +1 -1
  103. package/dist/tooling/shared/{68tf18v4.js → 0v9ptnxp.js} +8 -8
  104. package/dist/tooling/shared/{qartgazh.js → 0wtzps9p.js} +6 -6
  105. package/dist/tooling/shared/{1800eymw.js → 0yhf1db4.js} +6 -6
  106. package/dist/tooling/shared/{s6qha57m.js → 19msgrsc.js} +2 -2
  107. package/dist/tooling/shared/{3yn79q5m.js → 1fwfm5m3.js} +1 -1
  108. package/dist/tooling/shared/{hfq3n3bd.js → 2jdn09e2.js} +55 -5
  109. package/dist/tooling/shared/{v6j1ep6w.js → 2qrte2ct.js} +5 -5
  110. package/dist/tooling/shared/{yb4kd19s.js → 2v4nwnkb.js} +1 -1
  111. package/dist/tooling/shared/{ezqn88nj.js → 33z39trp.js} +106 -23
  112. package/dist/tooling/shared/{0r05zhyq.js → 353vnw9z.js} +5 -5
  113. package/dist/tooling/shared/{m4hhb8aa.js → 390kry9g.js} +2 -2
  114. package/dist/tooling/shared/{rcywj2w8.js → 3f6etb9z.js} +18 -31
  115. package/dist/tooling/shared/{9wsnsvbf.js → 3t2cqmsx.js} +1 -1
  116. package/dist/tooling/shared/{5zrzffnk.js → 4dzvjtpy.js} +1 -1
  117. package/dist/tooling/shared/{am04v8na.js → 4ec0nxvw.js} +10 -10
  118. package/dist/tooling/shared/{04svnxxj.js → 4tg2bn41.js} +59 -78
  119. package/dist/tooling/shared/{829fjxyw.js → 5rah55z0.js} +2 -2
  120. package/dist/tooling/shared/{c0xevn1h.js → 804w1t69.js} +4 -4
  121. package/dist/tooling/shared/{dhatassp.js → 8g0m0rvd.js} +6 -6
  122. package/dist/tooling/shared/{zx3v9phv.js → 8msbtw5s.js} +1 -1
  123. package/dist/tooling/shared/{7pxncnzs.js → 8t22h3h7.js} +2 -2
  124. package/dist/tooling/shared/{wbwq1jsa.js → 91c59b7b.js} +2 -2
  125. package/dist/tooling/shared/{a2xe04bm.js → adk9k6va.js} +1 -1
  126. package/dist/tooling/shared/{w17sj05d.js → ajjd5cwz.js} +2 -2
  127. package/dist/tooling/shared/{35zemrmf.js → angjck01.js} +1 -1
  128. package/dist/tooling/shared/{jsjjz875.js → asnnc3v5.js} +1 -1
  129. package/dist/tooling/shared/{5bmksndh.js → bj1kyqc8.js} +2 -2
  130. package/dist/tooling/shared/{kbqnrs1x.js → bt3tjemb.js} +11 -3
  131. package/dist/tooling/shared/{vwr2y833.js → chk2bh4f.js} +2 -2
  132. package/dist/tooling/shared/{ybdew6j7.js → ckq4z5ax.js} +5 -2
  133. package/dist/tooling/shared/{3rmaz3pq.js → d3rhb24q.js} +1 -1
  134. package/dist/tooling/shared/{mw5npye0.js → d8q00vxg.js} +4 -4
  135. package/dist/tooling/shared/{j664qjrt.js → dx188rxs.js} +2 -2
  136. package/dist/tooling/shared/{8yr7ges1.js → efq6p9df.js} +35 -42
  137. package/dist/tooling/shared/{yhp829bx.js → en6rwzq0.js} +3 -3
  138. package/dist/tooling/shared/{f77nnfqy.js → f2r5g0fs.js} +2 -2
  139. package/dist/tooling/shared/{zs9aymcp.js → f4gep0nk.js} +1 -1
  140. package/dist/tooling/shared/{kz715295.js → f8a010fm.js} +13 -11
  141. package/dist/tooling/shared/{w5d89rb4.js → fdsek6pa.js} +1 -1
  142. package/dist/tooling/shared/fvmrxsd1.js +8 -0
  143. package/dist/tooling/shared/{jpgmjsx3.js → g3xybeqf.js} +4 -3
  144. package/dist/tooling/shared/{wezxf3h8.js → gefmpwsa.js} +1 -1
  145. package/dist/tooling/shared/{jr2nmr81.js → gjj71hfh.js} +23 -20
  146. package/dist/tooling/shared/{9sfbtwsk.js → jgzt69pa.js} +5 -5
  147. package/dist/tooling/shared/{a4pzghha.js → jm30k2ca.js} +10 -8
  148. package/dist/tooling/shared/{shjs0379.js → kayqzpbk.js} +6 -3
  149. package/dist/tooling/shared/{tcwpjvxe.js → kvw1672d.js} +1 -1
  150. package/dist/tooling/shared/{stebqyyd.js → mr1kcgpw.js} +3 -3
  151. package/dist/tooling/shared/{tpssp7ac.js → mwbdf409.js} +1 -1
  152. package/dist/tooling/shared/{fh07z1dt.js → ns9207v3.js} +1 -1
  153. package/dist/tooling/shared/{5p8km5jb.js → p6tjv97k.js} +3 -3
  154. package/dist/tooling/shared/{ytk1tjs0.js → p73evwf0.js} +1 -1
  155. package/dist/tooling/shared/{8m5znpdt.js → phc1nc4g.js} +1 -1
  156. package/dist/tooling/shared/{asgr07mp.js → ppybjw50.js} +79 -16
  157. package/dist/tooling/shared/{t4xveemq.js → qdrn8ghn.js} +5 -4
  158. package/dist/tooling/shared/rd8h01ta.js +20 -0
  159. package/dist/tooling/shared/{7aews1zg.js → rwg8y90m.js} +3 -3
  160. package/dist/tooling/shared/{5x3pf400.js → ry2f3w8p.js} +3 -3
  161. package/dist/tooling/shared/{m3xt8b1h.js → ryc5sa58.js} +8 -8
  162. package/dist/tooling/shared/{57btdkgm.js → ss87qxwv.js} +2 -2
  163. package/dist/tooling/shared/{6vk28rjp.js → vg68k11z.js} +3 -3
  164. package/dist/tooling/shared/{62fxpc1p.js → vms9dta5.js} +73 -89
  165. package/dist/tooling/shared/{gqwevek4.js → xchpnbdv.js} +1 -1
  166. package/dist/tooling/shared/{t6zjeake.js → xn6p01fh.js} +5 -5
  167. package/dist/tooling/shared/{7b3xp9gc.js → xwr6q618.js} +3 -3
  168. package/dist/tooling/shared/{yfedr1zb.js → xx3gmq94.js} +1 -1
  169. package/dist/tooling/shared/{9qr9rkbh.js → y2bd7yfx.js} +21 -13
  170. package/dist/tooling/shared/{nxq95eb6.js → y4gq9rbe.js} +11 -11
  171. package/dist/tooling/shared/{76pv6qff.js → y4vwft92.js} +2 -2
  172. package/dist/tooling/shared/{sjwkt1pg.js → yz68xcmc.js} +1 -1
  173. package/dist/tooling/testing/factories.d.ts +1 -3
  174. package/dist/tooling/testing/index.js +1 -1
  175. package/package.json +2 -1
@@ -7,11 +7,12 @@ import {
7
7
  } from "./fk9vef30.js";
8
8
  import {
9
9
  roleExists
10
- } from "./jpybhf98.js";
10
+ } from "./6xqvesa5.js";
11
11
  import {
12
12
  canonicalTypeName,
13
13
  createdRoleName,
14
14
  createRoleOnce,
15
+ changesRole,
15
16
  DEFAULT_BACKFILL_PAUSE_MS,
16
17
  DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS,
17
18
  boundaryQuery,
@@ -20,10 +21,10 @@ import {
20
21
  planMigration,
21
22
  sessionFailure,
22
23
  assertTargetPolicy
23
- } from "./6nae0spm.js";
24
+ } from "./bdekbvmt.js";
24
25
  import {
25
26
  snakeCase
26
- } from "./gcefca6p.js";
27
+ } from "./f9t9g54z.js";
27
28
  import {
28
29
  catalog,
29
30
  isDomain
@@ -70,9 +71,68 @@ async function runTarget(connection, run, session) {
70
71
  async function assumeRole(connection, role, session) {
71
72
  if (session.setRole === role)
72
73
  return;
74
+ await ensureSetOption(connection, role);
73
75
  await connection.execute(`set role ${quoteIdent2(role)}`);
74
76
  session.setRole = role;
75
77
  }
78
+ async function ensureSetOption(connection, role) {
79
+ const who = await connection.execute("select current_user, rolsuper from pg_roles where rolname = current_user");
80
+ const current = who.rows[0]?.[0];
81
+ if (typeof current !== "string" || current === role)
82
+ return;
83
+ if (roleFlag(who.rows[0]?.[1]))
84
+ return;
85
+ const version = await connection.execute("show server_version_num");
86
+ const versionNum = Number(version.rows[0]?.[0] ?? 0);
87
+ if (Number.isFinite(versionNum) && versionNum >= 160000) {
88
+ const option = await connection.execute(`select am.set_option
89
+ from pg_auth_members am
90
+ join pg_roles r on r.oid = am.roleid
91
+ join pg_roles m on m.oid = am.member
92
+ where r.rolname = $1 and m.rolname = $2`, [role, current]);
93
+ if (roleFlag(option.rows[0]?.[0]))
94
+ return;
95
+ await connection.execute(`grant ${quoteIdent2(role)} to ${quoteIdent2(current)} with set true`);
96
+ return;
97
+ }
98
+ const member = await connection.execute("select pg_has_role($1, $2, 'MEMBER')", [current, role]);
99
+ if (roleFlag(member.rows[0]?.[0]))
100
+ return;
101
+ await connection.execute(`grant ${quoteIdent2(role)} to ${quoteIdent2(current)}`);
102
+ }
103
+ function rolePrivilegeError(error, role) {
104
+ const message = error instanceof Error ? error.message : String(error);
105
+ if (/permission denied to create role/i.test(message)) {
106
+ return new OkmError2("forbidden", "CREATEROLE is required to create roles. Nothing was changed.", {
107
+ kind: "forbidden",
108
+ cause: error,
109
+ fix: {
110
+ summary: "Grant CREATEROLE to the connecting role, or connect as a superuser."
111
+ }
112
+ });
113
+ }
114
+ if (/permission denied to set role/i.test(message)) {
115
+ const name = role ?? "the migration role";
116
+ return new OkmError2("forbidden", `Cannot SET ROLE ${name}: the connecting role lacks the SET option on that membership.`, {
117
+ kind: "forbidden",
118
+ cause: error,
119
+ fix: {
120
+ summary: `Grant ${name} to the connecting role (WITH SET TRUE on Postgres 16+), or connect as a superuser.`
121
+ }
122
+ });
123
+ }
124
+ if (/must be able to SET ROLE|not a member/i.test(message)) {
125
+ const name = role ?? "the migration role";
126
+ return new OkmError2("forbidden", `Cannot SET ROLE ${name}: the connecting role is not a member with the SET option.`, {
127
+ kind: "forbidden",
128
+ cause: error,
129
+ fix: {
130
+ summary: `Grant ${name} to the connecting role (WITH SET TRUE on Postgres 16+), or connect as a superuser.`
131
+ }
132
+ });
133
+ }
134
+ return;
135
+ }
76
136
  async function grantAppMeta(connection, role, session) {
77
137
  if (role === undefined)
78
138
  return;
@@ -86,6 +146,23 @@ async function grantAppMeta(connection, role, session) {
86
146
  async function grantSchema(connection, schema, role) {
87
147
  await connection.execute(`grant usage, create on schema ${quoteIdent2(schema)} to ${quoteIdent2(role)}`);
88
148
  }
149
+ async function ownSchema(connection, schema, role) {
150
+ await ensureSetOption(connection, role);
151
+ await connection.execute("savepoint okm_own_schema");
152
+ try {
153
+ const database = await databaseName(connection);
154
+ await connection.execute(`grant create on database ${quoteIdent2(database)} to ${quoteIdent2(role)}`);
155
+ await connection.execute(`alter schema ${quoteIdent2(schema)} owner to ${quoteIdent2(role)}`);
156
+ await connection.execute("release savepoint okm_own_schema");
157
+ } catch {
158
+ await connection.execute("rollback to savepoint okm_own_schema");
159
+ }
160
+ }
161
+ async function databaseName(connection) {
162
+ const result = await connection.execute("select current_database()");
163
+ const name = result.rows[0]?.[0];
164
+ return typeof name === "string" ? name : "postgres";
165
+ }
89
166
  async function runUnit(connection, unit, run, session) {
90
167
  const only = unit.steps.length === 1 ? unit.steps[0] : undefined;
91
168
  if (only?.step.backfill !== undefined) {
@@ -102,6 +179,9 @@ async function runUnit(connection, unit, run, session) {
102
179
  }
103
180
  for (const item of unit.steps) {
104
181
  current = item.index;
182
+ if (run.migrationRole !== undefined && !changesRole(item.step.sql) && session.setRole !== run.migrationRole && await roleExists(connection, run.migrationRole)) {
183
+ await assumeRole(connection, run.migrationRole, session);
184
+ }
105
185
  await rebuildInvalidIndex(connection, item.step.sql);
106
186
  const created = createdRoleName(item.step.sql);
107
187
  const existed = created !== undefined && await roleExists(connection, created);
@@ -110,12 +190,10 @@ async function runUnit(connection, unit, run, session) {
110
190
  await assertSchemaUsageLanded(connection, item.step.sql);
111
191
  } else if (!existed)
112
192
  await connection.execute(createRoleOnce(item.step.sql));
113
- if (created !== undefined && created === run.migrationRole) {
114
- if (!existed) {
115
- await grantSchema(connection, run.schema ?? "public", created);
116
- await connection.execute(`grant select, insert, update on okm_meta, okm_history, okm_backfill to ${quoteIdent2(created)}`);
117
- }
118
- await assumeRole(connection, created, session);
193
+ if (created !== undefined && created === run.migrationRole && !existed) {
194
+ await grantSchema(connection, run.schema ?? "public", created);
195
+ await ownSchema(connection, run.schema ?? "public", created);
196
+ await connection.execute(`grant select, insert, update on okm_meta, okm_history, okm_backfill to ${quoteIdent2(created)}`);
119
197
  }
120
198
  if (!unit.transactional) {
121
199
  await connection.execute("begin");
@@ -127,6 +205,9 @@ async function runUnit(connection, unit, run, session) {
127
205
  began = false;
128
206
  }
129
207
  }
208
+ if (run.migrationRole !== undefined && session.setRole !== run.migrationRole && await roleExists(connection, run.migrationRole)) {
209
+ await assumeRole(connection, run.migrationRole, session);
210
+ }
130
211
  if (unit.transactional) {
131
212
  for (const item of unit.steps)
132
213
  await writeStep(connection, unit, item);
@@ -182,7 +263,7 @@ async function runBackfill(connection, unit, item, spec, run) {
182
263
  rows,
183
264
  lastKey: committed.lastKey
184
265
  };
185
- run.onProgress?.(`backfill ${unit.migrationId} step ${String(item.index)} batch ${String(batches)} rows ${String(rows)} key ${committed.lastKey ?? "-"}`);
266
+ run.onProgress?.(`backfill ${unit.migrationId} step ${String(item.index + 1)} batch ${String(batches)} rows ${String(rows)} key ${committed.lastKey ?? "-"}`);
186
267
  await run.afterBatch?.(note);
187
268
  if (committed.final)
188
269
  return;
@@ -325,8 +406,7 @@ async function assertSchemaUsageLanded(connection, sql) {
325
406
  join pg_roles r on r.oid = a.grantee
326
407
  where n.nspname = $1 and r.rolname = $2 and a.privilege_type = 'USAGE'
327
408
  )`, [schema, role]);
328
- const landed = found.rows[0]?.[0];
329
- if (landed === "t" || landed === "true")
409
+ if (roleFlag(found.rows[0]?.[0]))
330
410
  return;
331
411
  throw new OkmError2("OKM1825", `Grant usage on schema ${schema} to ${role} did not take effect.`, {
332
412
  kind: "forbidden",
@@ -340,6 +420,10 @@ function failStep(unit, index, error) {
340
420
  if (lost !== undefined) {
341
421
  return new OkmError2(lost.code, `Migration ${unit.migrationId} failed at step ${String(index + 1)}: ${lost.message}`, { kind: lost.kind, cause: error, fix: lost.fix });
342
422
  }
423
+ const privilege = rolePrivilegeError(error, undefined);
424
+ if (privilege !== undefined) {
425
+ return new OkmError2(privilege.code, `Migration ${unit.migrationId} failed at step ${String(index + 1)}: ${privilege.message}`, { kind: privilege.kind, cause: error, fix: privilege.fix });
426
+ }
343
427
  if (error instanceof OkmError2)
344
428
  return error;
345
429
  const message = error instanceof Error ? error.message : "The step failed.";
@@ -359,6 +443,9 @@ function isLockTimeout(error) {
359
443
  function wireTrue(value) {
360
444
  return value === "t" || value === "true";
361
445
  }
446
+ function roleFlag(value) {
447
+ return value === true || value === "t" || value === "true";
448
+ }
362
449
  function unquote(token) {
363
450
  if (token.startsWith('"') && token.endsWith('"')) {
364
451
  return token.slice(1, -1).replaceAll('""', '"');
@@ -598,13 +685,14 @@ async function installSnapshot(connection, head, schema, reference, session, mig
598
685
  for (const step of plan.steps) {
599
686
  if (step.backfill !== undefined)
600
687
  continue;
688
+ if (migrationRole !== undefined && !changesRole(step.sql) && session.setRole !== migrationRole && await roleExists(connection, migrationRole)) {
689
+ await flush();
690
+ await assumeRole(connection, migrationRole, session);
691
+ }
601
692
  const created = createdRoleName(step.sql);
602
693
  const existed = created !== undefined && await roleExists(connection, created);
603
- if (created !== undefined && existed) {
604
- if (created === migrationRole)
605
- await assumeRole(connection, created, session);
694
+ if (created !== undefined && existed)
606
695
  continue;
607
- }
608
696
  const sql = created === undefined ? step.sql : createRoleOnce(step.sql);
609
697
  if (step.transactional && !statementOutsideTransaction(step.sql)) {
610
698
  batch.push(sql);
@@ -616,10 +704,14 @@ async function installSnapshot(connection, head, schema, reference, session, mig
616
704
  if (created !== undefined && created === migrationRole) {
617
705
  await flush();
618
706
  await grantSchema(connection, schema, created);
707
+ await ownSchema(connection, schema, created);
619
708
  await connection.execute(`grant select, insert, update on okm_meta, okm_history, okm_backfill to ${quoteIdent2(created)}`);
620
- await assumeRole(connection, created, session);
621
709
  }
622
710
  }
711
+ if (migrationRole !== undefined && session.setRole !== migrationRole && await roleExists(connection, migrationRole)) {
712
+ await flush();
713
+ await assumeRole(connection, migrationRole, session);
714
+ }
623
715
  await flush();
624
716
  for (const sql of referenceInserts(reference, schema)) {
625
717
  if (!began) {
@@ -754,4 +846,4 @@ function normaliseFunction(object) {
754
846
  };
755
847
  }
756
848
 
757
- export { runTarget, assumeRole, grantAppMeta, grantSchema, assertSchemaUsageLanded, statementOutsideTransaction, readReference, referenceInserts, catalogsEqual, provisionMark, expandProvisioned, provisionHistoryIsKnown, installSnapshot, snapshotDifference };
849
+ export { runTarget, assumeRole, grantAppMeta, grantSchema, ownSchema, assertSchemaUsageLanded, statementOutsideTransaction, readReference, referenceInserts, catalogsEqual, provisionMark, expandProvisioned, provisionHistoryIsKnown, installSnapshot, snapshotDifference };
@@ -3,9 +3,10 @@ import {
3
3
  } from "./cm3w0ywp.js";
4
4
  import {
5
5
  armDriverError,
6
+ logClientError,
6
7
  send,
7
8
  connectClient
8
- } from "./1mxm04fy.js";
9
+ } from "./tyqapcgk.js";
9
10
  import {
10
11
  resetSession2
11
12
  } from "./99qp9vtg.js";
@@ -31,8 +32,10 @@ async function checkout(held) {
31
32
  const client = await connectClient(pool, held.acquireMs, counters);
32
33
  counters.reserved += 1;
33
34
  let lost = false;
34
- const onError = () => {
35
+ const onError = (error) => {
35
36
  lost = true;
37
+ if (error !== undefined)
38
+ logClientError(held.notices, error);
36
39
  };
37
40
  const events = client;
38
41
  events.on("error", onError);
@@ -40,7 +43,7 @@ async function checkout(held) {
40
43
  const session = new WireSession((text, params) => {
41
44
  const pending = send(client, text, params, named, client);
42
45
  const guarded = pending.catch((error) => {
43
- if (isConnectionLost(error) || lost) {
46
+ if (isConnectionLost(error) || lost || pgClosed(error)) {
44
47
  lost = true;
45
48
  if (isConnectionLost(error))
46
49
  throw error;
@@ -77,6 +80,8 @@ async function checkout(held) {
77
80
  try {
78
81
  if (!lost)
79
82
  await reset(session, client);
83
+ } catch {
84
+ lost = true;
80
85
  } finally {
81
86
  counters.reserved = Math.max(0, counters.reserved - 1);
82
87
  events.removeListener("error", onError);
@@ -85,6 +90,9 @@ async function checkout(held) {
85
90
  }
86
91
  };
87
92
  }
93
+ function pgClosed(error) {
94
+ return error instanceof Error && error.message === "Client has encountered a connection error and is not queryable";
95
+ }
88
96
  async function reset(session, client) {
89
97
  await resetSession2(session.depth(), (text) => client.query(text));
90
98
  }
@@ -1,26 +1,29 @@
1
1
  import {
2
2
  open2
3
- } from "./dd5pmnky.js";
3
+ } from "./jvxcp1qk.js";
4
4
  import"./12z7aznj.js";
5
5
  import"./fk9vef30.js";
6
6
  import {
7
7
  createdOf,
8
8
  testing2
9
- } from "./2szgv6yj.js";
9
+ } from "./ns35jqz0.js";
10
10
  import"./n442h54c.js";
11
+ import {
12
+ probeStableBackend
13
+ } from "./e1c55v04.js";
11
14
  import {
12
15
  projectPath,
13
16
  loadConfig,
14
17
  loadSchema
15
- } from "./2zdxkt7b.js";
18
+ } from "./wp7y8q3y.js";
16
19
  import {
20
+ driverUrl,
17
21
  sessionFailure,
18
22
  finishQuiet,
19
23
  assertTargetPolicy,
20
24
  assertDirectConnection,
21
- assertStableBackend,
22
25
  selectTarget
23
- } from "./6nae0spm.js";
26
+ } from "./bdekbvmt.js";
24
27
  import {
25
28
  OkmError2
26
29
  } from "./52szzakm.js";
@@ -41,13 +44,13 @@ async function seedProject(cwd, file, flags) {
41
44
  const allowPooler = flags.allowPooler || config.allowPooler === true;
42
45
  assertDirectConnection(target.url, allowPooler);
43
46
  const schema = await loadSchema(cwd, config.schema);
44
- const pool = open2({ url: target.url, max: 1 });
47
+ const pool = open2({ url: driverUrl(target.url), max: 1 });
45
48
  let harness;
46
49
  try {
47
50
  const held = await pool.reserve?.();
48
51
  if (held !== undefined) {
49
52
  try {
50
- await assertStableBackend(async () => {
53
+ await probeStableBackend(target.url, async () => {
51
54
  const result = await held.execute("select pg_backend_pid()::text");
52
55
  const cell = result.rows[0]?.[0];
53
56
  return cell === null || cell === undefined ? "" : String(cell);
@@ -1,14 +1,14 @@
1
1
  import"./12z7aznj.js";
2
2
  import"./n442h54c.js";
3
3
  import"./fk9vef30.js";
4
- import"./dd5pmnky.js";
4
+ import"./jvxcp1qk.js";
5
5
  import {
6
6
  projectPath,
7
7
  loadConfig
8
- } from "./2zdxkt7b.js";
8
+ } from "./wp7y8q3y.js";
9
9
  import {
10
10
  listTargets
11
- } from "./6nae0spm.js";
11
+ } from "./bdekbvmt.js";
12
12
 
13
13
  // src/tooling/migrate/dev.ts
14
14
  import { mkdirSync } from "node:fs";
@@ -47,6 +47,20 @@ import {
47
47
  OkmError2
48
48
  } from "./52szzakm.js";
49
49
 
50
+ // src/tooling/migrate/driver-url.ts
51
+ function driverUrl(url) {
52
+ let parsed;
53
+ try {
54
+ parsed = new URL(url);
55
+ } catch {
56
+ return url;
57
+ }
58
+ if (!parsed.searchParams.has("pgbouncer"))
59
+ return url;
60
+ parsed.searchParams.delete("pgbouncer");
61
+ return parsed.toString();
62
+ }
63
+
50
64
  // src/tooling/migrate/sql-lex.ts
51
65
  function sqlStatements(sql) {
52
66
  return scan(sql).statements;
@@ -545,6 +559,9 @@ function privilegeSql(before, after, schema, recreated = new Set) {
545
559
  const tighten = tightenColumnSelect(object, after, schema);
546
560
  if (tighten !== undefined)
547
561
  grant.push(tighten);
562
+ const fromPublic = revokePublicExecute(object, schema);
563
+ if (fromPublic !== undefined)
564
+ grant.push(fromPublic);
548
565
  grant.push({ sql: grantStatement(object, schema, false), kind: "grant" });
549
566
  const writes = revokeMatviewWrites(object, after, schema);
550
567
  if (writes !== undefined)
@@ -554,6 +571,9 @@ function privilegeSql(before, after, schema, recreated = new Set) {
554
571
  if (previous !== undefined)
555
572
  continue;
556
573
  if (object.kind === "defaultPrivilege") {
574
+ const fromPublic = revokePublicDefaultExecute(object, schema);
575
+ if (fromPublic !== undefined)
576
+ defaults.push(fromPublic);
557
577
  defaults.push({ sql: defaultStatement(object, schema, false), kind: "grant-default" });
558
578
  }
559
579
  }
@@ -606,6 +626,26 @@ function tightenColumnSelect(object, after, schema) {
606
626
  kind: "revoke"
607
627
  };
608
628
  }
629
+ function revokePublicExecute(object, schema) {
630
+ if (object.identity.privilege.toUpperCase() !== "EXECUTE")
631
+ return;
632
+ if (object.identity.object.kind !== "function")
633
+ return;
634
+ return {
635
+ sql: `revoke execute on ${grantTarget(object.identity.object, schema)} from public`,
636
+ kind: "revoke"
637
+ };
638
+ }
639
+ function revokePublicDefaultExecute(object, schema) {
640
+ if (object.identity.objectKind !== "function")
641
+ return;
642
+ if (object.identity.privilege.toUpperCase() !== "EXECUTE")
643
+ return;
644
+ return {
645
+ sql: `alter default privileges for role ${quoteIdent2(object.identity.forRole)} in schema ${quoteIdent2(schema)} revoke execute on functions from public`,
646
+ kind: "grant-default"
647
+ };
648
+ }
609
649
  function revokeMatviewWrites(object, after, schema) {
610
650
  if (object.identity.privilege.toUpperCase() !== "SELECT")
611
651
  return;
@@ -3082,14 +3122,36 @@ function assertDirectConnection(url, allowPooler) {
3082
3122
  throw new OkmError2("invalid", `Refusing pooler host ${host}. Apply needs a direct connection.`, { fix: { summary: "Use a direct connection, or pass --allow-pooler." } });
3083
3123
  }
3084
3124
  }
3085
- async function assertStableBackend(readPid, allowPooler) {
3125
+ async function assertStableBackend(readPid, allowPooler, options) {
3086
3126
  if (allowPooler)
3087
3127
  return;
3088
- const first = await readPid();
3089
- const second = await readPid();
3090
- if (first === second)
3091
- return;
3092
- throw new OkmError2("OKM1854", `The backend pid changed from ${first} to ${second}. A pooler is handing out a new session between statements.`, { fix: { summary: "Use a direct connection, or pass --allow-pooler." } });
3128
+ const seen = new Set;
3129
+ let first = "";
3130
+ let release;
3131
+ try {
3132
+ for (let i = 0;i < 8; i += 1) {
3133
+ const pid = await readPid();
3134
+ if (first.length === 0)
3135
+ first = pid;
3136
+ seen.add(pid);
3137
+ if (seen.size >= 2) {
3138
+ const second = [...seen].find((value) => value !== first) ?? pid;
3139
+ throw new OkmError2("OKM1854", `The backend pid changed from ${first} to ${second}. A pooler is handing out a new session between statements.`, { fix: { summary: "Use a direct connection, or pass --allow-pooler." } });
3140
+ }
3141
+ if (release !== undefined) {
3142
+ await release();
3143
+ release = undefined;
3144
+ }
3145
+ if (i < 7 && options?.holdBackend !== undefined) {
3146
+ release = await options.holdBackend();
3147
+ }
3148
+ }
3149
+ } finally {
3150
+ if (release !== undefined)
3151
+ await release().catch(() => {
3152
+ return;
3153
+ });
3154
+ }
3093
3155
  }
3094
3156
  function listTargets(config) {
3095
3157
  if (config.database !== undefined && config.targets !== undefined) {
@@ -3179,4 +3241,4 @@ function endpointOf(url) {
3179
3241
  };
3180
3242
  }
3181
3243
 
3182
- export { sqlStatements, transactionControl, sqlIdentifiers, canonicalTypeName, prepareMigrationText, assertMigrationSteps, columnPrivilege, createdRoleName, createRoleOnce, changesRole, DEFAULT_BACKFILL_PAUSE_MS, DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS, batchSizeField, aboutBatchCount, boundaryQuery, checkedCast, classOf, strictestClass, assertExtensionsAvailable, omitManagedObjects, parseReplace, planMigration, formatPlan, parsePlan, staleRenames, sessionFailure, finishQuiet, assertTargetPolicy, assertTargetAlias, assertDirectConnection, assertStableBackend, listTargets, selectTarget };
3244
+ export { driverUrl, sqlStatements, transactionControl, sqlIdentifiers, canonicalTypeName, prepareMigrationText, assertMigrationSteps, columnPrivilege, createdRoleName, createRoleOnce, changesRole, DEFAULT_BACKFILL_PAUSE_MS, DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS, batchSizeField, aboutBatchCount, boundaryQuery, checkedCast, classOf, strictestClass, assertExtensionsAvailable, omitManagedObjects, parseReplace, planMigration, formatPlan, parsePlan, staleRenames, sessionFailure, finishQuiet, assertTargetPolicy, assertTargetAlias, assertDirectConnection, assertStableBackend, listTargets, selectTarget };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  build3
3
- } from "./cexb7z6b.js";
3
+ } from "./gr9sn4za.js";
4
4
  import {
5
5
  attachHttp
6
- } from "./npdftanb.js";
6
+ } from "./xbvz446k.js";
7
7
  import {
8
8
  rejectKeys,
9
9
  readLimit,