okmodel 0.5.1 → 0.6.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 (155) hide show
  1. package/README.md +29 -14
  2. package/dist/adapters/pg/no-inflight-timeout.d.ts +17 -0
  3. package/dist/adapters/pg/postgres-extra.d.ts +22 -0
  4. package/dist/contracts/catalog/document.d.ts +19 -5
  5. package/dist/contracts/catalog/object.d.ts +2 -1
  6. package/dist/contracts/catalog/policy.d.ts +48 -0
  7. package/dist/contracts/catalog/types.d.ts +25 -1
  8. package/dist/contracts/catalog/view.d.ts +2 -0
  9. package/dist/contracts/internal.js +1 -1
  10. package/dist/contracts/location.d.ts +10 -0
  11. package/dist/dialects/pg/column.d.ts +13 -2
  12. package/dist/dialects/pg/ddl.d.ts +37 -1
  13. package/dist/dialects/pg/emit.d.ts +19 -1
  14. package/dist/dialects/pg/errors.d.ts +32 -1
  15. package/dist/dialects/pg/ext/citext.js +1 -1
  16. package/dist/dialects/pg/ext/index.js +1 -1
  17. package/dist/dialects/pg/ext/pg-trgm.js +1 -1
  18. package/dist/dialects/pg/filter-parse.d.ts +5 -4
  19. package/dist/dialects/pg/fn/index.js +2 -2
  20. package/dist/dialects/pg/index.d.ts +2 -1
  21. package/dist/dialects/pg/index.js +23 -17
  22. package/dist/dialects/pg/introspect.d.ts +7 -0
  23. package/dist/dialects/pg/model.d.ts +7 -2
  24. package/dist/dialects/pg/owner.d.ts +42 -0
  25. package/dist/dialects/pg/path-endpoint.d.ts +20 -0
  26. package/dist/dialects/pg/role/check.d.ts +3 -0
  27. package/dist/dialects/pg/role/index.d.ts +71 -6
  28. package/dist/dialects/pg/role/sql.d.ts +11 -0
  29. package/dist/dialects/pg/schema.d.ts +2 -0
  30. package/dist/dialects/pg/table.d.ts +25 -3
  31. package/dist/dialects/pg/tenancy.d.ts +45 -3
  32. package/dist/dialects/pg/view/client.d.ts +19 -1
  33. package/dist/dialects/pg/view/index.d.ts +4 -0
  34. package/dist/dialects/pg/view/index.js +21 -178
  35. package/dist/dialects/pg/view/refresh.d.ts +24 -0
  36. package/dist/runtime/batch.d.ts +1 -1
  37. package/dist/runtime/conflict.d.ts +2 -2
  38. package/dist/runtime/drift.d.ts +9 -0
  39. package/dist/runtime/inflight.d.ts +14 -0
  40. package/dist/runtime/pg/bun.js +4 -3
  41. package/dist/runtime/pg/pg.js +3 -3
  42. package/dist/runtime/pg/pglite.js +4 -3
  43. package/dist/runtime/pg/postgresjs.js +3 -3
  44. package/dist/runtime/plan.d.ts +18 -10
  45. package/dist/runtime/predicate.d.ts +19 -0
  46. package/dist/runtime/safety/index.js +1 -1
  47. package/dist/runtime/tenancy/composite.d.ts +26 -0
  48. package/dist/runtime/tenancy/context.d.ts +16 -0
  49. package/dist/runtime/tenancy/index.d.ts +9 -5
  50. package/dist/runtime/tenancy/index.js +1508 -37
  51. package/dist/runtime/tenancy/path.d.ts +87 -0
  52. package/dist/runtime/tenancy/rls.d.ts +67 -0
  53. package/dist/runtime/tenancy/via.d.ts +22 -0
  54. package/dist/runtime/traits/index.js +17 -11
  55. package/dist/runtime/types.d.ts +13 -1
  56. package/dist/runtime/validate/index.js +4 -4
  57. package/dist/runtime/write.d.ts +1 -1
  58. package/dist/shared/09p049xa.js +702 -0
  59. package/dist/shared/{6qd27mx0.js → 1f0xa3cb.js} +5 -5
  60. package/dist/shared/1nw1f3vs.js +23 -0
  61. package/dist/shared/{96qcj21c.js → 1z04fa85.js} +19 -9
  62. package/dist/shared/{qm7cdtvr.js → 2cg3wnxc.js} +296 -60
  63. package/dist/shared/{dyj86cmj.js → 2jd1sn0e.js} +2 -2
  64. package/dist/shared/{gbtcc2d7.js → 2z7wv5tj.js} +10 -35
  65. package/dist/shared/{k8v5v5wj.js → 32cnm7kn.js} +6 -1
  66. package/dist/shared/{t4tbsy9h.js → 358zsv4r.js} +1 -1
  67. package/dist/shared/{2pqx26h8.js → 3fqwmr09.js} +31 -22
  68. package/dist/shared/{zqrnhwsq.js → 3qnhc7w5.js} +16 -9
  69. package/dist/shared/{r2sp8hrc.js → 5d8t84hg.js} +12 -3
  70. package/dist/shared/{87jvvhnt.js → 5j36z8dn.js} +16 -5
  71. package/dist/shared/{1g0drakv.js → 5yv4k6a6.js} +1 -1
  72. package/dist/shared/8dqhth8c.js +12 -0
  73. package/dist/shared/8etn7d8w.js +62 -0
  74. package/dist/shared/{v36c5g6p.js → 8x72s86k.js} +400 -13
  75. package/dist/shared/{nxdp2ze3.js → 929h19xp.js} +107 -5
  76. package/dist/shared/{gjthds9j.js → 9628a9xt.js} +1 -1
  77. package/dist/shared/{8xsbagad.js → 9s0ypp87.js} +88 -7
  78. package/dist/shared/{efmy2gcg.js → ag4c7516.js} +1 -1
  79. package/dist/shared/b3awxn7h.js +45 -0
  80. package/dist/shared/b4v57v3x.js +9 -0
  81. package/dist/shared/bdk4sty6.js +58 -0
  82. package/dist/shared/{51mvgj8w.js → c58secgj.js} +100 -7
  83. package/dist/shared/d5azex6s.js +6 -0
  84. package/dist/shared/{5fqx9yh4.js → dk8v79p3.js} +3 -1
  85. package/dist/shared/{4m9fn52z.js → ed0b8k4s.js} +1 -1
  86. package/dist/shared/{z2rnngj4.js → ek2wt2dz.js} +8 -5
  87. package/dist/shared/{sycnx8gt.js → f0f1n281.js} +18 -8
  88. package/dist/shared/{f5cw5q5r.js → g90m9b0g.js} +2 -1
  89. package/dist/shared/{pf0d8f77.js → gwn6v667.js} +1 -1
  90. package/dist/shared/{8rsy3hxz.js → h15jx9bc.js} +1 -1
  91. package/dist/shared/{5enne4ma.js → hazqqecq.js} +17 -93
  92. package/dist/shared/{jwtxdj06.js → j3rkk835.js} +32 -9
  93. package/dist/shared/{hf22e0wt.js → j60w0dvr.js} +19 -18
  94. package/dist/shared/{ycatkxmq.js → jgqj11w4.js} +1 -1
  95. package/dist/shared/k68eedmy.js +85 -0
  96. package/dist/shared/{szm0cksa.js → knmes2kh.js} +34 -6
  97. package/dist/shared/{0t22wwh1.js → mb2ycs1p.js} +4 -2
  98. package/dist/shared/{cr74e96e.js → n076ydsc.js} +1 -1
  99. package/dist/shared/{cctw17hj.js → nx9hh281.js} +31 -28
  100. package/dist/shared/{gde73es6.js → p248q79y.js} +56 -55
  101. package/dist/shared/{janx09m5.js → p6bjzq9q.js} +11 -7
  102. package/dist/shared/{6rk2gen5.js → pjr6xrm5.js} +2 -2
  103. package/dist/shared/{wq9n6pwr.js → pz6rj8ay.js} +82 -9
  104. package/dist/shared/{caw8b9d8.js → q3j1tq7a.js} +35 -22
  105. package/dist/shared/{8bwbwy8h.js → qq3899ht.js} +128 -18
  106. package/dist/shared/{tm5ab8we.js → r11n8grd.js} +2 -2
  107. package/dist/shared/r1fdd0f6.js +19 -0
  108. package/dist/shared/{8ktwd3rh.js → r3j8s5y0.js} +1 -1
  109. package/dist/shared/rhs3xe4k.js +7 -0
  110. package/dist/shared/{ntneex2a.js → rpycarcr.js} +6 -6
  111. package/dist/shared/{aj2qxyhj.js → sbdme3vy.js} +14 -2
  112. package/dist/shared/{qv2ydck9.js → t3g0k88x.js} +638 -67
  113. package/dist/shared/{ga3sfy93.js → ttmg09vd.js} +535 -200
  114. package/dist/shared/{kfjp0gck.js → venqrjna.js} +8 -4
  115. package/dist/shared/{fancm5pt.js → vjpnzwjz.js} +138 -15
  116. package/dist/shared/{32zqc1dw.js → vmfkhszw.js} +31 -7
  117. package/dist/shared/{krxx77g0.js → wkme6tep.js} +10 -8
  118. package/dist/shared/{ps7a6h14.js → wt5rzv1h.js} +12 -8
  119. package/dist/shared/wxr8cwns.js +6 -0
  120. package/dist/shared/{6mnp99hx.js → wynzeggs.js} +16 -12
  121. package/dist/shared/x3jc53kg.js +220 -0
  122. package/dist/shared/{sdc7r2ww.js → xq0mvhts.js} +8 -8
  123. package/dist/shared/xqve9598.js +53 -0
  124. package/dist/shared/{47x8x9ze.js → y2t3jktd.js} +2 -2
  125. package/dist/shared/{9x25fxhc.js → y34a8714.js} +1 -1
  126. package/dist/shared/{k7dwkhvq.js → y6jdds7g.js} +4 -1
  127. package/dist/shared/{nkaxvt9q.js → ys0xzyaj.js} +1 -1
  128. package/dist/shared/{c9z5g0fm.js → ywr8hmey.js} +5 -2
  129. package/dist/shared/{g7ez901n.js → ywspezzx.js} +1 -1
  130. package/dist/shared/{9m80mkac.js → ze4hxsmy.js} +1 -1
  131. package/dist/shared/{r8x5qhd7.js → zm0mafk4.js} +10 -4
  132. package/dist/shared/{y005b761.js → zy8r7qvz.js} +6 -2
  133. package/dist/tooling/cli.js +41 -15
  134. package/dist/tooling/migrate/apply.d.ts +14 -0
  135. package/dist/tooling/migrate/check.d.ts +3 -4
  136. package/dist/tooling/migrate/classify.d.ts +4 -0
  137. package/dist/tooling/migrate/config.d.ts +3 -2
  138. package/dist/tooling/migrate/files.d.ts +11 -1
  139. package/dist/tooling/migrate/index.js +14 -14
  140. package/dist/tooling/migrate/lint.d.ts +1 -1
  141. package/dist/tooling/migrate/lost.d.ts +28 -0
  142. package/dist/tooling/migrate/paths.d.ts +14 -0
  143. package/dist/tooling/migrate/plan.d.ts +6 -3
  144. package/dist/tooling/migrate/runner.d.ts +25 -0
  145. package/dist/tooling/migrate/sandbox.d.ts +57 -0
  146. package/dist/tooling/migrate/sql-lex.d.ts +20 -3
  147. package/dist/tooling/migrate/text.d.ts +42 -0
  148. package/dist/tooling/node-version.d.ts +21 -0
  149. package/dist/tooling/testing/facts.d.ts +33 -5
  150. package/dist/tooling/testing/index.d.ts +1 -1
  151. package/dist/tooling/testing/index.js +3 -3
  152. package/dist/tooling/testing/isolation.d.ts +11 -5
  153. package/package.json +5 -2
  154. package/dist/shared/7zwwyh0v.js +0 -448
  155. package/dist/shared/q58y17hy.js +0 -21
@@ -2,11 +2,11 @@ import {
2
2
  assertAuthorDrift2,
3
3
  catalogQuery2,
4
4
  managedRoleOptions2
5
- } from "./sycnx8gt.js";
6
- import"./fancm5pt.js";
7
- import"./ga3sfy93.js";
8
- import"./8xsbagad.js";
9
- import"./8bwbwy8h.js";
5
+ } from "./f0f1n281.js";
6
+ import"./vjpnzwjz.js";
7
+ import"./ttmg09vd.js";
8
+ import"./9s0ypp87.js";
9
+ import"./qq3899ht.js";
10
10
  export {
11
11
  assertAuthorDrift2 as assertAuthorDrift,
12
12
  catalogQuery2 as catalogQuery,
@@ -0,0 +1,23 @@
1
+ import {
2
+ ensureMigrationTables2,
3
+ applyTarget2,
4
+ applyProject2,
5
+ pushProject2,
6
+ backfillTiming2
7
+ } from "./nx9hh281.js";
8
+ import"./p248q79y.js";
9
+ import"./knmes2kh.js";
10
+ import"./vjpnzwjz.js";
11
+ import"./t3g0k88x.js";
12
+ import"./ttmg09vd.js";
13
+ import"./n442h54c.js";
14
+ import"./9s0ypp87.js";
15
+ import"./qq3899ht.js";
16
+ import"./wkme6tep.js";
17
+ export {
18
+ applyProject2 as applyProject,
19
+ applyTarget2 as applyTarget,
20
+ backfillTiming2 as backfillTiming,
21
+ ensureMigrationTables2 as ensureMigrationTables,
22
+ pushProject2 as pushProject
23
+ };
@@ -17,6 +17,9 @@ import net from "node:net";
17
17
  import pg from "pg";
18
18
  var CANCEL_CODE = 80877102;
19
19
  var identity = (value) => value;
20
+ function armDriverError(source, handle = () => {}) {
21
+ source.on("error", handle);
22
+ }
20
23
  var WIRE = {
21
24
  getTypeParser() {
22
25
  return identity;
@@ -42,6 +45,7 @@ function open4(config) {
42
45
  ...config.ssl !== undefined ? { ssl: config.ssl } : {},
43
46
  ...config.searchPath !== undefined ? { options: `-c search_path=${config.searchPath}` } : {}
44
47
  });
48
+ armDriverError(pool);
45
49
  pool.on("connect", (client) => {
46
50
  client.on("notice", (notice) => {
47
51
  notices.push(notice);
@@ -53,7 +57,7 @@ function open4(config) {
53
57
  counters.busy = Math.max(0, counters.busy - 1);
54
58
  }, inflight);
55
59
  const held = { pool, acquireMs, counters, notices, named, isClosed };
56
- const checkout = () => closed ? rejectClosed() : import("./r8x5qhd7.js").then((mod) => mod.checkout(held));
60
+ const checkout = () => closed ? rejectClosed() : import("./zm0mafk4.js").then((mod) => mod.checkout(held));
57
61
  return {
58
62
  capabilities: NODE_POSTGRES_CAPABILITIES,
59
63
  execute(text, params, options) {
@@ -121,24 +125,30 @@ function open4(config) {
121
125
  onNotify(message.payload ?? "");
122
126
  };
123
127
  client.on("notification", onNotification);
128
+ let released = false;
129
+ const release = (dead) => {
130
+ if (released)
131
+ return;
132
+ released = true;
133
+ client.removeListener("notification", onNotification);
134
+ client.removeListener("error", release);
135
+ counters.reserved = Math.max(0, counters.reserved - 1);
136
+ client.release(Boolean(dead));
137
+ };
138
+ armDriverError(client, release);
124
139
  try {
125
140
  await client.query(`LISTEN ${quoted}`);
126
141
  } catch (error) {
127
- counters.reserved = Math.max(0, counters.reserved - 1);
128
- client.release();
142
+ release(false);
129
143
  throw error;
130
144
  }
131
- let stopped = false;
132
145
  return async () => {
133
- if (stopped)
146
+ if (released)
134
147
  return;
135
- stopped = true;
136
148
  try {
137
149
  await client.query(`UNLISTEN ${quoted}`);
138
150
  } finally {
139
- client.removeListener("notification", onNotification);
140
- counters.reserved = Math.max(0, counters.reserved - 1);
141
- client.release();
151
+ release(false);
142
152
  }
143
153
  };
144
154
  }
@@ -1,3 +1,6 @@
1
+ import {
2
+ effectivePredicate
3
+ } from "./b3awxn7h.js";
1
4
  import {
2
5
  assertOperatorFits,
3
6
  textArray,
@@ -16,10 +19,10 @@ import {
16
19
  } from "./n4jqhpqz.js";
17
20
  import {
18
21
  writeWouldValidate
19
- } from "./9m80mkac.js";
22
+ } from "./ze4hxsmy.js";
20
23
  import {
21
24
  encodeJson
22
- } from "./gjthds9j.js";
25
+ } from "./9628a9xt.js";
23
26
  import {
24
27
  withRowFilters,
25
28
  archiveRules,
@@ -32,10 +35,9 @@ import {
32
35
  indexes,
33
36
  quote,
34
37
  isRecord,
35
- effectivePredicate,
36
38
  registerFailFix,
37
39
  fail
38
- } from "./gbtcc2d7.js";
40
+ } from "./2z7wv5tj.js";
39
41
  import {
40
42
  safetyInstalled,
41
43
  runSafety
@@ -118,7 +120,7 @@ async function prepareWrite(host, op, table, input, options, mods, scope) {
118
120
  return {
119
121
  statements: planned.statements,
120
122
  options: callOptions(options),
121
- ...planned.expect !== undefined ? { checked: "expect" } : {},
123
+ ...planned.parentGuard === true ? { checked: "parent" } : planned.expect !== undefined ? { checked: "expect" } : {},
122
124
  finish: (results) => finish(results, planned, table)
123
125
  };
124
126
  } catch (error) {
@@ -151,7 +153,7 @@ async function planWrite(schema, generators, op, tableName, input, options, mods
151
153
  if (op === "update") {
152
154
  return withRowFilters(scope, mods.archive, () => {
153
155
  noteArchive(table, mods.archive);
154
- return planUpdate(schema, table, input, record, mods);
156
+ return planUpdate(schema, table, input, record, mods, scope);
155
157
  });
156
158
  }
157
159
  return withRowFilters(scope, mods.archive, () => {
@@ -166,12 +168,16 @@ async function planInsert(table, input, options, mods, generators, scope, tenanc
166
168
  const rows = (many ? input : [input]).map((row) => insertRow(table, row, allow, tenancy));
167
169
  tenancy?.stamp(table.model.name, rows, scope);
168
170
  fillInsert(table, rows, generators);
169
- const columns = writtenColumns(table, rows, allow, tenancy?.key);
171
+ const columns = writtenColumns(table, rows, allow, tenantKeyList(tenancy));
170
172
  const returning = selectedColumns(table, options.returning);
171
173
  const outputs = outputsOf(returning);
172
174
  const clause = returningClause(returning, undefined);
173
175
  const expect = expectOf(options, mods);
174
176
  const shape = many ? "rows" : "row";
177
+ const path = tenancy?.isPath?.(table.model.name) === true;
178
+ if (path && options.onConflict !== undefined && options.onConflict !== "error") {
179
+ fail("OKM1120", `onConflict on ${table.model.name} is not available. The insert checks the parent.`);
180
+ }
175
181
  if (columns.length === 0) {
176
182
  const text = `insert into ${quote(table.model.sql)} default values${clause}`;
177
183
  return {
@@ -183,8 +189,8 @@ async function planInsert(table, input, options, mods, generators, scope, tenanc
183
189
  keyFields: undefined
184
190
  };
185
191
  }
186
- const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./32zqc1dw.js");
187
- const conflict = conflictMod?.readConflict(table, options.onConflict, allow, tenancy?.key);
192
+ const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./vmfkhszw.js");
193
+ const conflict = conflictMod?.readConflict(table, options.onConflict, allow, tenantKeyList(tenancy));
188
194
  const chunks = chunkRows(rows.map((row) => cellsFor(columns, row)));
189
195
  const statements = [];
190
196
  const keys = [];
@@ -192,7 +198,7 @@ async function planInsert(table, input, options, mods, generators, scope, tenanc
192
198
  for (const chunk of chunks) {
193
199
  const slice = rows.slice(offset, offset + chunk.length);
194
200
  offset += chunk.length;
195
- const built = insertHead(table, columns, chunk);
201
+ const built = path ? pathInsert(table, columns, chunk, scope, tenancy) : insertHead(table, columns, chunk);
196
202
  if (conflict === undefined || conflictMod === undefined) {
197
203
  statements.push(withParams(`${built.text}${clause}`, built.params));
198
204
  continue;
@@ -208,10 +214,11 @@ async function planInsert(table, input, options, mods, generators, scope, tenanc
208
214
  shape,
209
215
  expect,
210
216
  keys: conflict?.kind === "return" ? keys : undefined,
211
- keyFields: conflict?.kind === "return" ? conflict.columns.map((column) => column.field) : undefined
217
+ keyFields: conflict?.kind === "return" ? conflict.columns.map((column) => column.field) : undefined,
218
+ ...path ? { parent: rows.length } : {}
212
219
  };
213
220
  }
214
- function planUpdate(schema, table, input, options, mods) {
221
+ function planUpdate(schema, table, input, options, mods, scope) {
215
222
  const allow = allowSet(table, options.allow);
216
223
  noteGuarded(table, allow, "update");
217
224
  const returning = returningOption(table, options.returning);
@@ -221,8 +228,21 @@ function planUpdate(schema, table, input, options, mods) {
221
228
  const rows = input.map((item) => {
222
229
  const row = updateItem(table, item, allow, schema.tenancy);
223
230
  requireFilter(table, row.where, mods, "update");
231
+ pathAssignment(schema.tenancy, table.model.name, row.set, scope);
224
232
  return { ...row, where: stack(row.where, mods.presets) };
225
233
  });
234
+ const moves = rows.some((row) => pathField(schema.tenancy, table.model.name, row.set) !== undefined);
235
+ if (moves) {
236
+ return {
237
+ statements: [pathListUpdate(schema, table, rows, scope, returning)],
238
+ outputs: parentOutputs(returning),
239
+ shape: returning === undefined ? "count" : "rows",
240
+ expect,
241
+ keys: undefined,
242
+ keyFields: undefined,
243
+ parentGuard: true
244
+ };
245
+ }
226
246
  return {
227
247
  statements: updateList(schema, table, rows, returning),
228
248
  outputs,
@@ -239,12 +259,25 @@ function planUpdate(schema, table, input, options, mods) {
239
259
  requireFilter(table, input.where, mods, "update");
240
260
  if (Object.keys(set).length === 0)
241
261
  fail("OKM1120", "update set is empty.");
262
+ const where = stack(input.where, mods.presets);
263
+ if (pathAssignment(schema.tenancy, table.model.name, set, scope) !== undefined) {
264
+ return {
265
+ statements: [pathSetUpdate(schema, table, where, set, scope, returning)],
266
+ outputs: parentOutputs(returning),
267
+ shape: returning === undefined ? "count" : "rows",
268
+ expect,
269
+ keys: undefined,
270
+ keyFields: undefined,
271
+ parentGuard: true
272
+ };
273
+ }
242
274
  const sql = new Sql;
243
275
  sql.text("update ");
244
276
  sql.text(quote(table.model.sql));
245
277
  sql.text(" as t set ");
246
278
  emitSet(sql, table, set, "t");
247
- emitFilter(schema, table, stack(input.where, mods.presets), sql, "t");
279
+ emitFilter(schema, table, where, sql, "t");
280
+ schema.tenancy?.noteParent?.(table.model.name, set, sql.sink(), scope, (field) => table.columns.get(field)?.sql);
248
281
  sql.text(returningClause(returning, "t"));
249
282
  return {
250
283
  statements: [sql.statement()],
@@ -277,6 +310,8 @@ function planDelete(schema, table, input, options, mods) {
277
310
  };
278
311
  }
279
312
  function finish(results, planned, table) {
313
+ if (planned.parentGuard === true)
314
+ return finishParent(results, planned, table);
280
315
  if (planned.shape === "count") {
281
316
  let count = 0;
282
317
  for (const result of results)
@@ -291,63 +326,164 @@ function finish(results, planned, table) {
291
326
  }
292
327
  const rows = planned.keys === undefined || planned.keyFields === undefined ? decoded : orderByKeys(decoded, planned.keyFields, planned.keys);
293
328
  checkExpect(rows.length, planned.expect, table, "insert");
329
+ if (planned.parent !== undefined && rows.length < planned.parent) {
330
+ throw new OkmError2("OKM1705", `Insert on ${table} did not find a parent in this tenant.`, {
331
+ fix: { summary: "Insert under a parent that belongs to this tenant." }
332
+ });
333
+ }
294
334
  if (planned.shape === "row")
295
335
  return rows[0] ?? null;
296
336
  return rows;
297
337
  }
298
- function orderByKeys(rows, fields, keys) {
299
- const found = new Map;
300
- for (const row of rows)
301
- found.set(fields.map((field) => wireKey(row[field])).join("\x00"), row);
302
- const ordered = [];
303
- for (const key of keys) {
304
- const row = found.get(key.map((value) => value ?? "").join("\x00"));
305
- if (row !== undefined)
306
- ordered.push(row);
338
+ function finishParent(results, planned, table) {
339
+ const row = results[0]?.rows[0];
340
+ const matched = countCell(row?.[0]);
341
+ const updated = countCell(row?.[1]);
342
+ if (updated < matched) {
343
+ throw new OkmError2("OKM1705", `Update on ${table} did not find a parent in this tenant.`, {
344
+ fix: { summary: "Point the foreign key at a parent in this tenant." }
345
+ });
307
346
  }
308
- return ordered;
347
+ if (planned.shape === "count") {
348
+ checkExpect(updated, planned.expect, table, "write");
349
+ return { count: updated };
350
+ }
351
+ const decoded = [];
352
+ if (updated > 0) {
353
+ for (const result of results) {
354
+ for (const item of result.rows)
355
+ decoded.push(decodeRow(planned.outputs, item));
356
+ }
357
+ }
358
+ checkExpect(decoded.length, planned.expect, table, "write");
359
+ if (planned.shape === "row")
360
+ return decoded[0] ?? null;
361
+ return decoded;
309
362
  }
310
- function wireKey(value) {
311
- if (typeof value === "string")
363
+ function countCell(value) {
364
+ if (typeof value === "number")
312
365
  return value;
313
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
314
- return String(value);
315
- }
316
- return "";
366
+ if (typeof value === "bigint")
367
+ return Number(value);
368
+ if (typeof value === "string" && value.length > 0)
369
+ return Number(value);
370
+ return 0;
317
371
  }
318
- function insertHead(table, columns, rows) {
319
- const params = [];
320
- const lists = [];
321
- for (const row of rows) {
322
- const slots = [];
323
- for (const cell of row) {
324
- if (cell.kind === "default") {
325
- slots.push("default");
326
- continue;
327
- }
328
- params.push(cell.kind === "null" ? null : cell.wire);
329
- slots.push(`$${String(params.length)}::${cell.dataType}`);
330
- }
331
- lists.push(`(${slots.join(", ")})`);
372
+ function pathField(tenancy, table, set) {
373
+ if (tenancy?.isPath?.(table) !== true)
374
+ return;
375
+ const field = tenancy.pathOf?.(table)?.[0]?.localField;
376
+ if (field === undefined || !Object.hasOwn(set, field))
377
+ return;
378
+ return field;
379
+ }
380
+ function pathAssignment(tenancy, table, set, scope) {
381
+ const field = pathField(tenancy, table, set);
382
+ if (field === undefined)
383
+ return;
384
+ const value = set[field];
385
+ if (typeof value !== "string" || value.length === 0) {
386
+ throw new OkmError2("OKM1705", `Update on ${table} cannot verify ${field}. Pass the parent id as a string.`, { fix: { summary: "Pass the parent id as a string so the tenant can be checked." } });
332
387
  }
333
- const names = columns.map((column) => quote(column.sql)).join(", ");
388
+ if (scope === undefined || "unscoped" in scope) {
389
+ throw new OkmError2("OKM1705", `Update on ${table} cannot verify ${field} without a tenant.`, {
390
+ fix: { summary: `Call for({ ... }) so ${field} can be checked.` }
391
+ });
392
+ }
393
+ return field;
394
+ }
395
+ function parentOutputs(returning) {
396
+ const outputs = outputsOf(returning);
397
+ if (returning === undefined)
398
+ return outputs;
334
399
  return {
335
- text: `insert into ${quote(table.model.sql)} (${names}) values ${lists.join(", ")}`,
336
- params
400
+ fields: outputs.fields.map((field) => ({ ...field, at: field.at + 2 })),
401
+ includes: outputs.includes
337
402
  };
338
403
  }
339
- function updateList(schema, table, rows, returning) {
340
- if (rows.length === 0)
341
- return [];
342
- const width = Math.max(1, Object.keys(rows[0]?.set ?? {}).length + 2);
343
- const size = Math.max(1, Math.floor(WRITE_PARAM_BUDGET / (width * 2)));
344
- const statements = [];
345
- for (let index = 0;index < rows.length; index += size) {
346
- statements.push(updateSlice(schema, table, rows.slice(index, index + size), returning));
404
+ function pathSetUpdate(schema, table, where, set, scope, returning) {
405
+ const sql = new Sql;
406
+ sql.text("with matched as (select t.ctid as tid from ");
407
+ sql.text(quote(table.model.sql));
408
+ sql.text(" as t");
409
+ emitFilter(schema, table, where, sql, "t");
410
+ sql.text("), updated as (update ");
411
+ sql.text(quote(table.model.sql));
412
+ sql.text(" as t set ");
413
+ emitSet(sql, table, set, "t");
414
+ sql.text(" where t.ctid in (select tid from matched)");
415
+ schema.tenancy?.noteParent?.(table.model.name, set, sql.sink(), scope, (field) => table.columns.get(field)?.sql);
416
+ endParent(sql, returning);
417
+ return sql.statement();
418
+ }
419
+ function pathListUpdate(schema, table, rows, scope, returning) {
420
+ const sql = new Sql;
421
+ sql.text("with matched as (");
422
+ for (let index = 0;index < rows.length; index += 1) {
423
+ const row = rows[index];
424
+ if (row === undefined)
425
+ continue;
426
+ if (index > 0)
427
+ sql.text(" union all ");
428
+ sql.text("select t.ctid as tid, ");
429
+ sql.text(String(index));
430
+ sql.text(" as ord from ");
431
+ sql.text(quote(table.model.sql));
432
+ sql.text(" as t where ");
433
+ emitBare(schema, table, row.where, sql, "t");
347
434
  }
348
- return statements;
435
+ sql.text("), rejected as (");
436
+ let rejected = false;
437
+ for (let index = 0;index < rows.length; index += 1) {
438
+ const row = rows[index];
439
+ if (row === undefined)
440
+ continue;
441
+ if (pathField(schema.tenancy, table.model.name, row.set) === undefined)
442
+ continue;
443
+ if (rejected)
444
+ sql.text(" union all ");
445
+ rejected = true;
446
+ sql.text("select ");
447
+ sql.text(String(index));
448
+ sql.text(" as ord where exists (select 1 from matched where ord = ");
449
+ sql.text(String(index));
450
+ sql.text(")");
451
+ noteParentNot(schema, table, row.set, scope, sql);
452
+ }
453
+ if (!rejected)
454
+ sql.text("select 0 as ord where false");
455
+ sql.text("), updated as (");
456
+ updateSliceBody(schema, table, rows, sql, true);
457
+ sql.text(" and not exists (select 1 from rejected)");
458
+ endParent(sql, returning);
459
+ return sql.statement();
349
460
  }
350
- function updateSlice(schema, table, rows, returning) {
461
+ function noteParentNot(schema, table, set, scope, sql) {
462
+ let first = true;
463
+ schema.tenancy?.noteParent?.(table.model.name, set, {
464
+ text(value) {
465
+ if (first) {
466
+ first = false;
467
+ sql.text(" and not ");
468
+ return;
469
+ }
470
+ sql.text(value);
471
+ },
472
+ param(encoded) {
473
+ sql.param(encoded, "");
474
+ },
475
+ mark() {}
476
+ }, scope, (field) => table.columns.get(field)?.sql);
477
+ }
478
+ function endParent(sql, returning) {
479
+ if (returning === undefined || returning.length === 0) {
480
+ sql.text(" returning 1) select (select count(*)::int from matched), (select count(*)::int from updated)");
481
+ return;
482
+ }
483
+ sql.text(returningClause(returning, "t"));
484
+ sql.text(") select c.matched, c.updated, u.* from (select (select count(*)::int from matched) as matched, (select count(*)::int from updated) as updated) c left join updated u on true");
485
+ }
486
+ function updateSliceBody(schema, table, rows, sql, wrapWhere = false) {
351
487
  const fields = [];
352
488
  const seen = new Set;
353
489
  for (const row of rows) {
@@ -362,7 +498,6 @@ function updateSlice(schema, table, rows, returning) {
362
498
  }
363
499
  if (fields.length === 0)
364
500
  fail("OKM1120", "update set is empty.");
365
- const sql = new Sql;
366
501
  sql.text("update ");
367
502
  sql.text(quote(table.model.sql));
368
503
  sql.text(" as t set ");
@@ -389,7 +524,7 @@ function updateSlice(schema, table, rows, returning) {
389
524
  sql.text(" end");
390
525
  }
391
526
  emitTouch(sql, table, fields.length > 0);
392
- sql.text(" where ");
527
+ sql.text(wrapWhere ? " where (" : " where ");
393
528
  for (let index = 0;index < rows.length; index += 1) {
394
529
  const row = rows[index];
395
530
  if (row === undefined)
@@ -400,6 +535,64 @@ function updateSlice(schema, table, rows, returning) {
400
535
  emitBare(schema, table, row.where, sql, "t");
401
536
  sql.text(")");
402
537
  }
538
+ if (wrapWhere)
539
+ sql.text(")");
540
+ }
541
+ function orderByKeys(rows, fields, keys) {
542
+ const found = new Map;
543
+ for (const row of rows)
544
+ found.set(fields.map((field) => wireKey(row[field])).join("\x00"), row);
545
+ const ordered = [];
546
+ for (const key of keys) {
547
+ const row = found.get(key.map((value) => value ?? "").join("\x00"));
548
+ if (row !== undefined)
549
+ ordered.push(row);
550
+ }
551
+ return ordered;
552
+ }
553
+ function wireKey(value) {
554
+ if (typeof value === "string")
555
+ return value;
556
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
557
+ return String(value);
558
+ }
559
+ return "";
560
+ }
561
+ function insertHead(table, columns, rows) {
562
+ const params = [];
563
+ const lists = [];
564
+ for (const row of rows) {
565
+ const slots = [];
566
+ for (const cell of row) {
567
+ if (cell.kind === "default") {
568
+ slots.push("default");
569
+ continue;
570
+ }
571
+ params.push(cell.kind === "null" ? null : cell.wire);
572
+ slots.push(`$${String(params.length)}::${cell.dataType}`);
573
+ }
574
+ lists.push(`(${slots.join(", ")})`);
575
+ }
576
+ const names = columns.map((column) => quote(column.sql)).join(", ");
577
+ return {
578
+ text: `insert into ${quote(table.model.sql)} (${names}) values ${lists.join(", ")}`,
579
+ params
580
+ };
581
+ }
582
+ function updateList(schema, table, rows, returning) {
583
+ if (rows.length === 0)
584
+ return [];
585
+ const width = Math.max(1, Object.keys(rows[0]?.set ?? {}).length + 2);
586
+ const size = Math.max(1, Math.floor(WRITE_PARAM_BUDGET / (width * 2)));
587
+ const statements = [];
588
+ for (let index = 0;index < rows.length; index += size) {
589
+ statements.push(updateSlice(schema, table, rows.slice(index, index + size), returning));
590
+ }
591
+ return statements;
592
+ }
593
+ function updateSlice(schema, table, rows, returning) {
594
+ const sql = new Sql;
595
+ updateSliceBody(schema, table, rows, sql);
403
596
  sql.text(returningClause(returning, "t"));
404
597
  return sql.statement();
405
598
  }
@@ -609,7 +802,7 @@ function nextFill(fill, generators) {
609
802
  return generators.okid();
610
803
  return fill.call();
611
804
  }
612
- function writtenColumns(table, rows, allow, tenantKey) {
805
+ function writtenColumns(table, rows, allow, tenantKeys) {
613
806
  const present = new Set;
614
807
  for (const row of rows) {
615
808
  for (const key of Object.keys(row))
@@ -617,12 +810,55 @@ function writtenColumns(table, rows, allow, tenantKey) {
617
810
  }
618
811
  const columns = [];
619
812
  for (const column of table.model.columns) {
620
- if (present.has(column.field) && (writable(table, column, allow) || column.fill !== undefined || column.field === tenantKey)) {
813
+ if (present.has(column.field) && (writable(table, column, allow) || column.fill !== undefined || tenantKeys?.includes(column.field) === true)) {
621
814
  columns.push(column);
622
815
  }
623
816
  }
624
817
  return columns;
625
818
  }
819
+ function tenantKeyList(tenancy) {
820
+ if (tenancy === undefined)
821
+ return;
822
+ if (tenancy.keys !== undefined && tenancy.keys.length > 0)
823
+ return tenancy.keys;
824
+ return [tenancy.key];
825
+ }
826
+ function pathInsert(table, columns, rows, scope, tenancy) {
827
+ const sql = new Sql;
828
+ const names = columns.map((column) => quote(column.sql)).join(", ");
829
+ const selected = columns.map((column) => `v.${quote(column.sql)}`).join(", ");
830
+ sql.text(`insert into ${quote(table.model.sql)} (${names}) select ${selected} from (values `);
831
+ for (let row = 0;row < rows.length; row += 1) {
832
+ const cells = rows[row];
833
+ if (cells === undefined)
834
+ continue;
835
+ if (row > 0)
836
+ sql.text(", ");
837
+ sql.text("(");
838
+ for (let index = 0;index < cells.length; index += 1) {
839
+ const cell = cells[index];
840
+ if (index > 0)
841
+ sql.text(", ");
842
+ if (cell === undefined || cell.kind === "default") {
843
+ sql.text("default");
844
+ continue;
845
+ }
846
+ sql.param(cell.kind === "null" ? null : cell.wire, cell.dataType);
847
+ }
848
+ sql.text(")");
849
+ }
850
+ sql.text(`) as v(${names})`);
851
+ tenancy?.lockPredicate?.({
852
+ table: table.model.name,
853
+ fieldSql: (field) => table.columns.get(field)?.sql,
854
+ encode: table.columns.get(tenancy.key)?.encode,
855
+ alias: "v",
856
+ appended: false,
857
+ scope,
858
+ sink: sql.sink()
859
+ });
860
+ return sql.statement();
861
+ }
626
862
  function writeSet(table, value, allow, tenancy) {
627
863
  if (!isRecord(value))
628
864
  fail("OKM1121", "set must be an object of fields.");
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  encodeText,
3
3
  decodeText
4
- } from "./4m9fn52z.js";
4
+ } from "./ed0b8k4s.js";
5
5
  import {
6
6
  required,
7
7
  openColumn
8
- } from "./c9z5g0fm.js";
8
+ } from "./ywr8hmey.js";
9
9
  import {
10
10
  definition,
11
11
  rejected