@vibeorm/sql 5.8.0 → 5.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -99,6 +99,360 @@ function jsonListParameter(params) {
99
99
  function isJsonListParameter(value) {
100
100
  return typeof value === "object" && value !== null && JSON_LIST_PARAMETER in value;
101
101
  }
102
+ // packages/sql/src/json-patch.ts
103
+ import { internalError } from "@vibeorm/schema";
104
+
105
+ // packages/sql/src/ast.ts
106
+ var COALESCE_REF_NAME = "__vibe_coalesce";
107
+ function coalesceRef(params) {
108
+ return { name: COALESCE_REF_NAME, coalesce: params.members };
109
+ }
110
+ var SET_OPERATORS = ["increment", "decrement", "multiply", "divide"];
111
+ function isSetOperation(value) {
112
+ if (typeof value !== "object" || value === null || Array.isArray(value))
113
+ return false;
114
+ const keys = Object.keys(value);
115
+ if (!keys.includes("__op") || !keys.includes("value"))
116
+ return false;
117
+ const candidate = value;
118
+ if (keys.length !== 2 && !(keys.length === 3 && keys.includes("numericType") && candidate.numericType === "Decimal"))
119
+ return false;
120
+ return typeof candidate.__op === "string" && SET_OPERATORS.includes(candidate.__op) && (typeof candidate.value === "number" && Number.isFinite(candidate.value) || typeof candidate.value === "string" || typeof candidate.value === "bigint");
121
+ }
122
+ var LIST_SET_OPERATORS = ["append", "concat"];
123
+ function isListSetOperation(value) {
124
+ if (typeof value !== "object" || value === null || Array.isArray(value))
125
+ return false;
126
+ const keys = Object.keys(value);
127
+ if (keys.length !== 2 || !keys.includes("__listOp") || !keys.includes("value"))
128
+ return false;
129
+ const candidate = value;
130
+ return typeof candidate.__listOp === "string" && LIST_SET_OPERATORS.includes(candidate.__listOp);
131
+ }
132
+ function dbNowMarker(params) {
133
+ return { __dbNow: true, offsetSeconds: params.offsetSeconds };
134
+ }
135
+ function isDbNowMarker(value) {
136
+ if (typeof value !== "object" || value === null || Array.isArray(value))
137
+ return false;
138
+ if (!Object.hasOwn(value, "__dbNow"))
139
+ return false;
140
+ const keys = Object.keys(value);
141
+ if (keys.length !== 2 || !keys.includes("__dbNow") || !keys.includes("offsetSeconds"))
142
+ return false;
143
+ const candidate = value;
144
+ return candidate.__dbNow === true && typeof candidate.offsetSeconds === "number" && Number.isFinite(candidate.offsetSeconds);
145
+ }
146
+ var SQL_DEFAULT_CELL = Object.freeze({ __sqlDefault: true });
147
+ var SQL_KEEP_CELL = Object.freeze({ __sqlKeep: true });
148
+ function isSqlKeepCell(value) {
149
+ return value === SQL_KEEP_CELL;
150
+ }
151
+ function isSqlDefaultCell(value) {
152
+ return value === SQL_DEFAULT_CELL;
153
+ }
154
+ function jsonParamCell(params) {
155
+ return params.storage === "json" ? { __jsonParam: true, text: params.text, storage: "json" } : { __jsonParam: true, text: params.text };
156
+ }
157
+ function isJsonParamCell(value) {
158
+ return typeof value === "object" && value !== null && value.__jsonParam === true;
159
+ }
160
+ function excludedRef(params) {
161
+ return { __excluded: true, column: params.column };
162
+ }
163
+ function isExcludedRef(value) {
164
+ return typeof value === "object" && value !== null && value.__excluded === true;
165
+ }
166
+ function returningColumnName(column) {
167
+ return typeof column === "string" ? column : ("capture" in column) || ("from" in column) ? column.alias : column.name;
168
+ }
169
+ function isOtherRowReturningColumn(column) {
170
+ return typeof column !== "string" && "from" in column;
171
+ }
172
+ function isCaptureReturningColumn(column) {
173
+ return typeof column !== "string" && "capture" in column;
174
+ }
175
+ var ADVISORY_LOCK_RESULT_COLUMN = "acquired";
176
+ function stepColumnRef(params) {
177
+ return { __stepColumn: true, step: params.step, column: params.column };
178
+ }
179
+ function sourceColumnRef(params) {
180
+ return Object.freeze({ __sourceColumn: true, column: params.column });
181
+ }
182
+ function isSourceColumnRef(value) {
183
+ if (typeof value !== "object" || value === null)
184
+ return false;
185
+ const candidate = value;
186
+ return candidate.__sourceColumn === true && typeof candidate.column === "string" && Object.keys(value).length === 2;
187
+ }
188
+ function isStepColumnRef(value) {
189
+ return typeof value === "object" && value !== null && value.__stepColumn === true;
190
+ }
191
+ var CHAIN_VETO_PREFIX = "__vibe_veto:";
192
+ var CHAIN_VETO_TOKEN_PATTERN = /^[a-z][a-z0-9_]*$/;
193
+ function chainVetoToken(params) {
194
+ const { message, nonce } = params;
195
+ if (nonce === "")
196
+ return;
197
+ for (let start = message.indexOf(CHAIN_VETO_PREFIX);start >= 0; start = message.indexOf(CHAIN_VETO_PREFIX, start + 1)) {
198
+ const rest = message.slice(start + CHAIN_VETO_PREFIX.length);
199
+ const match = /^([a-z][a-z0-9_]*):/.exec(rest);
200
+ if (match !== null && rest.startsWith(`${nonce}:`, match[0].length))
201
+ return match[1];
202
+ }
203
+ return;
204
+ }
205
+
206
+ // packages/sql/src/json-path.ts
207
+ function jsonPathString(path) {
208
+ let out = "$";
209
+ for (const segment of path) {
210
+ if (typeof segment === "number") {
211
+ out += `[${segment}]`;
212
+ } else if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment)) {
213
+ out += `.${segment}`;
214
+ } else {
215
+ out += `."${segment.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
216
+ }
217
+ }
218
+ return out;
219
+ }
220
+
221
+ // packages/sql/src/json-patch.ts
222
+ var JSON_PATCH_VETO_TOKEN = "json_patch";
223
+ function isJsonPatchOperation(value) {
224
+ if (typeof value !== "object" || value === null || Array.isArray(value))
225
+ return false;
226
+ if (!Object.hasOwn(value, "__jsonPatch"))
227
+ return false;
228
+ const keys = Object.keys(value);
229
+ if (keys.some((key) => key !== "__jsonPatch" && key !== "steps" && key !== "nonce" && key !== "storage"))
230
+ return false;
231
+ const candidate = value;
232
+ return candidate.__jsonPatch === true && Array.isArray(candidate.steps) && candidate.steps.length > 0 && typeof candidate.nonce === "string" && (candidate.storage === undefined || candidate.storage === "json");
233
+ }
234
+ var editNode = () => ({ kind: "edit", create: false, children: new Map });
235
+ function isJsonObject(value) {
236
+ return typeof value === "object" && value !== null && !Array.isArray(value);
237
+ }
238
+ function foundType(value) {
239
+ if (value === null)
240
+ return "null";
241
+ if (Array.isArray(value))
242
+ return "array";
243
+ switch (typeof value) {
244
+ case "string":
245
+ return "string";
246
+ case "number":
247
+ return "number";
248
+ case "boolean":
249
+ return "boolean";
250
+ }
251
+ throw internalError({ message: "a parsed JSON value has no JSON type" });
252
+ }
253
+ function defineMember(params) {
254
+ Object.defineProperty(params.container, params.key, { value: params.value, enumerable: true, writable: true, configurable: true });
255
+ }
256
+ function applyKnown(params) {
257
+ const { rest, step, index } = params;
258
+ let container = params.value;
259
+ for (let depth = 0;depth < rest.length; depth++) {
260
+ if (!isJsonObject(container))
261
+ return { step: index, path: [...params.at, ...rest.slice(0, depth)], found: foundType(container) };
262
+ const key = rest[depth];
263
+ if (depth === rest.length - 1) {
264
+ if ("set" in step)
265
+ defineMember({ container, key, value: JSON.parse(step.value) });
266
+ else
267
+ delete container[key];
268
+ return;
269
+ }
270
+ if (!Object.hasOwn(container, key)) {
271
+ if (!("set" in step))
272
+ return;
273
+ defineMember({ container, key, value: {} });
274
+ }
275
+ container = container[key];
276
+ }
277
+ return;
278
+ }
279
+ function fold(steps) {
280
+ const root = editNode();
281
+ const checks = [];
282
+ const checked = new Set;
283
+ for (let index = 0;index < steps.length; index++) {
284
+ const step = steps[index];
285
+ const setting = "set" in step;
286
+ const path = setting ? step.set : step.remove;
287
+ if (path.length === 0)
288
+ throw internalError({ message: "a JSON patch step needs a non-empty path" });
289
+ let node = root;
290
+ for (let depth = 0;depth < path.length; depth++) {
291
+ const signature = JSON.stringify(path.slice(0, depth));
292
+ if (!checked.has(signature)) {
293
+ checked.add(signature);
294
+ checks.push({ path: path.slice(0, depth), step: index });
295
+ }
296
+ if (setting)
297
+ node.create = true;
298
+ const key = path[depth];
299
+ if (depth === path.length - 1) {
300
+ node.children.set(key, setting ? { kind: "replace", text: step.value } : { kind: "delete" });
301
+ break;
302
+ }
303
+ let child = node.children.get(key);
304
+ if (child === undefined) {
305
+ child = editNode();
306
+ node.children.set(key, child);
307
+ }
308
+ if (child.kind === "edit") {
309
+ node = child;
310
+ continue;
311
+ }
312
+ if (child.kind === "delete") {
313
+ if (!setting)
314
+ break;
315
+ child = { kind: "replace", text: "{}", parsed: {} };
316
+ node.children.set(key, child);
317
+ }
318
+ child.parsed ??= JSON.parse(child.text);
319
+ const conflict = applyKnown({ value: child.parsed, rest: path.slice(depth + 1), step, at: path.slice(0, depth + 1), index });
320
+ if (conflict !== undefined)
321
+ return { conflict };
322
+ break;
323
+ }
324
+ }
325
+ return { root, checks };
326
+ }
327
+ function planJsonPatch(params) {
328
+ const folded = fold(params.steps);
329
+ return "conflict" in folded ? { conflict: folded.conflict } : { checks: folded.checks };
330
+ }
331
+ var textOf = (node) => node.parsed === undefined ? node.text : JSON.stringify(node.parsed);
332
+ function writtenMembers(node) {
333
+ const out = [];
334
+ for (const [key, child] of node.children) {
335
+ if (child.kind === "replace" || child.kind === "edit" && child.create)
336
+ out.push([key, child]);
337
+ }
338
+ return out;
339
+ }
340
+ function deletedPaths(params) {
341
+ const out = [];
342
+ for (const [key, child] of params.node.children) {
343
+ if (child.kind === "delete")
344
+ out.push([...params.path, key]);
345
+ else if (child.kind === "edit")
346
+ out.push(...deletedPaths({ node: child, path: [...params.path, key] }));
347
+ }
348
+ return out;
349
+ }
350
+ var VETO_TEXT = `${CHAIN_VETO_PREFIX}${JSON_PATCH_VETO_TOKEN}:`;
351
+ function renderJsonPatch(params) {
352
+ const folded = fold(params.patch.steps);
353
+ if ("conflict" in folded) {
354
+ throw internalError({ message: "a JSON patch whose steps conflict reached the renderer; the query builder refuses it" });
355
+ }
356
+ switch (params.dialect.jsonPathMode) {
357
+ case "operators":
358
+ return renderPostgresPatch({ ...params, ...folded });
359
+ case "sqliteExtract":
360
+ return renderSqlitePatch({ ...params, ...folded });
361
+ case "mysqlExtract":
362
+ return renderMysqlPatch({ ...params, ...folded });
363
+ }
364
+ }
365
+ function renderPostgresPatch(params) {
366
+ const { patch, read, collector } = params;
367
+ const json = patch.storage === "json";
368
+ const typeOf = json ? "json_typeof" : "jsonb_typeof";
369
+ const at = (source, path) => path.length === 0 ? source : `${source} #> ${collector.add([...path])}::text[]`;
370
+ const arms = params.checks.map((check, index) => {
371
+ const condition = `${typeOf}(${at(read, check.path)}) <> 'object'`;
372
+ const marker = collector.add(`${patch.nonce}:${index}`);
373
+ return `WHEN ${condition} THEN to_jsonb(CAST(concat('${VETO_TEXT}', ${marker}::text, ':', ${typeOf}(${at(read, check.path)})) AS integer))`;
374
+ });
375
+ const source = json ? `${read}::jsonb` : read;
376
+ const build = (node, path) => {
377
+ const base = at(source, path);
378
+ const start = node.create ? `COALESCE(${base}, '{}'::jsonb)` : base;
379
+ const members = writtenMembers(node);
380
+ if (members.length === 0)
381
+ return start;
382
+ const pairs = members.map(([key, child]) => {
383
+ const name = `${collector.add(key)}::text`;
384
+ return `${name}, ${child.kind === "replace" ? `${collector.add(textOf(child))}::text::jsonb` : build(child, [...path, key])}`;
385
+ });
386
+ return `(${start} || jsonb_build_object(${pairs.join(", ")}))`;
387
+ };
388
+ let result = build(params.root, []);
389
+ const deletes = deletedPaths({ node: params.root, path: [] });
390
+ if (deletes.length > 0)
391
+ result = `(${result}${deletes.map((path) => ` #- ${collector.add(path)}::text[]`).join("")})`;
392
+ const value = `CASE ${arms.join(" ")} ELSE ${result} END`;
393
+ return json ? `(${value})::json` : value;
394
+ }
395
+ function renderSqlitePatch(params) {
396
+ const { patch, read, collector } = params;
397
+ const typeOf = (path) => path.length === 0 ? `json_type(${read})` : `json_type(${read}, ${collector.add(jsonPathString(path))})`;
398
+ const arms = params.checks.map((check, index) => {
399
+ const condition = `${typeOf(check.path)} <> 'object'`;
400
+ const marker = collector.add(`${patch.nonce}:${index}`);
401
+ return `WHEN ${condition} THEN json_extract('{}', '${VETO_TEXT}' || ${marker} || ':' || ${typeOf(check.path)})`;
402
+ });
403
+ const build = (node, path) => {
404
+ const base = path.length === 0 ? read : `json_extract(${read}, ${collector.add(jsonPathString(path))})`;
405
+ const start = node.create ? `COALESCE(${base}, '{}')` : base;
406
+ const members = writtenMembers(node);
407
+ if (members.length === 0)
408
+ return start;
409
+ const pairs = members.map(([key, child]) => {
410
+ const member = collector.add(jsonPathString([key]));
411
+ return `${member}, json(${child.kind === "replace" ? collector.add(textOf(child)) : build(child, [...path, key])})`;
412
+ });
413
+ return `json_set(${start}, ${pairs.join(", ")})`;
414
+ };
415
+ let result = build(params.root, []);
416
+ const deletes = deletedPaths({ node: params.root, path: [] });
417
+ if (deletes.length > 0)
418
+ result = `json_remove(${result}, ${deletes.map((path) => collector.add(jsonPathString(path))).join(", ")})`;
419
+ return `CASE ${arms.join(" ")} ELSE ${result} END`;
420
+ }
421
+ function renderMysqlPatch(params) {
422
+ const { patch, read, collector } = params;
423
+ const at = (path) => path.length === 0 ? read : `JSON_EXTRACT(${read}, ${collector.add(jsonPathString(path))})`;
424
+ const arms = params.checks.map((check, index) => {
425
+ const condition = `JSON_TYPE(${at(check.path)}) <> 'OBJECT'`;
426
+ const marker = collector.add(`${patch.nonce}:${index}`);
427
+ return `WHEN ${condition} THEN JSON_ARRAY(UUID_TO_BIN(CONCAT('${VETO_TEXT}', ${marker}, ':', JSON_TYPE(${at(check.path)}))))`;
428
+ });
429
+ const build = (node, path) => {
430
+ const base = at(path);
431
+ const start = node.create ? `COALESCE(${base}, JSON_OBJECT())` : base;
432
+ const members = writtenMembers(node);
433
+ if (members.length === 0)
434
+ return start;
435
+ const pairs = members.map(([key, child]) => {
436
+ const member = collector.add(jsonPathString([key]));
437
+ return `${member}, ${child.kind === "replace" ? `CAST(${collector.add(textOf(child))} AS JSON)` : build(child, [...path, key])}`;
438
+ });
439
+ return `JSON_SET(${start}, ${pairs.join(", ")})`;
440
+ };
441
+ let result = build(params.root, []);
442
+ const deletes = deletedPaths({ node: params.root, path: [] });
443
+ if (deletes.length > 0)
444
+ result = `JSON_REMOVE(${result}, ${deletes.map((path) => collector.add(jsonPathString(path))).join(", ")})`;
445
+ return `CASE ${arms.join(" ")} ELSE ${result} END`;
446
+ }
447
+ function jsonPatchVetoOf(params) {
448
+ const start = params.message.indexOf(VETO_TEXT);
449
+ if (start < 0)
450
+ return;
451
+ const match = /^([0-9a-f]+):(\d+):([A-Za-z]+)/.exec(params.message.slice(start + VETO_TEXT.length));
452
+ if (match === null)
453
+ return;
454
+ return { nonce: match[1], check: Number(match[2]), found: match[3] };
455
+ }
102
456
  // packages/sql/src/dialects/external-ownership.ts
103
457
  import { unsupportedCapability } from "@vibeorm/schema";
104
458
  function renderExternalOwnershipCatalog(params) {
@@ -1193,7 +1547,7 @@ function renderPostgresPhysicalCatalog(params) {
1193
1547
  let text;
1194
1548
  switch (params.query) {
1195
1549
  case "tables":
1196
- text = `SELECT table_name FROM information_schema.tables WHERE table_schema = ${namespace} AND table_type = 'BASE TABLE' AND table_name <> '_vibe_migrations' AND ${extensionOwnedFilter} ORDER BY table_name`;
1550
+ text = `SELECT table_name, pg_catalog.array_to_json(storage_relation.reloptions)::text AS storage_options, pg_catalog.array_to_json(storage_toast.reloptions)::text AS toast_storage_options, storage_relation.reltoastrelid <> 0 AS has_toast_table FROM information_schema.tables JOIN pg_catalog.pg_namespace storage_namespace ON storage_namespace.nspname = table_schema JOIN pg_catalog.pg_class storage_relation ON storage_relation.relnamespace = storage_namespace.oid AND storage_relation.relname = table_name LEFT JOIN pg_catalog.pg_class storage_toast ON storage_toast.oid = storage_relation.reltoastrelid WHERE table_schema = ${namespace} AND table_type = 'BASE TABLE' AND table_name <> '_vibe_migrations' AND ${extensionOwnedFilter} ORDER BY table_name`;
1197
1551
  break;
1198
1552
  case "views":
1199
1553
  text = `SELECT table_name FROM information_schema.views WHERE table_schema = ${namespace} AND ${extensionOwnedFilter} ORDER BY table_name`;
@@ -1214,7 +1568,7 @@ function renderPostgresPhysicalCatalog(params) {
1214
1568
  text = `SELECT t.typname AS type_name, n.nspname AS type_namespace, e.enumlabel AS enum_value, e.enumsortorder AS sort_order FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_type'::regclass AND d.objid = t.oid AND d.deptype = 'e') ORDER BY t.typname, e.enumsortorder`;
1215
1569
  break;
1216
1570
  case "indexes":
1217
- text = `SELECT tablename AS table_name, indexname AS index_name, indexdef AS index_def, con.conname AS backing_constraint, con.contype AS backing_constraint_type, health.indisvalid AS valid, health.indisready AS ready, health.indimmediate AS immediate, health.indexprs IS NULL AS plain_columns, health.indnkeyatts AS key_count, health.indnatts AS attribute_count FROM pg_indexes ix JOIN pg_catalog.pg_namespace ns ON ns.nspname = ix.schemaname JOIN pg_catalog.pg_class idx ON idx.relnamespace = ns.oid AND idx.relname = ix.indexname JOIN pg_catalog.pg_index health ON health.indexrelid = idx.oid LEFT JOIN pg_catalog.pg_constraint con ON con.conindid = idx.oid AND con.contype IN ('p', 'u', 'x') WHERE schemaname = ${namespace} ORDER BY tablename, indexname`;
1571
+ text = `SELECT tablename AS table_name, indexname AS index_name, indexdef AS index_def, con.conname AS backing_constraint, con.contype AS backing_constraint_type, health.indisvalid AS valid, health.indisready AS ready, health.indimmediate AS immediate, health.indexprs IS NULL AS plain_columns, health.indnkeyatts AS key_count, health.indnatts AS attribute_count, pg_catalog.array_to_json(ARRAY(SELECT included.attname FROM pg_catalog.generate_series(health.indnkeyatts + 1, health.indnatts) AS included_key(ordinal) JOIN pg_catalog.pg_attribute included ON included.attrelid = health.indrelid AND included.attnum = health.indkey[included_key.ordinal - 1] ORDER BY included_key.ordinal))::text AS included_columns FROM pg_indexes ix JOIN pg_catalog.pg_namespace ns ON ns.nspname = ix.schemaname JOIN pg_catalog.pg_class idx ON idx.relnamespace = ns.oid AND idx.relname = ix.indexname JOIN pg_catalog.pg_index health ON health.indexrelid = idx.oid LEFT JOIN pg_catalog.pg_constraint con ON con.conindid = idx.oid AND con.contype IN ('p', 'u', 'x') WHERE schemaname = ${namespace} ORDER BY tablename, indexname`;
1218
1572
  break;
1219
1573
  }
1220
1574
  return { text, values: collector.values, returning: "none" };
@@ -1810,7 +2164,7 @@ function extractIndexKeyList({ sql, open, close, tokens, wrapped = false, remove
1810
2164
  }
1811
2165
  return keys.length === 0 ? undefined : keys;
1812
2166
  }
1813
- function extractStandaloneIndexKeys({ definition, index, postgres = false }) {
2167
+ function extractStandaloneIndexKeys({ definition, index, postgres = false, included }) {
1814
2168
  const tokens = tableDefinitionTokens({ sql: definition });
1815
2169
  const top = tokens.filter((token) => token.depth === 0);
1816
2170
  let position = 0;
@@ -1851,6 +2205,16 @@ function extractStandaloneIndexKeys({ definition, index, postgres = false }) {
1851
2205
  const close = top[position++];
1852
2206
  if (open?.kind !== "symbol" || open.value !== "(" || close?.kind !== "symbol" || close.value !== ")")
1853
2207
  return;
2208
+ let includedCount = 0;
2209
+ if (postgres && word({ value: "INCLUDE" })) {
2210
+ const includeOpen = top[position++];
2211
+ const includeClose = top[position++];
2212
+ if (includeOpen?.kind !== "symbol" || includeOpen.value !== "(" || includeClose?.kind !== "symbol" || includeClose.value !== ")")
2213
+ return;
2214
+ includedCount = 1 + tokens.filter((token) => token.start > includeOpen.start && token.start < includeClose.start && token.depth === 1 && token.value === ",").length;
2215
+ }
2216
+ if (includedCount !== (included ?? 0))
2217
+ return;
1854
2218
  if (postgres && word({ value: "NULLS" }) && (!word({ value: "NOT" }) || !word({ value: "DISTINCT" })))
1855
2219
  return;
1856
2220
  const tail = top[position];
@@ -1877,8 +2241,8 @@ function extractSqliteIndexKeys({ definition, index }) {
1877
2241
  }
1878
2242
 
1879
2243
  // packages/sql/src/dialects/postgres-index-expressions.ts
1880
- function extractPostgresIndexKeys({ definition, index }) {
1881
- return extractStandaloneIndexKeys({ definition, index, postgres: true });
2244
+ function extractPostgresIndexKeys({ definition, index, included }) {
2245
+ return extractStandaloneIndexKeys({ definition, index, postgres: true, ...included === undefined ? {} : { included } });
1882
2246
  }
1883
2247
  function renderPostgresIndexExpressionProbe({ ...params }) {
1884
2248
  const { dialect, operation, probe } = params;
@@ -2147,10 +2511,10 @@ function renderIndexKeys({ dialect, keys, unique }) {
2147
2511
  return `${expression}${key.desc ? " DESC" : ""}`;
2148
2512
  });
2149
2513
  }
2150
- function extractIndexKeys({ dialect, definition, index, backslashEscapes = true }) {
2514
+ function extractIndexKeys({ dialect, definition, index, backslashEscapes = true, included }) {
2151
2515
  switch (dialect) {
2152
2516
  case "postgres":
2153
- return extractPostgresIndexKeys({ definition, index });
2517
+ return extractPostgresIndexKeys({ definition, index, ...included === undefined ? {} : { included } });
2154
2518
  case "sqlite":
2155
2519
  return extractSqliteIndexKeys({ definition, index });
2156
2520
  case "mysql":
@@ -2318,6 +2682,57 @@ UNION ALL SELECT 'EXTERNAL FOREIGN KEY', CONCAT(TABLE_NAME, '.', CONSTRAINT_NAME
2318
2682
  }
2319
2683
  }
2320
2684
  }
2685
+ // packages/sql/src/dialects/migration-shadow-marker.ts
2686
+ import { VibeError as VibeError12, unsupportedCapability as unsupportedCapability14 } from "@vibeorm/schema";
2687
+ var SHADOW_MARKER_CAPABILITIES = Object.freeze({
2688
+ postgres: Object.freeze({ databaseMarker: true }),
2689
+ mysql: Object.freeze({ databaseMarker: false }),
2690
+ sqlite: Object.freeze({ databaseMarker: false })
2691
+ });
2692
+ var AUTOMATIC_SHADOW_NAME_PATTERN = "^(vibeorm_shadow|vibeorm_test_shadow)_[0-9a-f]{32}$";
2693
+ var NAME = new RegExp(AUTOMATIC_SHADOW_NAME_PATTERN);
2694
+ var MICROS = /^\d{1,20}$/;
2695
+ var HOST = /^[A-Za-z0-9._-]{1,253}$/;
2696
+ var MARKER = /^\{"vibeorm":"automatic-shadow","version":1,"database":"([a-z0-9_]+)","adminPid":(\d{1,10}),"adminStarted":(?:"(\d{1,20})"|null),"created":"(\d{1,20})","host":"([A-Za-z0-9._-]{1,253})","pid":(\d{1,10})\}$/;
2697
+ function micros(expression) {
2698
+ return `(extract(epoch FROM ${expression}) * 1000000)::bigint::text`;
2699
+ }
2700
+ function positiveInteger(value) {
2701
+ return Number.isSafeInteger(value) && value > 0;
2702
+ }
2703
+ function automaticShadowMarkerText(params) {
2704
+ const { marker } = params;
2705
+ if (!NAME.test(marker.database) || !positiveInteger(marker.adminPid) || !positiveInteger(marker.pid) || marker.adminStarted !== null && !MICROS.test(marker.adminStarted) || !MICROS.test(marker.created) || !HOST.test(marker.host)) {
2706
+ throw new VibeError12({ code: "VIBE_INTERNAL", message: "Automatic shadow marker fields are outside their closed format", meta: { reason: "shadow-marker-invalid", database: marker.database } });
2707
+ }
2708
+ return `{"vibeorm":"automatic-shadow","version":1,"database":"${marker.database}","adminPid":${marker.adminPid},"adminStarted":${marker.adminStarted === null ? "null" : `"${marker.adminStarted}"`},"created":"${marker.created}","host":"${marker.host}","pid":${marker.pid}}`;
2709
+ }
2710
+ function parseAutomaticShadowMarker(params) {
2711
+ if (typeof params.text !== "string")
2712
+ return;
2713
+ const match = MARKER.exec(params.text);
2714
+ if (match === null)
2715
+ return;
2716
+ const marker = { database: match[1], adminPid: Number(match[2]), adminStarted: match[3] ?? null, created: match[4], host: match[5], pid: Number(match[6]) };
2717
+ return NAME.test(marker.database) && positiveInteger(marker.adminPid) && positiveInteger(marker.pid) ? marker : undefined;
2718
+ }
2719
+ function renderAutomaticShadowOperation(params) {
2720
+ const { dialect, operation } = params;
2721
+ if (!SHADOW_MARKER_CAPABILITIES[dialect.name].databaseMarker)
2722
+ throw unsupportedCapability14({ dialect: dialect.name, feature: "automaticShadowMarker", hint: "only PostgreSQL labels an automatic shadow database with its creator" });
2723
+ const collector = new ParamCollector(dialect);
2724
+ const result = (text) => ({ text, values: collector.values, returning: "none" });
2725
+ switch (operation.kind) {
2726
+ case "session":
2727
+ return result(`SELECT pg_catalog.pg_backend_pid() AS pid, (SELECT ${micros("a.backend_start")} FROM pg_catalog.pg_stat_activity a WHERE a.pid = pg_catalog.pg_backend_pid()) AS started, ${micros("pg_catalog.statement_timestamp()")} AS now`);
2728
+ case "mark":
2729
+ return result(`COMMENT ON DATABASE ${dialect.quoteIdent({ name: operation.marker.database })} IS '${automaticShadowMarkerText({ marker: operation.marker })}'`);
2730
+ case "list":
2731
+ return result(`SELECT d.datname::text AS name, pg_catalog.shobj_description(d.oid, 'pg_database') AS marker, (SELECT count(*)::int FROM pg_catalog.pg_stat_activity a WHERE a.datid = d.oid) AS sessions, ${micros("pg_catalog.statement_timestamp()")} AS now FROM pg_catalog.pg_database d WHERE d.datname ~ ${collector.add(AUTOMATIC_SHADOW_NAME_PATTERN)} ORDER BY d.datname`);
2732
+ case "backends":
2733
+ return result(`SELECT a.pid, ${micros("a.backend_start")} AS started FROM pg_catalog.pg_stat_activity a`);
2734
+ }
2735
+ }
2321
2736
  // packages/sql/src/module-journal.ts
2322
2737
  var MODULE_JOURNAL_COLUMN = "module_origin";
2323
2738
  function renderModuleJournal(params) {
@@ -2355,10 +2770,10 @@ import {
2355
2770
  } from "@vibeorm/schema";
2356
2771
 
2357
2772
  // packages/sql/src/identifiers.ts
2358
- import { VibeError as VibeError12 } from "@vibeorm/schema";
2773
+ import { VibeError as VibeError13 } from "@vibeorm/schema";
2359
2774
  function assertNoNul(params) {
2360
2775
  if (params.name.includes("\x00")) {
2361
- throw new VibeError12({
2776
+ throw new VibeError13({
2362
2777
  code: "VIBE_VALIDATION",
2363
2778
  message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
2364
2779
  meta: { name: params.name.replaceAll("\x00", "\\0") }
@@ -2400,6 +2815,7 @@ var sqliteDialect = {
2400
2815
  jsonPathMode: "sqliteExtract",
2401
2816
  supportedJsonOps: [
2402
2817
  "equals",
2818
+ "exists",
2403
2819
  "string_contains",
2404
2820
  "string_starts_with",
2405
2821
  "string_ends_with",
@@ -2504,11 +2920,11 @@ var SQLITE_SESSION_SQL = Object.freeze({
2504
2920
  foreignKeysOn: "PRAGMA foreign_keys = ON"
2505
2921
  });
2506
2922
  // packages/sql/src/composable.ts
2507
- import { VibeError as VibeError13 } from "@vibeorm/schema";
2923
+ import { VibeError as VibeError14 } from "@vibeorm/schema";
2508
2924
  var fragments = new WeakMap;
2509
2925
  registerSqlHandleCheck({ check: (value) => fragments.has(value) });
2510
2926
  function invalid4(message) {
2511
- throw new VibeError13({ code: "VIBE_VALIDATION", message });
2927
+ throw new VibeError14({ code: "VIBE_VALIDATION", message });
2512
2928
  }
2513
2929
  function construct(parts) {
2514
2930
  const snapshot = Object.freeze([...parts]);
@@ -2606,12 +3022,12 @@ function renderSqlFragment(params) {
2606
3022
  const only = parts.length === 1 ? parts[0] : undefined;
2607
3023
  if (recording && only !== undefined && typeof only !== "string" && "value" in only) {
2608
3024
  if (only.decoder !== undefined && resultScalarContract({ decoder: only.decoder }) === undefined)
2609
- throw new VibeError13({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL cannot compile an opaque encoder whose output controls a greatest NULL frame" });
3025
+ throw new VibeError14({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL cannot compile an opaque encoder whose output controls a greatest NULL frame" });
2610
3026
  if (only.value === null) {
2611
3027
  if (only.decoder !== undefined)
2612
3028
  recordSqlBindingCheck({ collector, check: () => {
2613
3029
  if (encode({ ...only, value: snapshotSqlValue(only.value) }) !== null)
2614
- throw new VibeError13({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL NULL frame encoding changed its declared structure" });
3030
+ throw new VibeError14({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL NULL frame encoding changed its declared structure" });
2615
3031
  } });
2616
3032
  return "NULL";
2617
3033
  }
@@ -2675,14 +3091,14 @@ function sqlUsesIncoming(params) {
2675
3091
  return partsOf(params.fragment).some((part) => typeof part !== "string" && (("row" in part) && part.row === "incoming" || ("greatest" in part) && part.greatest.some((fragment) => sqlUsesIncoming({ fragment }))));
2676
3092
  }
2677
3093
  // packages/sql/src/query-plan.ts
2678
- import { VibeError as VibeError14 } from "@vibeorm/schema";
3094
+ import { VibeError as VibeError15 } from "@vibeorm/schema";
2679
3095
  var queries = new WeakMap;
2680
3096
  var commands = new WeakMap;
2681
3097
  var reusableQueries = new WeakMap;
2682
3098
  var reusableCommands = new WeakMap;
2683
3099
  registerSqlHandleCheck({ check: (value) => queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) });
2684
3100
  function invalid5(message) {
2685
- throw new VibeError14({ code: "VIBE_VALIDATION", message });
3101
+ throw new VibeError15({ code: "VIBE_VALIDATION", message });
2686
3102
  }
2687
3103
  function createSqlQuery(params) {
2688
3104
  if (typeof params.render !== "function" || typeof params.decoder?.decode !== "function")
@@ -2773,14 +3189,14 @@ function compileSqlCommand(params) {
2773
3189
  return { text, values: collector.values };
2774
3190
  }
2775
3191
  // packages/sql/src/exclusion.ts
2776
- import { VibeError as VibeError15, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability14 } from "@vibeorm/schema";
3192
+ import { VibeError as VibeError16, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
2777
3193
  function invalid6(rule) {
2778
- throw new VibeError15({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
3194
+ throw new VibeError16({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
2779
3195
  }
2780
3196
  function compileExclusion(params) {
2781
3197
  const { exclusion: spec, model, dialect } = params;
2782
3198
  if (!dialect.capabilities.exclusionConstraints)
2783
- throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
3199
+ throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
2784
3200
  if (!spec.name || spec.name.includes("\x00"))
2785
3201
  invalid6("name");
2786
3202
  if (!["int8range", "tsrange", "tstzrange"].includes(spec.range.type))
@@ -2833,13 +3249,13 @@ function compileExclusion(params) {
2833
3249
  }
2834
3250
  function renderExclusionSetup(params) {
2835
3251
  if (!params.dialect.capabilities.exclusionConstraints)
2836
- throw unsupportedCapability14({ dialect: params.dialect.name, feature: "exclusionConstraints" });
3252
+ throw unsupportedCapability15({ dialect: params.dialect.name, feature: "exclusionConstraints" });
2837
3253
  return params.exclusion.requiredExtensions.map((name) => `CREATE EXTENSION IF NOT EXISTS ${params.dialect.quoteIdent({ name })};`);
2838
3254
  }
2839
3255
  function renderExclusionDdl(params) {
2840
3256
  const { dialect, table, exclusion, operation } = params;
2841
3257
  if (!dialect.capabilities.exclusionConstraints)
2842
- throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
3258
+ throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
2843
3259
  const target = `ALTER TABLE ${dialect.quoteIdent({ name: table })}`;
2844
3260
  if (operation === "drop")
2845
3261
  return [`${target} DROP CONSTRAINT ${dialect.quoteIdent({ name: exclusion.name })};`];
@@ -2848,111 +3264,18 @@ function renderExclusionDdl(params) {
2848
3264
  function renderExclusionCatalog(params) {
2849
3265
  const { dialect, probe } = params;
2850
3266
  if (!dialect.capabilities.exclusionConstraints)
2851
- throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
3267
+ throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
2852
3268
  const scope = probe === undefined ? `n.nspname = ${dialect.quoteStringLiteral({ value: params.namespace ?? "public" })}` : `t.oid = ${dialect.quoteStringLiteral({ value: probe })}::regclass`;
2853
3269
  return `SELECT t.relname AS table_name, c.conname AS constraint_name, pg_catalog.pg_get_constraintdef(c.oid, true) AS definition, c.convalidated AS validated, i.indisvalid AS valid, i.indisready AS ready FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class t ON t.oid = c.conrelid JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace JOIN pg_catalog.pg_index i ON i.indexrelid = c.conindid WHERE c.contype = 'x' AND ${scope} ORDER BY t.relname, c.conname`;
2854
3270
  }
2855
3271
  function renderExclusionProbe(params) {
2856
3272
  const { dialect, table, probe, definition } = params;
2857
3273
  if (!dialect.capabilities.exclusionConstraints)
2858
- throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
3274
+ throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
2859
3275
  return [`CREATE TEMP TABLE ${dialect.quoteIdent({ name: probe })} (LIKE ${dialect.quoteIdent({ name: params.namespace ?? "public" })}.${dialect.quoteIdent({ name: table })})`, `ALTER TABLE ${dialect.quoteIdent({ name: probe })} ADD CONSTRAINT ${dialect.quoteIdent({ name: `${probe}_x` })} ${definition}`];
2860
3276
  }
2861
- // packages/sql/src/ast.ts
2862
- var SET_OPERATORS = ["increment", "decrement", "multiply", "divide"];
2863
- function isSetOperation(value) {
2864
- if (typeof value !== "object" || value === null || Array.isArray(value))
2865
- return false;
2866
- const keys = Object.keys(value);
2867
- if (!keys.includes("__op") || !keys.includes("value"))
2868
- return false;
2869
- const candidate = value;
2870
- if (keys.length !== 2 && !(keys.length === 3 && keys.includes("numericType") && candidate.numericType === "Decimal"))
2871
- return false;
2872
- return typeof candidate.__op === "string" && SET_OPERATORS.includes(candidate.__op) && (typeof candidate.value === "number" && Number.isFinite(candidate.value) || typeof candidate.value === "string" || typeof candidate.value === "bigint");
2873
- }
2874
- var LIST_SET_OPERATORS = ["append", "concat"];
2875
- function isListSetOperation(value) {
2876
- if (typeof value !== "object" || value === null || Array.isArray(value))
2877
- return false;
2878
- const keys = Object.keys(value);
2879
- if (keys.length !== 2 || !keys.includes("__listOp") || !keys.includes("value"))
2880
- return false;
2881
- const candidate = value;
2882
- return typeof candidate.__listOp === "string" && LIST_SET_OPERATORS.includes(candidate.__listOp);
2883
- }
2884
- function dbNowMarker(params) {
2885
- return { __dbNow: true, offsetSeconds: params.offsetSeconds };
2886
- }
2887
- function isDbNowMarker(value) {
2888
- if (typeof value !== "object" || value === null || Array.isArray(value))
2889
- return false;
2890
- if (!Object.hasOwn(value, "__dbNow"))
2891
- return false;
2892
- const keys = Object.keys(value);
2893
- if (keys.length !== 2 || !keys.includes("__dbNow") || !keys.includes("offsetSeconds"))
2894
- return false;
2895
- const candidate = value;
2896
- return candidate.__dbNow === true && typeof candidate.offsetSeconds === "number" && Number.isFinite(candidate.offsetSeconds);
2897
- }
2898
- var SQL_DEFAULT_CELL = Object.freeze({ __sqlDefault: true });
2899
- var SQL_KEEP_CELL = Object.freeze({ __sqlKeep: true });
2900
- function isSqlKeepCell(value) {
2901
- return value === SQL_KEEP_CELL;
2902
- }
2903
- function isSqlDefaultCell(value) {
2904
- return value === SQL_DEFAULT_CELL;
2905
- }
2906
- function jsonParamCell(params) {
2907
- return params.storage === "json" ? { __jsonParam: true, text: params.text, storage: "json" } : { __jsonParam: true, text: params.text };
2908
- }
2909
- function isJsonParamCell(value) {
2910
- return typeof value === "object" && value !== null && value.__jsonParam === true;
2911
- }
2912
- function excludedRef(params) {
2913
- return { __excluded: true, column: params.column };
2914
- }
2915
- function isExcludedRef(value) {
2916
- return typeof value === "object" && value !== null && value.__excluded === true;
2917
- }
2918
- function returningColumnName(column) {
2919
- return typeof column === "string" ? column : ("capture" in column) ? column.alias : column.name;
2920
- }
2921
- function isCaptureReturningColumn(column) {
2922
- return typeof column !== "string" && "capture" in column;
2923
- }
2924
- var ADVISORY_LOCK_RESULT_COLUMN = "acquired";
2925
- function stepColumnRef(params) {
2926
- return { __stepColumn: true, step: params.step, column: params.column };
2927
- }
2928
- function sourceColumnRef(params) {
2929
- return Object.freeze({ __sourceColumn: true, column: params.column });
2930
- }
2931
- function isSourceColumnRef(value) {
2932
- if (typeof value !== "object" || value === null)
2933
- return false;
2934
- const candidate = value;
2935
- return candidate.__sourceColumn === true && typeof candidate.column === "string" && Object.keys(value).length === 2;
2936
- }
2937
- function isStepColumnRef(value) {
2938
- return typeof value === "object" && value !== null && value.__stepColumn === true;
2939
- }
2940
- var CHAIN_VETO_PREFIX = "__vibe_veto:";
2941
- var CHAIN_VETO_TOKEN_PATTERN = /^[a-z][a-z0-9_]*$/;
2942
- function chainVetoToken(params) {
2943
- const { message, nonce } = params;
2944
- if (nonce === "")
2945
- return;
2946
- for (let start = message.indexOf(CHAIN_VETO_PREFIX);start >= 0; start = message.indexOf(CHAIN_VETO_PREFIX, start + 1)) {
2947
- const rest = message.slice(start + CHAIN_VETO_PREFIX.length);
2948
- const match = /^([a-z][a-z0-9_]*):/.exec(rest);
2949
- if (match !== null && rest.startsWith(`${nonce}:`, match[0].length))
2950
- return match[1];
2951
- }
2952
- return;
2953
- }
2954
3277
  // packages/sql/src/locks.ts
2955
- import { internalError, unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
3278
+ import { internalError as internalError2, unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
2956
3279
  var ROW_LOCK_PROSE = Object.freeze({
2957
3280
  strengths: Object.freeze({
2958
3281
  update: "FOR UPDATE",
@@ -2972,7 +3295,7 @@ function renderRowLockClause(params) {
2972
3295
  if (syntax === null) {
2973
3296
  if (lock.strength === "update" && lock.wait === "wait")
2974
3297
  return null;
2975
- throw unsupportedCapability15({
3298
+ throw unsupportedCapability16({
2976
3299
  dialect: dialect.name,
2977
3300
  feature: `a ${describeRowLock({ lock })} locking read`,
2978
3301
  hint: `${dialect.name} has no locking-read clause at all`
@@ -2980,7 +3303,7 @@ function renderRowLockClause(params) {
2980
3303
  }
2981
3304
  const strengthText = syntax.strengths[lock.strength];
2982
3305
  if (strengthText === undefined) {
2983
- throw unsupportedCapability15({
3306
+ throw unsupportedCapability16({
2984
3307
  dialect: dialect.name,
2985
3308
  feature: `the ${describeRowLock({ lock: { ...lock, wait: "wait" } })} row-lock strength`,
2986
3309
  hint: `${dialect.name} can express: ${Object.keys(syntax.strengths).join(", ")}`
@@ -2988,7 +3311,7 @@ function renderRowLockClause(params) {
2988
3311
  }
2989
3312
  const waitText = syntax.wait[lock.wait];
2990
3313
  if (waitText === undefined) {
2991
- throw unsupportedCapability15({
3314
+ throw unsupportedCapability16({
2992
3315
  dialect: dialect.name,
2993
3316
  feature: `the "${lock.wait}" lock wait policy`,
2994
3317
  hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
@@ -2996,7 +3319,7 @@ function renderRowLockClause(params) {
2996
3319
  }
2997
3320
  if (lock.of !== undefined) {
2998
3321
  if (lock.of.length === 0)
2999
- throw internalError({ message: "a row lock's `of` list cannot be empty" });
3322
+ throw internalError2({ message: "a row lock's `of` list cannot be empty" });
3000
3323
  const tables = lock.of.map((name) => dialect.quoteIdent({ name })).join(", ");
3001
3324
  return `${strengthText} OF ${tables}${waitText}`;
3002
3325
  }
@@ -3013,7 +3336,7 @@ function renderAdvisoryLock(params) {
3013
3336
  const { dialect, statement, collector } = params;
3014
3337
  const functions = dialect.advisoryLockFunctions;
3015
3338
  if (functions === null) {
3016
- throw unsupportedCapability15({
3339
+ throw unsupportedCapability16({
3017
3340
  dialect: dialect.name,
3018
3341
  feature: "advisory locks",
3019
3342
  hint: dialect.name === "mysql" ? "mysql's GET_LOCK is SESSION-scoped \u2014 it survives a rollback, so it cannot be released with the transaction" : `${dialect.name} has no advisory-lock functions`
@@ -3034,11 +3357,11 @@ function renderAdvisoryLocks(params) {
3034
3357
  const { dialect, keys, mode } = params;
3035
3358
  const functions = dialect.advisoryLockFunctions;
3036
3359
  if (functions === null)
3037
- throw unsupportedCapability15({ dialect: dialect.name, feature: "advisory locks" });
3360
+ throw unsupportedCapability16({ dialect: dialect.name, feature: "advisory locks" });
3038
3361
  if (mode !== "exclusive" && mode !== "shared")
3039
- throw internalError({ message: "advisory lock mode must be exclusive or shared" });
3362
+ throw internalError2({ message: "advisory lock mode must be exclusive or shared" });
3040
3363
  if (keys.some((key) => typeof key !== "bigint" || key < ADVISORY_KEY_BOUNDS.single.min || key > ADVISORY_KEY_BOUNDS.single.max)) {
3041
- throw internalError({ message: "advisory lock keys must be signed 64-bit integers" });
3364
+ throw internalError2({ message: "advisory lock keys must be signed 64-bit integers" });
3042
3365
  }
3043
3366
  const collector = new ParamCollector(dialect);
3044
3367
  const binding = collector.add(JSON.stringify(keys.map((key) => key.toString())));
@@ -3055,7 +3378,7 @@ function renderAdvisoryLocks(params) {
3055
3378
  function assertInt32(params) {
3056
3379
  const { value, part } = params;
3057
3380
  if (!Number.isInteger(value) || value < ADVISORY_KEY_BOUNDS.pair.min || value > ADVISORY_KEY_BOUNDS.pair.max) {
3058
- throw internalError({
3381
+ throw internalError2({
3059
3382
  message: `advisory lock ${part} must be a 32-bit integer, got ${String(value)}`,
3060
3383
  meta: { part, value }
3061
3384
  });
@@ -3101,6 +3424,7 @@ var postgresDialect = {
3101
3424
  jsonPathMode: "operators",
3102
3425
  supportedJsonOps: [
3103
3426
  "equals",
3427
+ "exists",
3104
3428
  "string_contains",
3105
3429
  "string_starts_with",
3106
3430
  "string_ends_with",
@@ -3195,6 +3519,7 @@ var mysqlDialect = {
3195
3519
  jsonPathMode: "mysqlExtract",
3196
3520
  supportedJsonOps: [
3197
3521
  "equals",
3522
+ "exists",
3198
3523
  "string_contains",
3199
3524
  "string_starts_with",
3200
3525
  "string_ends_with",
@@ -3225,11 +3550,12 @@ var mysqlDialect = {
3225
3550
  renderEnumOrdinal({ column }) {
3226
3551
  return `${column} + 0`;
3227
3552
  },
3228
- renderEnumMinMax({ fn, column, result }) {
3553
+ renderEnumMinMax({ fn, column, result, when }) {
3229
3554
  const aggregate = fn === "min" ? "MIN" : "MAX";
3555
+ const only = (value) => when === undefined ? value : `CASE WHEN ${when} THEN ${value} END`;
3230
3556
  if (result === "ordinal")
3231
- return `${aggregate}(${column} + 0)`;
3232
- return `SUBSTRING(${aggregate}(CONCAT(LPAD(${column} + 0, 5, '0'), ${column})), 6)`;
3557
+ return `${aggregate}(${only(`${column} + 0`)})`;
3558
+ return `SUBSTRING(${aggregate}(${only(`CONCAT(LPAD(${column} + 0, 5, '0'), ${column})`)}), 6)`;
3233
3559
  },
3234
3560
  placeholder() {
3235
3561
  return "?";
@@ -3253,7 +3579,7 @@ function getSqlDialect(params) {
3253
3579
  return SQL_DIALECTS[params.dialect];
3254
3580
  }
3255
3581
  // packages/sql/src/key-list.ts
3256
- import { unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
3582
+ import { unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
3257
3583
  var SCALAR_ELEMENT_TYPES = Object.freeze({
3258
3584
  String: "text",
3259
3585
  Boolean: "bool",
@@ -3324,7 +3650,7 @@ function postgresKeyListElementType(params) {
3324
3650
  const { dialect, member } = params;
3325
3651
  const type = postgresKeyListElementTypeOf({ dialect, member });
3326
3652
  if (type === undefined) {
3327
- throw unsupportedCapability16({
3653
+ throw unsupportedCapability17({
3328
3654
  dialect: dialect.name,
3329
3655
  feature: `the key-list filter (IN) on a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} member`,
3330
3656
  hint: "IN binds every member as a typed array; filter this field with equality inside OR instead"
@@ -3345,7 +3671,7 @@ function postgresBulkInsertArrayCast(params) {
3345
3671
  const { dialect, member } = params;
3346
3672
  const cast = postgresBulkInsertArrayCastOf({ dialect, member });
3347
3673
  if (cast === undefined) {
3348
- throw unsupportedCapability16({
3674
+ throw unsupportedCapability17({
3349
3675
  dialect: dialect.name,
3350
3676
  feature: `a bulk insert binding a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} column as a typed array`,
3351
3677
  hint: "the query builder keeps VALUES for this column type"
@@ -3365,7 +3691,7 @@ function renderLocalDateCheck({ dialect, table, column }) {
3365
3691
  };
3366
3692
  }
3367
3693
  // packages/sql/src/date-truncation.ts
3368
- import { DATE_TRUNCATION_UNITS, VibeError as VibeError16, internalError as internalError2, unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
3694
+ import { DATE_TRUNCATION_UNITS, VibeError as VibeError17, internalError as internalError3, unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
3369
3695
  var TIME_ZONE_NAME = /^[A-Za-z][A-Za-z0-9_+\-/]{0,63}$/;
3370
3696
  function isTimeZoneName(params) {
3371
3697
  return typeof params.value === "string" && TIME_ZONE_NAME.test(params.value);
@@ -3373,17 +3699,17 @@ function isTimeZoneName(params) {
3373
3699
  function renderDateTruncation(params) {
3374
3700
  const { dialect, reference, truncation } = params;
3375
3701
  if (!dialect.capabilities.dateTruncation) {
3376
- throw unsupportedCapability17({
3702
+ throw unsupportedCapability18({
3377
3703
  dialect: dialect.name,
3378
3704
  feature: "a truncated groupBy key ({ field, truncate, timeZone })",
3379
3705
  hint: "only PostgreSQL and PGlite truncate a timestamp in a named time zone \u2014 group by the plain field and bucket the rows in code instead"
3380
3706
  });
3381
3707
  }
3382
3708
  if (!DATE_TRUNCATION_UNITS.includes(truncation.unit)) {
3383
- throw internalError2({ message: `unknown date truncation unit ${JSON.stringify(truncation.unit)}` });
3709
+ throw internalError3({ message: `unknown date truncation unit ${JSON.stringify(truncation.unit)}` });
3384
3710
  }
3385
3711
  if (!isTimeZoneName({ value: truncation.timeZone })) {
3386
- throw new VibeError16({
3712
+ throw new VibeError17({
3387
3713
  code: "VIBE_VALIDATION",
3388
3714
  message: `time zone ${JSON.stringify(truncation.timeZone)} is not a time-zone name \u2014 use an IANA name such as "Europe/Oslo" or "UTC"`,
3389
3715
  meta: { timeZone: truncation.timeZone }
@@ -3397,20 +3723,129 @@ function renderDateTruncation(params) {
3397
3723
  case "timestamp":
3398
3724
  return `(date_trunc(${unit}, ${reference} AT TIME ZONE 'UTC', ${zone}) AT TIME ZONE 'UTC')`;
3399
3725
  default:
3400
- throw internalError2({ message: `unknown date truncation storage ${JSON.stringify(truncation.storage)}` });
3726
+ throw internalError3({ message: `unknown date truncation storage ${JSON.stringify(truncation.storage)}` });
3727
+ }
3728
+ }
3729
+ // packages/sql/src/native-types.ts
3730
+ var POSTGRES_SCALAR_COLUMN_TYPES = Object.freeze({
3731
+ String: "text",
3732
+ Boolean: "boolean",
3733
+ Int: "integer",
3734
+ BigInt: "bigint",
3735
+ Float: "double precision",
3736
+ Decimal: "numeric",
3737
+ DateTime: "timestamptz",
3738
+ Json: "jsonb",
3739
+ Bytes: "bytea"
3740
+ });
3741
+ var MYSQL_SCALAR_COLUMN_TYPES = Object.freeze({
3742
+ String: `varchar(${mysqlDialect.conflictKeyEquality.defaultStringMaxLength})`,
3743
+ Boolean: "tinyint(1)",
3744
+ Int: "int",
3745
+ BigInt: "bigint",
3746
+ Float: "double",
3747
+ Decimal: "decimal(65,30)",
3748
+ DateTime: "datetime(3)",
3749
+ Json: "json",
3750
+ Bytes: "longblob"
3751
+ });
3752
+ var SQLITE_SCALAR_STORAGE_CLASSES = Object.freeze({
3753
+ String: "TEXT",
3754
+ Boolean: "INTEGER",
3755
+ Int: "INTEGER",
3756
+ BigInt: "INTEGER",
3757
+ Float: "REAL",
3758
+ Decimal: "TEXT",
3759
+ DateTime: "TEXT",
3760
+ Json: "TEXT",
3761
+ Bytes: "BLOB"
3762
+ });
3763
+ var POSTGRES_NATIVE_ALIASES = Object.freeze({
3764
+ charactervarying: "varchar",
3765
+ character: "char",
3766
+ bpchar: "char",
3767
+ int: "integer",
3768
+ int4: "integer",
3769
+ int2: "smallint",
3770
+ int8: "bigint",
3771
+ serial: "integer",
3772
+ bigserial: "bigint",
3773
+ bool: "boolean",
3774
+ float4: "real",
3775
+ float8: "doubleprecision",
3776
+ double: "doubleprecision",
3777
+ doubleprecision: "doubleprecision",
3778
+ decimal: "numeric",
3779
+ timestampwithtimezone: "timestamptz",
3780
+ timestampwithouttimezone: "timestamp",
3781
+ timewithtimezone: "timetz",
3782
+ timewithouttimezone: "time",
3783
+ bitvarying: "varbit"
3784
+ });
3785
+ var POSTGRES_IMPLICIT_MODIFIERS = Object.freeze({
3786
+ bit: "(1)"
3787
+ });
3788
+ var POSTGRES_DEFAULT_PRECISION_SIX = new Set(["timestamp", "time", "timetz"]);
3789
+ function normalizePostgresNativeType(params) {
3790
+ const compact = params.raw.toLowerCase().replace(/\s+/g, "");
3791
+ const match = compact.match(/^([a-z_0-9]+)(\(.*\))?(\[\])?$/);
3792
+ if (match === null)
3793
+ return compact;
3794
+ const base = POSTGRES_NATIVE_ALIASES[match[1]] ?? match[1];
3795
+ const modifier = POSTGRES_DEFAULT_PRECISION_SIX.has(base) && match[2] === "(6)" ? "" : match[2] ?? POSTGRES_IMPLICIT_MODIFIERS[base] ?? "";
3796
+ return `${base}${modifier}${match[3] ?? ""}`;
3797
+ }
3798
+ var POSTGRES_EXACT_EQUIVALENTS = Object.freeze({
3799
+ DateTime: Object.freeze(["timestamptz(6)"])
3800
+ });
3801
+ var MYSQL_NATIVE_ALIASES = Object.freeze({
3802
+ integer: "int",
3803
+ dec: "decimal",
3804
+ numeric: "decimal",
3805
+ bool: "tinyint(1)",
3806
+ boolean: "tinyint(1)",
3807
+ character: "char"
3808
+ });
3809
+ function normalizeMysqlNativeType(params) {
3810
+ const compact = params.raw.toLowerCase().replace(/\s+/g, "");
3811
+ if (compact === "tinyint(1)" || compact === "bool" || compact === "boolean")
3812
+ return "tinyint(1)";
3813
+ const match = compact.match(/^([a-z_0-9]+)(\(.*\))?(.*)$/);
3814
+ if (match === null)
3815
+ return compact;
3816
+ const base = MYSQL_NATIVE_ALIASES[match[1]] ?? match[1];
3817
+ let modifier = match[2] ?? "";
3818
+ if (/^(tinyint|smallint|mediumint|int|bigint)$/.test(base))
3819
+ modifier = "";
3820
+ return `${base}${modifier}${match[3] ?? ""}`;
3821
+ }
3822
+ function canonicalColumnType(params) {
3823
+ const { dialect, scalar, nativeType } = params;
3824
+ switch (dialect) {
3825
+ case "postgres": {
3826
+ const canonical = normalizePostgresNativeType({ raw: POSTGRES_SCALAR_COLUMN_TYPES[scalar] });
3827
+ if (nativeType === undefined)
3828
+ return canonical;
3829
+ const declared = normalizePostgresNativeType({ raw: nativeType });
3830
+ return declared === canonical || POSTGRES_EXACT_EQUIVALENTS[scalar]?.includes(declared) === true ? canonical : declared;
3831
+ }
3832
+ case "mysql":
3833
+ return normalizeMysqlNativeType({ raw: nativeType ?? MYSQL_SCALAR_COLUMN_TYPES[scalar] });
3834
+ case "sqlite":
3835
+ return SQLITE_SCALAR_STORAGE_CLASSES[scalar];
3401
3836
  }
3402
3837
  }
3403
3838
  // packages/sql/src/generated-columns.ts
3404
- import { VibeError as VibeError17, unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
3839
+ import { VibeError as VibeError18, unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
3405
3840
 
3406
3841
  // packages/sql/src/dialects/postgres-generated-columns.ts
3407
- import { unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
3842
+ import { unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
3408
3843
  function renderPostgresGeneratedColumnClause({ generated }) {
3409
3844
  return `GENERATED ALWAYS AS (${generated.expression}) STORED`;
3410
3845
  }
3411
3846
  function renderPostgresGeneratedColumnProbe({ dialect, probe }) {
3412
3847
  if (dialect.name !== "postgres")
3413
- throw unsupportedCapability18({ dialect: dialect.name, feature: "rollbackGeneratedColumnProbe" });
3848
+ throw unsupportedCapability19({ dialect: dialect.name, feature: "rollbackGeneratedColumnProbe" });
3414
3849
  const q = ({ name }) => dialect.quoteIdent({ name });
3415
3850
  let text;
3416
3851
  switch (probe.operation) {
@@ -3431,20 +3866,20 @@ function renderPostgresGeneratedColumnProbe({ dialect, probe }) {
3431
3866
  }
3432
3867
  function renderPostgresDropGeneratedExpression({ dialect, table, column }) {
3433
3868
  if (dialect.name !== "postgres")
3434
- throw unsupportedCapability18({ dialect: dialect.name, feature: "dropGeneratedExpression" });
3869
+ throw unsupportedCapability19({ dialect: dialect.name, feature: "dropGeneratedExpression" });
3435
3870
  return { text: `ALTER TABLE ${dialect.quoteIdent({ name: table.namespace })}.${dialect.quoteIdent({ name: table.name })} ALTER COLUMN ${dialect.quoteIdent({ name: column })} DROP EXPRESSION`, values: [], returning: "none" };
3436
3871
  }
3437
3872
 
3438
3873
  // packages/sql/src/generated-columns.ts
3439
3874
  function renderGeneratedColumnClause({ dialect, generated }) {
3440
3875
  if (!("generatedColumns" in dialect.capabilities) || dialect.capabilities.generatedColumns !== true) {
3441
- throw unsupportedCapability19({ dialect: dialect.name, feature: "generatedColumns" });
3876
+ throw unsupportedCapability20({ dialect: dialect.name, feature: "generatedColumns" });
3442
3877
  }
3443
3878
  if (generated.storage !== "stored") {
3444
- throw unsupportedCapability19({ dialect: dialect.name, feature: "virtualGeneratedColumns" });
3879
+ throw unsupportedCapability20({ dialect: dialect.name, feature: "virtualGeneratedColumns" });
3445
3880
  }
3446
3881
  if (typeof generated.expression !== "string" || generated.expression.trim().length === 0 || generated.expression.includes("\x00")) {
3447
- throw new VibeError17({ code: "VIBE_SCHEMA_INVALID", message: "A generated column requires non-empty, NUL-free SQL", meta: { reason: "generated-expression-invalid" } });
3882
+ throw new VibeError18({ code: "VIBE_SCHEMA_INVALID", message: "A generated column requires non-empty, NUL-free SQL", meta: { reason: "generated-expression-invalid" } });
3448
3883
  }
3449
3884
  switch (dialect.name) {
3450
3885
  case "postgres":
@@ -3456,10 +3891,10 @@ function renderGeneratedColumnClause({ dialect, generated }) {
3456
3891
  }
3457
3892
  }
3458
3893
  // packages/sql/src/row-rules.ts
3459
- import { VibeError as VibeError21, internalError as internalError3, normalizeRowRules, rowRuleGateField, rowRuleIdentities, rowRuleProtectedFields, rowRuleShapeIssue, rowRuleTriggerName, unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
3894
+ import { VibeError as VibeError22, internalError as internalError4, normalizeRowRules, rowRuleGateField, rowRuleIdentities, rowRuleProtectedFields, rowRuleShapeIssue, rowRuleTriggerName, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
3460
3895
 
3461
3896
  // packages/sql/src/dialects/postgres-row-rules.ts
3462
- import { VibeError as VibeError18, unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
3897
+ import { VibeError as VibeError19, unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
3463
3898
  function comparison({ dialect, field }) {
3464
3899
  const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
3465
3900
  const cast = field.type === "Json" ? `::pg_catalog.jsonb${field.isList ? "[]" : ""}` : "";
@@ -3467,7 +3902,7 @@ function comparison({ dialect, field }) {
3467
3902
  }
3468
3903
  function renderPostgresRowRule({ dialect, context }) {
3469
3904
  if (dialect.name !== "postgres")
3470
- throw unsupportedCapability20({ dialect: dialect.name, feature: "postgresRowRules" });
3905
+ throw unsupportedCapability21({ dialect: dialect.name, feature: "postgresRowRules" });
3471
3906
  const { rule, table, functionIdentity, triggerIdentity, protectedFields, gateField } = context;
3472
3907
  const qualify = ({ name }) => `${dialect.quoteIdent({ name: name.namespace })}.${dialect.quoteIdent({ name: name.name })}`;
3473
3908
  const functionName = qualify({ name: functionIdentity.name });
@@ -3484,7 +3919,7 @@ END;`;
3484
3919
  } else {
3485
3920
  const terms = protectedFields.map((field) => comparison({ dialect, field }));
3486
3921
  if (terms.length === 0) {
3487
- throw new VibeError18({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
3922
+ throw new VibeError19({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
3488
3923
  }
3489
3924
  const condition = gateField === undefined ? terms.join(`
3490
3925
  OR `) : `OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
@@ -3519,19 +3954,19 @@ FOR EACH ROW EXECUTE FUNCTION ${functionName}();`;
3519
3954
  }
3520
3955
 
3521
3956
  // packages/sql/src/dialects/sqlite-row-rules.ts
3522
- import { SQLITE_ROW_RULE_ERROR_PREFIX, VibeError as VibeError19, unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
3957
+ import { SQLITE_ROW_RULE_ERROR_PREFIX, VibeError as VibeError20, unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
3523
3958
  function renderSqliteRowRule({ dialect, context }) {
3524
3959
  if (dialect.name !== "sqlite")
3525
- throw unsupportedCapability21({ dialect: dialect.name, feature: "sqliteRowRules" });
3960
+ throw unsupportedCapability22({ dialect: dialect.name, feature: "sqliteRowRules" });
3526
3961
  const { rule, table, triggers, protectedFields, gateField } = context;
3527
3962
  if (table.namespace !== "main")
3528
- throw unsupportedCapability21({ dialect: dialect.name, feature: "fixedNamespaces" });
3963
+ throw unsupportedCapability22({ dialect: dialect.name, feature: "fixedNamespaces" });
3529
3964
  const terms = protectedFields.map((field) => {
3530
3965
  const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
3531
3966
  return `OLD.${column} IS NOT NEW.${column}`;
3532
3967
  });
3533
3968
  if (rule.kind !== "appendOnly" && terms.length === 0)
3534
- throw new VibeError19({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
3969
+ throw new VibeError20({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
3535
3970
  const condition = rule.kind === "appendOnly" ? "" : gateField === undefined ? `WHEN ${terms.join(`
3536
3971
  OR `)}
3537
3972
  ` : `WHEN OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
@@ -3556,11 +3991,11 @@ END;`,
3556
3991
  }
3557
3992
 
3558
3993
  // packages/sql/src/dialects/mysql-row-rules.ts
3559
- import { MYSQL_ROW_RULE_MESSAGE_MAX_BYTES, VibeError as VibeError20, mysqlRowRuleMessageByteLength, unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
3994
+ import { MYSQL_ROW_RULE_MESSAGE_MAX_BYTES, VibeError as VibeError21, mysqlRowRuleMessageByteLength, unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
3560
3995
  function diagnosticLiteral({ name, context, dialect }) {
3561
3996
  const message = `Check constraint '${name}' is violated.`;
3562
3997
  if (mysqlRowRuleMessageByteLength({ triggerName: name }) > MYSQL_ROW_RULE_MESSAGE_MAX_BYTES)
3563
- throw new VibeError20({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${context.rule.name} exceeds MySQL's ${MYSQL_ROW_RULE_MESSAGE_MAX_BYTES}-byte diagnostic limit; supply a shorter name stem`, meta: { model: context.model, rule: context.rule.name } });
3998
+ throw new VibeError21({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${context.rule.name} exceeds MySQL's ${MYSQL_ROW_RULE_MESSAGE_MAX_BYTES}-byte diagnostic limit; supply a shorter name stem`, meta: { model: context.model, rule: context.rule.name } });
3564
3999
  if (message.includes("\\")) {
3565
4000
  const hex = Array.from(new TextEncoder().encode(message), (byte) => byte.toString(16).padStart(2, "0")).join("");
3566
4001
  return `_utf8mb4 X'${hex}'`;
@@ -3569,11 +4004,11 @@ function diagnosticLiteral({ name, context, dialect }) {
3569
4004
  }
3570
4005
  function renderMysqlRowRule({ dialect, context }) {
3571
4006
  if (dialect.name !== "mysql")
3572
- throw unsupportedCapability22({ dialect: dialect.name, feature: "mysqlRowRules" });
4007
+ throw unsupportedCapability23({ dialect: dialect.name, feature: "mysqlRowRules" });
3573
4008
  const { rule, table, triggers, protectedFields, gateField, placement } = context;
3574
4009
  for (const name of Object.keys(placement ?? {}))
3575
4010
  if (!triggers.some(({ identity }) => identity.name === name))
3576
- throw new VibeError20({ code: "VIBE_INTERNAL", message: "A MySQL trigger placement names a trigger this rule does not own", meta: { model: context.model, rule: rule.name, trigger: name } });
4011
+ throw new VibeError21({ code: "VIBE_INTERNAL", message: "A MySQL trigger placement names a trigger this rule does not own", meta: { model: context.model, rule: rule.name, trigger: name } });
3577
4012
  const position = ({ name }) => {
3578
4013
  const entry = placement?.[name];
3579
4014
  if (entry === undefined)
@@ -3586,7 +4021,7 @@ function renderMysqlRowRule({ dialect, context }) {
3586
4021
  return `NOT (OLD.${column} <=> NEW.${column})`;
3587
4022
  });
3588
4023
  if (rule.kind !== "appendOnly" && terms.length === 0)
3589
- throw new VibeError20({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
4024
+ throw new VibeError21({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
3590
4025
  const condition = gateField === undefined ? terms.join(`
3591
4026
  OR `) : `OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
3592
4027
  ${terms.join(`
@@ -3615,10 +4050,10 @@ END;`, values: [] };
3615
4050
  function renderRowRule({ schema, model, rule, dialect, placement }) {
3616
4051
  const issue = rowRuleShapeIssue({ value: rule });
3617
4052
  if (issue !== undefined) {
3618
- throw new VibeError21({ code: "VIBE_SCHEMA_INVALID", message: issue, meta: { model: model.name } });
4053
+ throw new VibeError22({ code: "VIBE_SCHEMA_INVALID", message: issue, meta: { model: model.name } });
3619
4054
  }
3620
4055
  if (placement !== undefined && Object.keys(placement).length > 0 && dialect.name !== "mysql")
3621
- throw unsupportedCapability23({ dialect: dialect.name, feature: "rowRuleTriggerPlacement" });
4056
+ throw unsupportedCapability24({ dialect: dialect.name, feature: "rowRuleTriggerPlacement" });
3622
4057
  const normalized = normalizeRowRules({ schema, model, rules: [rule], dialect: dialect.name })[0];
3623
4058
  switch (dialect.name) {
3624
4059
  case "postgres": {
@@ -3626,7 +4061,7 @@ function renderRowRule({ schema, model, rule, dialect, placement }) {
3626
4061
  const functionIdentity = objects.find((object) => object.role === "function")?.identity;
3627
4062
  const triggerIdentity = objects.find((object) => object.role === "trigger")?.identity;
3628
4063
  if (functionIdentity?.kind !== "function" || triggerIdentity?.kind !== "trigger")
3629
- throw internalError3({ message: "PostgreSQL row rules own one function and one trigger", meta: { model: model.name, rule: normalized.name } });
4064
+ throw internalError4({ message: "PostgreSQL row rules own one function and one trigger", meta: { model: model.name, rule: normalized.name } });
3630
4065
  const gate = rowRuleGateField({ rule: normalized });
3631
4066
  const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
3632
4067
  return renderPostgresRowRule({
@@ -3647,15 +4082,15 @@ function renderRowRule({ schema, model, rule, dialect, placement }) {
3647
4082
  const objects = rowRuleIdentities({ schema, model, rule: normalized, dialect: dialect.name });
3648
4083
  const triggers = objects.map(({ role, identity }) => {
3649
4084
  if (role !== "trigger" || identity.kind !== "trigger")
3650
- throw internalError3({ message: "Event row rules own only triggers", meta: { model: model.name, rule: normalized.name } });
4085
+ throw internalError4({ message: "Event row rules own only triggers", meta: { model: model.name, rule: normalized.name } });
3651
4086
  const event = ["update", "delete"].find((event) => identity.name === rowRuleTriggerName({ rule: normalized, event }));
3652
4087
  if (event === undefined)
3653
- throw internalError3({ message: "Row-rule identity has no declared event", meta: { model: model.name, rule: normalized.name, trigger: identity.name } });
4088
+ throw internalError4({ message: "Row-rule identity has no declared event", meta: { model: model.name, rule: normalized.name, trigger: identity.name } });
3654
4089
  return { event, identity: { kind: "trigger", table: identity.table, name: identity.name } };
3655
4090
  });
3656
4091
  const table = triggers[0]?.identity.table;
3657
4092
  if (table === undefined)
3658
- throw internalError3({ message: "Event row rules require at least one trigger", meta: { model: model.name, rule: normalized.name } });
4093
+ throw internalError4({ message: "Event row rules require at least one trigger", meta: { model: model.name, rule: normalized.name } });
3659
4094
  const gate = rowRuleGateField({ rule: normalized });
3660
4095
  const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
3661
4096
  const context = {
@@ -3686,15 +4121,16 @@ function renderSqliteJsonEquality(params) {
3686
4121
  }
3687
4122
 
3688
4123
  // packages/sql/src/dialects/diagnostics.ts
3689
- import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
4124
+ import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as unsupportedCapability25 } from "@vibeorm/schema";
3690
4125
  var QUERIES = Object.freeze({
3691
4126
  postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
4127
+ postgresServerVersion: { dialect: "postgres", text: `SELECT pg_catalog.current_setting('server_version_num') AS "serverVersionNum"` },
3692
4128
  sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
3693
4129
  });
3694
4130
  function renderDiagnostic(params) {
3695
4131
  const query = QUERIES[params.query];
3696
4132
  if (params.dialect.name !== query.dialect) {
3697
- throw unsupportedCapability24({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
4133
+ throw unsupportedCapability25({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
3698
4134
  }
3699
4135
  return { text: query.text, values: [], returning: "none" };
3700
4136
  }
@@ -3705,7 +4141,7 @@ var CAPTURE_READINESS_SQL = `SELECT pg_catalog.current_setting('server_version_n
3705
4141
  function renderCaptureReadiness(params) {
3706
4142
  const { dialect, tables } = params;
3707
4143
  if (dialect.name !== "postgres") {
3708
- throw unsupportedCapability24({ dialect: dialect.name, feature: "postgresCaptureReadiness", hint: "row capture reads RETURNING old/new, PostgreSQL 18 or newer" });
4144
+ throw unsupportedCapability25({ dialect: dialect.name, feature: "postgresCaptureReadiness", hint: "row capture reads RETURNING old/new, PostgreSQL 18 or newer" });
3709
4145
  }
3710
4146
  const entries = tables.map((entry, index) => {
3711
4147
  const { name, namespace } = entry.table;
@@ -3719,7 +4155,7 @@ function renderCaptureReadiness(params) {
3719
4155
  }
3720
4156
 
3721
4157
  // packages/sql/src/dialects/postgres-diagnostics.ts
3722
- import { VibeError as VibeError22, unsupportedCapability as unsupportedCapability25 } from "@vibeorm/schema";
4158
+ import { VibeError as VibeError23, unsupportedCapability as unsupportedCapability26 } from "@vibeorm/schema";
3723
4159
  function renderPostgresExplain(params) {
3724
4160
  const { dialect, query, options } = params;
3725
4161
  requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
@@ -3754,7 +4190,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
3754
4190
  });
3755
4191
  function requirePostgres(params) {
3756
4192
  if (params.dialect.name !== "postgres") {
3757
- throw unsupportedCapability25({
4193
+ throw unsupportedCapability26({
3758
4194
  dialect: params.dialect.name,
3759
4195
  feature: params.feature,
3760
4196
  hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
@@ -3772,7 +4208,7 @@ function renderPostgresWorkload(params) {
3772
4208
  if (query === "statementSnapshot") {
3773
4209
  const bounded = limit ?? 20;
3774
4210
  if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
3775
- throw new VibeError22({
4211
+ throw new VibeError23({
3776
4212
  code: "VIBE_CONFIG",
3777
4213
  message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
3778
4214
  meta: { limit }
@@ -3791,7 +4227,7 @@ function renderPostgresWorkload(params) {
3791
4227
  function requireArg(params) {
3792
4228
  const { value, name, query } = params;
3793
4229
  if (value === undefined || value.length === 0) {
3794
- throw new VibeError22({
4230
+ throw new VibeError23({
3795
4231
  code: "VIBE_CONFIG",
3796
4232
  message: `the "${query}" diagnostic read requires a ${name}`,
3797
4233
  meta: { query, argument: name }
@@ -3801,10 +4237,10 @@ function requireArg(params) {
3801
4237
  }
3802
4238
 
3803
4239
  // packages/sql/src/dialects/postgres-rls-readiness.ts
3804
- import { unsupportedCapability as unsupportedCapability27 } from "@vibeorm/schema";
4240
+ import { unsupportedCapability as unsupportedCapability28 } from "@vibeorm/schema";
3805
4241
 
3806
4242
  // packages/sql/src/dialects/postgres-rls.ts
3807
- import { RlsContextError, VibeError as VibeError23, assertNativeRlsAdmission, assertValidSchemaIR, unsupportedCapability as unsupportedCapability26, validateRlsContextIR } from "@vibeorm/schema";
4243
+ import { RlsContextError, VibeError as VibeError24, assertNativeRlsAdmission, assertValidSchemaIR, unsupportedCapability as unsupportedCapability27, validateRlsContextIR } from "@vibeorm/schema";
3808
4244
  var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
3809
4245
  var COLUMN_TYPES = {
3810
4246
  String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
@@ -3817,18 +4253,18 @@ var COLUMN_TYPES = {
3817
4253
  };
3818
4254
  function requireRls(params) {
3819
4255
  if (!params.dialect.capabilities.nativeRowLevelSecurity) {
3820
- throw unsupportedCapability26({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
4256
+ throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
3821
4257
  }
3822
4258
  }
3823
4259
  function identifier2(params) {
3824
4260
  if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
3825
- throw new VibeError23({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
4261
+ throw new VibeError24({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
3826
4262
  }
3827
4263
  return params.dialect.quoteIdent({ name: params.name });
3828
4264
  }
3829
4265
  function tableRef(params) {
3830
4266
  if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
3831
- throw unsupportedCapability26({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
4267
+ throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
3832
4268
  }
3833
4269
  return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
3834
4270
  }
@@ -3869,7 +4305,7 @@ function renderRlsColumnType(params) {
3869
4305
  if (native !== undefined)
3870
4306
  return native;
3871
4307
  }
3872
- throw unsupportedCapability26({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
4308
+ throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
3873
4309
  }
3874
4310
  function protectedModel(params) {
3875
4311
  requireRls(params);
@@ -3877,7 +4313,7 @@ function protectedModel(params) {
3877
4313
  assertValidSchemaIR({ schema: params.schema });
3878
4314
  const model = params.schema.models.find((entry) => entry.name === params.model);
3879
4315
  if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
3880
- throw new VibeError23({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
4316
+ throw new VibeError24({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
3881
4317
  }
3882
4318
  return model;
3883
4319
  }
@@ -3910,7 +4346,7 @@ function predicate(params) {
3910
4346
  }).join(" AND ");
3911
4347
  return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
3912
4348
  }
3913
- throw new VibeError23({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
4349
+ throw new VibeError24({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
3914
4350
  }
3915
4351
  function renderRlsPolicy(params) {
3916
4352
  const model = protectedModel(params);
@@ -3951,7 +4387,7 @@ function renderRlsContext(params) {
3951
4387
  }
3952
4388
  function runtimeRoleOf(params) {
3953
4389
  if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
3954
- throw new VibeError23({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
4390
+ throw new VibeError24({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
3955
4391
  }
3956
4392
  return params.role;
3957
4393
  }
@@ -4108,7 +4544,7 @@ ORDER BY c.relname, p.polname`,
4108
4544
  });
4109
4545
  function requireRls2(params) {
4110
4546
  if (!params.dialect.capabilities.nativeRowLevelSecurity) {
4111
- throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
4547
+ throw unsupportedCapability28({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
4112
4548
  }
4113
4549
  }
4114
4550
  function renderRlsReadinessQuery(params) {
@@ -4140,7 +4576,7 @@ function renderRlsPolicyPath(params) {
4140
4576
  }
4141
4577
 
4142
4578
  // packages/sql/src/render.ts
4143
- import { assertSqlNamespace as assertSqlNamespace3, internalError as internalError4, unsupportedCapability as unsupportedCapability28 } from "@vibeorm/schema";
4579
+ import { assertSqlNamespace as assertSqlNamespace3, internalError as internalError5, unsupportedCapability as unsupportedCapability29 } from "@vibeorm/schema";
4144
4580
 
4145
4581
  // packages/sql/src/like.ts
4146
4582
  function escapeLikePattern(params) {
@@ -4228,21 +4664,29 @@ function renderStatement(params) {
4228
4664
  }
4229
4665
  }
4230
4666
  function quoteCol(dialect, column) {
4667
+ if (column.coalesce !== undefined) {
4668
+ if (column.coalesce.length < 2 || column.coalesce.some((member) => member.coalesce !== undefined)) {
4669
+ throw internalError5({ message: "ColRef.coalesce needs two or more plain member references" });
4670
+ }
4671
+ return `COALESCE(${column.coalesce.map((member) => quoteCol(dialect, member)).join(", ")})`;
4672
+ }
4231
4673
  const name = dialect.quoteIdent({ name: column.name });
4232
4674
  const ref = column.table === undefined ? name : `${dialect.quoteIdent({ name: column.table })}.${name}`;
4233
4675
  if (column.truncate !== undefined)
4234
4676
  return renderDateTruncation({ dialect, reference: ref, truncation: column.truncate });
4677
+ if (column.groupKey !== undefined)
4678
+ return renderGroupKey({ dialect, reference: ref, key: column.groupKey });
4235
4679
  if (column.enumOrdinal === true)
4236
4680
  return dialect.renderEnumOrdinal({ column: ref });
4237
4681
  if (column.castReal === true) {
4238
4682
  if (dialect.name !== "sqlite") {
4239
- throw internalError4({ message: "ColRef.castReal is a sqlite Decimal construct" });
4683
+ throw internalError5({ message: "ColRef.castReal is a sqlite Decimal construct" });
4240
4684
  }
4241
4685
  return `CAST(${ref} AS REAL)`;
4242
4686
  }
4243
4687
  if (column.castJsonb !== undefined) {
4244
4688
  if (dialect.name !== "postgres") {
4245
- throw internalError4({ message: "ColRef.castJsonb is a postgres json construct" });
4689
+ throw internalError5({ message: "ColRef.castJsonb is a postgres json construct" });
4246
4690
  }
4247
4691
  return `${ref}::${column.castJsonb}`;
4248
4692
  }
@@ -4250,7 +4694,9 @@ function quoteCol(dialect, column) {
4250
4694
  }
4251
4695
  function typedOrderRef(params) {
4252
4696
  const { statement, column } = params;
4253
- if (column.table !== undefined || column.castReal === true || column.castJsonb !== undefined || column.truncate !== undefined)
4697
+ if (column.coalesce !== undefined)
4698
+ return { ...column, coalesce: column.coalesce.map((member) => typedOrderRef({ statement, column: member })) };
4699
+ if (column.table !== undefined || column.castReal === true || column.castJsonb !== undefined || column.truncate !== undefined || column.groupKey !== undefined)
4254
4700
  return column;
4255
4701
  const renamed = statement.columns.some((projected) => (projected.alias ?? projected.name) === column.name && (projected.transform !== undefined || projected.alias !== undefined));
4256
4702
  return renamed ? { ...column, table: statement.table.alias ?? statement.table.name } : column;
@@ -4259,14 +4705,14 @@ function jsonCellCast(dialect, cell) {
4259
4705
  if (cell.storage !== "json" || dialect.jsonParamCast === "")
4260
4706
  return dialect.jsonParamCast;
4261
4707
  if (dialect.name !== "postgres") {
4262
- throw internalError4({ message: "JsonParamCell storage json is a postgres construct" });
4708
+ throw internalError5({ message: "JsonParamCell storage json is a postgres construct" });
4263
4709
  }
4264
4710
  return "::text::json";
4265
4711
  }
4266
4712
  function quoteProjection(dialect, column) {
4267
4713
  if (column.transform === undefined) {
4268
4714
  const base = quoteCol(dialect, column);
4269
- const alias = column.alias ?? (column.truncate === undefined ? undefined : column.name);
4715
+ const alias = column.alias ?? (column.truncate === undefined && column.groupKey === undefined ? undefined : column.name);
4270
4716
  if (alias === undefined)
4271
4717
  return base;
4272
4718
  return `${base} AS ${dialect.quoteIdent({ name: alias })}`;
@@ -4283,7 +4729,7 @@ function quoteProjection(dialect, column) {
4283
4729
  if (column.transform === "encodeBase64" && dialect.name === "mysql")
4284
4730
  return `TO_BASE64(${ref}) AS ${alias}`;
4285
4731
  if (dialect.name !== "postgres") {
4286
- throw internalError4({ message: `${column.transform} is a postgres construct` });
4732
+ throw internalError5({ message: `${column.transform} is a postgres construct` });
4287
4733
  }
4288
4734
  if (column.transform === "encodeArrayBase64")
4289
4735
  return `encode(array_send(${ref}), 'base64') AS ${alias}`;
@@ -4307,7 +4753,7 @@ function renderTable(dialect, table) {
4307
4753
  }
4308
4754
  function assertCount(value, what) {
4309
4755
  if (!Number.isSafeInteger(value) || value < 0) {
4310
- throw internalError4({ message: `${what} must be a non-negative integer, got ${value}` });
4756
+ throw internalError5({ message: `${what} must be a non-negative integer, got ${value}` });
4311
4757
  }
4312
4758
  }
4313
4759
  var COMPARISON_SQL2 = {
@@ -4331,7 +4777,9 @@ var AGG_FN_SQL = {
4331
4777
  min: "MIN",
4332
4778
  max: "MAX"
4333
4779
  };
4334
- function renderAggRef(dialect, agg) {
4780
+ function renderAggRef(dialect, agg, collector) {
4781
+ if (agg.filter !== undefined)
4782
+ return renderFilteredAggregate({ dialect, agg, filter: agg.filter, collector });
4335
4783
  if (agg.fn === "countAll")
4336
4784
  return "COUNT(*)";
4337
4785
  const column = quoteCol(dialect, agg.column);
@@ -4340,10 +4788,112 @@ function renderAggRef(dialect, agg) {
4340
4788
  }
4341
4789
  return `${AGG_FN_SQL[agg.fn]}(${column})`;
4342
4790
  }
4791
+ function renderFilteredAggregate(params) {
4792
+ const { dialect, agg, filter, collector } = params;
4793
+ if (dialect.capabilities.filteredAggregates === "filterClause") {
4794
+ const { filter: _filter, ...plain } = agg;
4795
+ const aggregate = renderAggRef(dialect, plain, collector);
4796
+ return `${aggregate} FILTER (WHERE ${renderExpr({ dialect, expr: filter, collector })})`;
4797
+ }
4798
+ const when = renderExpr({ dialect, expr: filter, collector });
4799
+ if (agg.fn === "countAll")
4800
+ return `COUNT(CASE WHEN ${when} THEN 1 END)`;
4801
+ const column = quoteCol(dialect, agg.column);
4802
+ if ((agg.fn === "min" || agg.fn === "max") && agg.enumOrder !== undefined) {
4803
+ return dialect.renderEnumMinMax({ fn: agg.fn, column, result: agg.enumOrder, when });
4804
+ }
4805
+ return `${AGG_FN_SQL[agg.fn]}(CASE WHEN ${when} THEN ${column} END)`;
4806
+ }
4807
+ var MAX_GROUP_KEY_PREFIX = 1000;
4808
+ function renderGroupKey(params) {
4809
+ const { dialect, reference, key } = params;
4810
+ if (key.kind === "prefix") {
4811
+ if (!Number.isSafeInteger(key.length) || key.length < 1 || key.length > MAX_GROUP_KEY_PREFIX) {
4812
+ throw internalError5({ message: `a groupBy prefix length must be an integer from 1 to ${MAX_GROUP_KEY_PREFIX}, got ${String(key.length)}` });
4813
+ }
4814
+ if (dialect.name === "sqlite")
4815
+ return `substr(${reference}, 1, ${key.length})`;
4816
+ return `${dialect.name === "mysql" ? "LEFT" : "left"}(${reference}, ${key.length})`;
4817
+ }
4818
+ if (key.path.length === 0 || key.path.some((step) => typeof step !== "string" || step.length === 0 || step.includes("\x00"))) {
4819
+ throw internalError5({ message: "a groupBy JSON path needs one or more non-empty object keys without NUL characters" });
4820
+ }
4821
+ switch (dialect.jsonPathMode) {
4822
+ case "operators": {
4823
+ const steps = key.path.map((step) => dialect.quoteStringLiteral({ value: step }));
4824
+ if (key.storage === "json")
4825
+ return `((${reference} -> ${steps.join(" -> ")})::jsonb #>> '{}')`;
4826
+ const last = steps.length - 1;
4827
+ return `(${reference}${steps.map((step, index) => `${index === last ? " ->> " : " -> "}${step}`).join("")})`;
4828
+ }
4829
+ case "sqliteExtract": {
4830
+ const path = dialect.quoteStringLiteral({ value: jsonPathString(key.path) });
4831
+ const extracted = `json_extract(${reference}, ${path})`;
4832
+ return `CASE json_type(${reference}, ${path}) WHEN 'text' THEN ${extracted} WHEN 'true' THEN 'true' WHEN 'false' THEN 'false' WHEN 'null' THEN NULL ELSE CAST(${extracted} AS TEXT) END`;
4833
+ }
4834
+ case "mysqlExtract": {
4835
+ const extracted = `JSON_EXTRACT(${reference}, ${dialect.quoteStringLiteral({ value: jsonPathString(key.path) })})`;
4836
+ return `CASE WHEN JSON_TYPE(${extracted}) = 'NULL' THEN NULL ELSE JSON_UNQUOTE(${extracted}) END`;
4837
+ }
4838
+ }
4839
+ }
4840
+ function renderGroupingSets(params) {
4841
+ const { dialect, keys, grouping } = params;
4842
+ const sets = groupingSetIndexes({ keys, grouping });
4843
+ const mode = dialect.capabilities.groupingSets;
4844
+ if (mode === "groupingSets") {
4845
+ return `GROUPING SETS (${sets.map((set) => `(${set.map((index) => keys[index]).join(", ")})`).join(", ")})`;
4846
+ }
4847
+ if (mode === "rollup" && isRollupChain({ count: keys.length, sets }))
4848
+ return `${keys.join(", ")} WITH ROLLUP`;
4849
+ throw unsupportedCapability29({
4850
+ dialect: dialect.name,
4851
+ feature: mode === "rollup" ? "groupBy grouping sets other than the rollup of `by` in order" : "groupBy grouping sets",
4852
+ hint: mode === "rollup" ? "MySQL groups by `WITH ROLLUP` only: list the sets [[a, b], [a], []] for by [a, b], or run one groupBy per set" : "run one groupBy per grouping set instead"
4853
+ });
4854
+ }
4855
+ function groupingSetIndexes(params) {
4856
+ const { keys, grouping } = params;
4857
+ if (grouping.sets.length === 0 || keys.length === 0 || keys.length > 31) {
4858
+ throw internalError5({ message: "grouping sets need one or more sets over 1 to 31 groupBy keys" });
4859
+ }
4860
+ for (const set of grouping.sets) {
4861
+ if (set.some((index) => !Number.isSafeInteger(index) || index < 0 || index >= keys.length)) {
4862
+ throw internalError5({ message: "a grouping set names a groupBy key that does not exist" });
4863
+ }
4864
+ }
4865
+ return grouping.sets;
4866
+ }
4867
+ function isRollupChain(params) {
4868
+ const { count, sets } = params;
4869
+ if (sets.length !== count + 1)
4870
+ return false;
4871
+ const lengths = new Set;
4872
+ for (const set of sets) {
4873
+ const sorted = [...set].sort((left, right) => left - right);
4874
+ if (!sorted.every((index, position) => index === position))
4875
+ return false;
4876
+ lengths.add(set.length);
4877
+ }
4878
+ return lengths.size === count + 1;
4879
+ }
4880
+ function renderGroupingSetIndex(params) {
4881
+ const { keys } = params;
4882
+ const sets = groupingSetIndexes(params);
4883
+ const branches = sets.map((set, position) => {
4884
+ let mask = 0;
4885
+ for (let index = 0;index < keys.length; index++) {
4886
+ if (!set.includes(index))
4887
+ mask += 2 ** (keys.length - 1 - index);
4888
+ }
4889
+ return `WHEN ${mask} THEN ${position}`;
4890
+ });
4891
+ return `CASE GROUPING(${keys.join(", ")}) ${branches.join(" ")} END`;
4892
+ }
4343
4893
  function assertScalarArrays(dialect, feature) {
4344
4894
  if (dialect.capabilities.scalarArrays)
4345
4895
  return;
4346
- throw unsupportedCapability28({
4896
+ throw unsupportedCapability29({
4347
4897
  dialect: dialect.name,
4348
4898
  feature,
4349
4899
  hint: "only postgres has native array columns \u2014 model the list as a related table instead"
@@ -4355,7 +4905,7 @@ function renderExpr(params) {
4355
4905
  case "cmp": {
4356
4906
  const op = COMPARISON_SQL2[expr.op];
4357
4907
  if (op === undefined) {
4358
- throw internalError4({ message: `unknown comparison operator "${expr.op}"` });
4908
+ throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
4359
4909
  }
4360
4910
  if (isDbNowMarker(expr.value)) {
4361
4911
  return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
@@ -4366,7 +4916,7 @@ function renderExpr(params) {
4366
4916
  case "cmpCol": {
4367
4917
  const op = COMPARISON_SQL2[expr.op];
4368
4918
  if (op === undefined) {
4369
- throw internalError4({ message: `unknown comparison operator "${expr.op}"` });
4919
+ throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
4370
4920
  }
4371
4921
  return `${quoteCol(dialect, expr.left)} ${op} ${quoteCol(dialect, expr.right)}`;
4372
4922
  }
@@ -4390,11 +4940,11 @@ function renderExpr(params) {
4390
4940
  }
4391
4941
  case "tupleIn": {
4392
4942
  if (expr.columns.length === 0) {
4393
- throw internalError4({ message: "tupleIn needs at least one column" });
4943
+ throw internalError5({ message: "tupleIn needs at least one column" });
4394
4944
  }
4395
4945
  for (const row of expr.rows) {
4396
4946
  if (row.length !== expr.columns.length) {
4397
- throw internalError4({
4947
+ throw internalError5({
4398
4948
  message: `tupleIn row has ${row.length} values for ${expr.columns.length} columns`
4399
4949
  });
4400
4950
  }
@@ -4420,7 +4970,7 @@ function renderExpr(params) {
4420
4970
  case "rowCompare": {
4421
4971
  const op = COMPARISON_SQL2[expr.op];
4422
4972
  if (op === undefined || expr.columns.length === 0 || expr.values.length !== expr.columns.length) {
4423
- throw internalError4({ message: `rowCompare needs a range operator and one value per column (${expr.columns.length} columns, ${expr.values.length} values)` });
4973
+ throw internalError5({ message: `rowCompare needs a range operator and one value per column (${expr.columns.length} columns, ${expr.values.length} values)` });
4424
4974
  }
4425
4975
  const columns = expr.columns.map((column) => quoteCol(dialect, column));
4426
4976
  const placeholders = expr.values.map((value) => collector.add(value));
@@ -4428,9 +4978,9 @@ function renderExpr(params) {
4428
4978
  }
4429
4979
  case "inSelect": {
4430
4980
  if (expr.columns.length === 0)
4431
- throw internalError4({ message: "inSelect needs at least one column" });
4981
+ throw internalError5({ message: "inSelect needs at least one column" });
4432
4982
  if (dialect.name === "mysql" && (expr.select.limit !== undefined || expr.select.offset !== undefined)) {
4433
- throw unsupportedCapability28({
4983
+ throw unsupportedCapability29({
4434
4984
  dialect: dialect.name,
4435
4985
  feature: "LIMIT inside an IN subquery",
4436
4986
  hint: "mysql refuses LIMIT directly inside an IN subquery (error 1235); wrap the limited select in a derived table"
@@ -4501,9 +5051,9 @@ function renderExpr(params) {
4501
5051
  case "aggCmp": {
4502
5052
  const op = COMPARISON_SQL2[expr.op];
4503
5053
  if (op === undefined) {
4504
- throw internalError4({ message: `unknown comparison operator "${expr.op}"` });
5054
+ throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
4505
5055
  }
4506
- return `${renderAggRef(dialect, expr.agg)} ${op} ${collector.add(expr.value)}`;
5056
+ return `${renderAggRef(dialect, expr.agg, collector)} ${op} ${collector.add(expr.value)}`;
4507
5057
  }
4508
5058
  case "arrayHas": {
4509
5059
  assertScalarArrays(dialect, 'the scalar-list filter "has"');
@@ -4556,19 +5106,6 @@ function renderFragment(params) {
4556
5106
  }
4557
5107
  return text;
4558
5108
  }
4559
- function jsonPathString(path) {
4560
- let out = "$";
4561
- for (const segment of path) {
4562
- if (typeof segment === "number") {
4563
- out += `[${segment}]`;
4564
- } else if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment)) {
4565
- out += `.${segment}`;
4566
- } else {
4567
- out += `."${segment.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
4568
- }
4569
- }
4570
- return out;
4571
- }
4572
5109
  function listComparison(params) {
4573
5110
  const { dialect, column, value, collector } = params;
4574
5111
  if (isJsonListParameter(value) && value.storage === "json") {
@@ -4583,13 +5120,13 @@ function renderKeyList(params) {
4583
5120
  const { dialect, expr, collector } = params;
4584
5121
  const arity = expr.columns.length;
4585
5122
  if (arity === 0)
4586
- throw internalError4({ message: "keyList needs at least one column" });
5123
+ throw internalError5({ message: "keyList needs at least one column" });
4587
5124
  if (expr.members.length !== arity) {
4588
- throw internalError4({ message: `keyList has ${expr.members.length} members for ${arity} columns` });
5125
+ throw internalError5({ message: `keyList has ${expr.members.length} members for ${arity} columns` });
4589
5126
  }
4590
5127
  for (const row of expr.rows) {
4591
5128
  if (row.length !== arity)
4592
- throw internalError4({ message: `keyList row has ${row.length} values for ${arity} columns` });
5129
+ throw internalError5({ message: `keyList row has ${row.length} values for ${arity} columns` });
4593
5130
  }
4594
5131
  if (expr.rows.length === 0)
4595
5132
  return "1 = 0";
@@ -4621,25 +5158,25 @@ function renderKeyPage(params) {
4621
5158
  const { select } = page;
4622
5159
  const arity = page.keyColumns.length;
4623
5160
  if (arity === 0 || page.members.length !== arity) {
4624
- throw internalError4({ message: `keyPage has ${page.members.length} members for ${arity} key columns` });
5161
+ throw internalError5({ message: `keyPage has ${page.members.length} members for ${arity} key columns` });
4625
5162
  }
4626
5163
  if (page.rows.length === 0)
4627
- throw internalError4({ message: "keyPage needs at least one key" });
5164
+ throw internalError5({ message: "keyPage needs at least one key" });
4628
5165
  for (const row of page.rows) {
4629
5166
  if (row.length !== arity)
4630
- throw internalError4({ message: `keyPage row has ${row.length} values for ${arity} key columns` });
5167
+ throw internalError5({ message: `keyPage row has ${row.length} values for ${arity} key columns` });
4631
5168
  }
4632
5169
  if (page.identity.length === 0)
4633
- throw internalError4({ message: "keyPage needs the child's identity columns" });
5170
+ throw internalError5({ message: "keyPage needs the child's identity columns" });
4634
5171
  const order = select.orderBy ?? [];
4635
5172
  if (order.length === 0 || order.some((term) => !("column" in term))) {
4636
- throw internalError4({ message: "keyPage needs a non-empty order of plain column terms" });
5173
+ throw internalError5({ message: "keyPage needs a non-empty order of plain column terms" });
4637
5174
  }
4638
5175
  if (select.table.alias !== undefined || Object.keys(select).some((member) => !KEY_PAGE_SELECT_MEMBERS.has(member))) {
4639
- throw internalError4({ message: "keyPage select carries a member the per-key page cannot render" });
5176
+ throw internalError5({ message: "keyPage select carries a member the per-key page cannot render" });
4640
5177
  }
4641
5178
  if (dialect.keyPageStrategy === "window") {
4642
- throw unsupportedCapability28({
5179
+ throw unsupportedCapability29({
4643
5180
  dialect: dialect.name,
4644
5181
  feature: "a per-key page (keyPage)",
4645
5182
  hint: "the runtime keeps the ROW_NUMBER window on this dialect"
@@ -4667,7 +5204,7 @@ function renderKeyPage(params) {
4667
5204
  const keys = dialect.quoteIdent({ name: keyAlias });
4668
5205
  if (dialect.keyPageStrategy === "lateral") {
4669
5206
  if (keyListBindsUntypedRows({ members: page.members })) {
4670
- throw unsupportedCapability28({
5207
+ throw unsupportedCapability29({
4671
5208
  dialect: dialect.name,
4672
5209
  feature: "a per-key page over a DateTime key without a declared native type",
4673
5210
  hint: "the runtime keeps the ROW_NUMBER window for such keys"
@@ -4695,22 +5232,22 @@ function renderKeyPage(params) {
4695
5232
  function renderKeyRows(params) {
4696
5233
  const { dialect, statement, keys, collector } = params;
4697
5234
  if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.fromUnion !== undefined || statement.table.alias !== undefined) {
4698
- throw internalError4({ message: "fromKeys uses table.name as the derived-table alias \u2014 fromSelect, keyPage, fromUnion and table.alias must be unset" });
5235
+ throw internalError5({ message: "fromKeys uses table.name as the derived-table alias \u2014 fromSelect, keyPage, fromUnion and table.alias must be unset" });
4699
5236
  }
4700
5237
  if (statement.rowLock !== undefined)
4701
- throw internalError4({ message: "a locking read cannot read from key rows" });
5238
+ throw internalError5({ message: "a locking read cannot read from key rows" });
4702
5239
  const arity = keys.columns.length;
4703
5240
  if (arity === 0 || keys.members.length !== arity) {
4704
- throw internalError4({ message: `fromKeys has ${keys.members.length} members for ${arity} columns` });
5241
+ throw internalError5({ message: `fromKeys has ${keys.members.length} members for ${arity} columns` });
4705
5242
  }
4706
5243
  if (keys.rows.length === 0)
4707
- throw internalError4({ message: "fromKeys needs at least one key row" });
5244
+ throw internalError5({ message: "fromKeys needs at least one key row" });
4708
5245
  for (const row of keys.rows) {
4709
5246
  if (row.length !== arity)
4710
- throw internalError4({ message: `fromKeys row has ${row.length} values for ${arity} columns` });
5247
+ throw internalError5({ message: `fromKeys row has ${row.length} values for ${arity} columns` });
4711
5248
  }
4712
5249
  if (dialect.keyListStrategy !== "unnest" || keyListBindsUntypedRows({ members: keys.members })) {
4713
- throw unsupportedCapability28({
5250
+ throw unsupportedCapability29({
4714
5251
  dialect: dialect.name,
4715
5252
  feature: dialect.keyListStrategy !== "unnest" ? "a key-rows FROM (fromKeys)" : "a key-rows FROM over a DateTime key without a declared native type",
4716
5253
  hint: "the runtime keeps the per-relation loads for this write"
@@ -4726,20 +5263,20 @@ function renderUnionPage(params) {
4726
5263
  const { dialect, statement, collector } = params;
4727
5264
  const branches = statement.fromUnion ?? [];
4728
5265
  if (branches.length < 2)
4729
- throw internalError4({ message: "fromUnion needs at least two branches" });
5266
+ throw internalError5({ message: "fromUnion needs at least two branches" });
4730
5267
  if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.table.alias !== undefined) {
4731
- throw internalError4({ message: "fromUnion uses table.name as the derived-table alias \u2014 fromSelect, keyPage and table.alias must be unset" });
5268
+ throw internalError5({ message: "fromUnion uses table.name as the derived-table alias \u2014 fromSelect, keyPage and table.alias must be unset" });
4732
5269
  }
4733
5270
  if (statement.rowLock !== undefined)
4734
- throw internalError4({ message: "a locking read cannot read from a union page" });
5271
+ throw internalError5({ message: "a locking read cannot read from a union page" });
4735
5272
  const outputNames = (branch) => branch.columns.map((column) => column.alias ?? column.name).join("\x00");
4736
5273
  const names = outputNames(branches[0]);
4737
5274
  return branches.map((branch, index) => {
4738
5275
  if (branch.columns.length === 0 || Object.keys(branch).some((member) => !UNION_BRANCH_MEMBERS.has(member))) {
4739
- throw internalError4({ message: "a fromUnion branch must be a plain SELECT <columns> FROM <table> [WHERE] [ORDER BY] [LIMIT]" });
5276
+ throw internalError5({ message: "a fromUnion branch must be a plain SELECT <columns> FROM <table> [WHERE] [ORDER BY] [LIMIT]" });
4740
5277
  }
4741
5278
  if (outputNames(branch) !== names)
4742
- throw internalError4({ message: "fromUnion branches must project the same output names in the same order" });
5279
+ throw internalError5({ message: "fromUnion branches must project the same output names in the same order" });
4743
5280
  const inner = renderSelect({ dialect, statement: branch, collector });
4744
5281
  return `SELECT * FROM (${inner.text}) AS ${dialect.quoteIdent({ name: `__vibe_u${index}` })}`;
4745
5282
  }).join(" UNION ALL ");
@@ -4747,7 +5284,7 @@ function renderUnionPage(params) {
4747
5284
  function jsonStringPattern(params) {
4748
5285
  const { expr } = params;
4749
5286
  if (typeof expr.value !== "string") {
4750
- throw internalError4({ message: `json ${expr.op} expects a string operand` });
5287
+ throw internalError5({ message: `json ${expr.op} expects a string operand` });
4751
5288
  }
4752
5289
  if (expr.op === "string_contains")
4753
5290
  return containsPattern({ raw: expr.value });
@@ -4768,10 +5305,14 @@ function renderJsonLike(params) {
4768
5305
  base = `${base}${dialect.likeEscapeClause}`;
4769
5306
  return expr.negate === true ? `NOT (${base})` : base;
4770
5307
  }
5308
+ function renderJsonExists(params) {
5309
+ const { present, column, exists } = params;
5310
+ return `CASE WHEN ${present} THEN ${exists ? "TRUE" : "FALSE"} WHEN ${column} IS NOT NULL THEN ${exists ? "FALSE" : "TRUE"} END`;
5311
+ }
4771
5312
  function renderJsonFilter(params) {
4772
5313
  const { dialect, expr, collector } = params;
4773
5314
  if (!dialect.supportedJsonOps.includes(expr.op)) {
4774
- throw unsupportedCapability28({
5315
+ throw unsupportedCapability29({
4775
5316
  dialect: dialect.name,
4776
5317
  feature: `the json filter "${expr.op}"`,
4777
5318
  hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
@@ -4783,6 +5324,16 @@ function renderJsonFilter(params) {
4783
5324
  const pathParam = `${collector.add(expr.path.map(String))}::text[]`;
4784
5325
  const extracted = expr.storage === "json" ? `(${column} #> ${pathParam})::jsonb` : `(${column} #> ${pathParam})`;
4785
5326
  switch (expr.op) {
5327
+ case "exists": {
5328
+ const typeOf = expr.storage === "json" ? "json_typeof" : "jsonb_typeof";
5329
+ const guards = expr.path.flatMap((segment, index) => {
5330
+ if (typeof segment !== "string" || !/^\s*[+-]?\d/.test(segment))
5331
+ return [];
5332
+ const container = index === 0 ? column : `${column} #> ${collector.add(expr.path.slice(0, index).map(String))}::text[]`;
5333
+ return [` AND ${typeOf}(${container}) = 'object'`];
5334
+ });
5335
+ return renderJsonExists({ present: `(${column} #> ${pathParam}) IS NOT NULL${guards.join("")}`, column, exists: expr.value === true });
5336
+ }
4786
5337
  case "equals":
4787
5338
  return `${extracted} ${cmp} ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
4788
5339
  case "string_contains":
@@ -4820,6 +5371,8 @@ function renderJsonFilter(params) {
4820
5371
  return expr.negate === true ? `NOT (${base})` : base;
4821
5372
  };
4822
5373
  switch (expr.op) {
5374
+ case "exists":
5375
+ return renderJsonExists({ present: `json_type(${column}, ${collector.add(jsonPathString(expr.path))}) IS NOT NULL`, column, exists: expr.value === true });
4823
5376
  case "equals":
4824
5377
  return compareExtracted(jsonPathString(expr.path), expr.value);
4825
5378
  case "string_contains":
@@ -4838,7 +5391,7 @@ function renderJsonFilter(params) {
4838
5391
  const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
4839
5392
  const tests = elements.map((element) => {
4840
5393
  if (element === null || typeof element === "object" && element !== null) {
4841
- throw unsupportedCapability28({
5394
+ throw unsupportedCapability29({
4842
5395
  dialect: dialect.name,
4843
5396
  feature: "json array_contains with object/array/null elements",
4844
5397
  hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
@@ -4852,12 +5405,16 @@ function renderJsonFilter(params) {
4852
5405
  return tests.length === 0 ? guard : `(${[guard, ...tests].join(" AND ")})`;
4853
5406
  }
4854
5407
  default:
4855
- throw internalError4({ message: `json op "${expr.op}" leaked past supportedJsonOps on sqlite` });
5408
+ throw internalError5({ message: `json op "${expr.op}" leaked past supportedJsonOps on sqlite` });
4856
5409
  }
4857
5410
  }
4858
5411
  const column = quoteCol(dialect, expr.column);
4859
5412
  const pathString = jsonPathString(expr.path);
4860
5413
  switch (expr.op) {
5414
+ case "exists": {
5415
+ const present = `JSON_CONTAINS_PATH(${column}, 'one', ${collector.add(pathString)})`;
5416
+ return expr.value === true ? present : `NOT ${present}`;
5417
+ }
4861
5418
  case "equals":
4862
5419
  return `JSON_EXTRACT(${column}, ${collector.add(pathString)}) ${cmp} CAST(${collector.add(JSON.stringify(expr.value))} AS JSON)`;
4863
5420
  case "string_contains":
@@ -4874,10 +5431,18 @@ function renderJsonFilter(params) {
4874
5431
  return `JSON_EXTRACT(${column}, ${collector.add(`${pathString}[last]`)}) ${cmp} CAST(${collector.add(JSON.stringify(expr.value))} AS JSON)`;
4875
5432
  }
4876
5433
  }
5434
+ function groupKeyOutputName(params) {
5435
+ const { dialect, statement, column } = params;
5436
+ if (column.groupKey === undefined)
5437
+ return;
5438
+ const text = quoteCol(dialect, column);
5439
+ const projected = statement.columns.find((candidate) => candidate.groupKey !== undefined && candidate.transform === undefined && candidate.alias !== undefined && quoteCol(dialect, candidate) === text);
5440
+ return projected?.alias;
5441
+ }
4877
5442
  function renderOrderTerm(params) {
4878
- const { dialect, term, collector } = params;
5443
+ const { dialect, term, collector, nullsOutputName } = params;
4879
5444
  if ("agg" in term) {
4880
- return `${renderAggRef(dialect, term.agg)} ${term.direction === "asc" ? "ASC" : "DESC"}`;
5445
+ return `${renderAggRef(dialect, term.agg, collector)} ${term.direction === "asc" ? "ASC" : "DESC"}`;
4881
5446
  }
4882
5447
  const expression = "select" in term ? () => renderScalarSubquery({ dialect, subquery: term, collector }) : () => quoteCol(dialect, term.column);
4883
5448
  const direction = term.direction === "asc" ? "ASC" : "DESC";
@@ -4890,7 +5455,7 @@ function renderOrderTerm(params) {
4890
5455
  if (term.nulls === "first" === (term.direction === "asc")) {
4891
5456
  return `${expression()} ${direction}`;
4892
5457
  }
4893
- const tested = expression();
5458
+ const tested = nullsOutputName === undefined ? expression() : dialect.quoteIdent({ name: nullsOutputName });
4894
5459
  const nullsTerm = term.nulls === "first" ? `${tested} IS NULL DESC` : `${tested} IS NULL`;
4895
5460
  return `${nullsTerm}, ${expression()} ${direction}`;
4896
5461
  }
@@ -4907,11 +5472,11 @@ function renderPositionalRow(params) {
4907
5472
  const position = join.select.rowNumber;
4908
5473
  const positioned = position !== undefined && json.order === "positionSlot" && join.agg === "jsonArray" && position.partitionBy.length === 0 && position.orderBy.length > 0;
4909
5474
  if (join.select.columns.length === 0 || join.select.selectExpr !== undefined || (join.select.aggregates?.length ?? 0) > 0 || position !== undefined && !positioned) {
4910
- throw internalError4({ message: "positional lateral rows require an explicit column-only projection" });
5475
+ throw internalError5({ message: "positional lateral rows require an explicit column-only projection" });
4911
5476
  }
4912
5477
  const names = [...join.select.columns.map((column) => projectionOutputName({ column })), ...positioned ? [position.alias] : []];
4913
5478
  if (new Set(names).size !== names.length) {
4914
- throw internalError4({ message: "positional lateral projection has duplicate output names" });
5479
+ throw internalError5({ message: "positional lateral projection has duplicate output names" });
4915
5480
  }
4916
5481
  const values = names.map((name) => `${subAlias}.${dialect.quoteIdent({ name })}`);
4917
5482
  const chunks = [];
@@ -4919,14 +5484,14 @@ function renderPositionalRow(params) {
4919
5484
  chunks.push(`${json.rowArray}(${values.slice(index, index + JSON_TRANSPORT.chunkSize).join(", ")})`);
4920
5485
  }
4921
5486
  if (chunks.length > JSON_TRANSPORT.chunkSize) {
4922
- throw internalError4({ message: "positional lateral projection exceeds the transport width limit" });
5487
+ throw internalError5({ message: "positional lateral projection exceeds the transport width limit" });
4923
5488
  }
4924
5489
  return chunks.length === 1 ? chunks[0] : `${json.rowArray}(${chunks.join(", ")})`;
4925
5490
  }
4926
5491
  function lateralJsonOf(params) {
4927
5492
  const json = params.dialect.lateralJson;
4928
5493
  if (json === null)
4929
- throw internalError4({ message: "lateral JSON rendering on a dialect without LATERAL" });
5494
+ throw internalError5({ message: "lateral JSON rendering on a dialect without LATERAL" });
4930
5495
  return json;
4931
5496
  }
4932
5497
  function renderLateralJoin(params) {
@@ -4934,7 +5499,7 @@ function renderLateralJoin(params) {
4934
5499
  const inner = renderSelect({ dialect, statement: join.select, collector });
4935
5500
  if (join.agg === "count") {
4936
5501
  if (join.rowFormat === "positional" || join.select.columns.length !== 0 || join.select.selectExpr?.kind !== "countAll" || join.select.selectExpr.alias !== join.columnAlias || join.select.groupBy !== undefined || join.select.having !== undefined || join.select.limit !== undefined || join.select.offset !== undefined || join.select.distinctOn !== undefined || join.select.distinct !== undefined) {
4937
- throw internalError4({ message: "count lateral must return one ungrouped COUNT(*) under its column alias" });
5502
+ throw internalError5({ message: "count lateral must return one ungrouped COUNT(*) under its column alias" });
4938
5503
  }
4939
5504
  return `LEFT JOIN LATERAL (${inner.text}) AS ${dialect.quoteIdent({ name: join.alias })} ON true`;
4940
5505
  }
@@ -4946,12 +5511,14 @@ function renderLateralJoin(params) {
4946
5511
  const projected = new Set(join.select.columns.filter((column) => column.transform === undefined && column.castReal !== true).map((column) => column.name).concat((join.select.scalarSubqueries ?? []).map((subquery) => subquery.alias)));
4947
5512
  const terms = join.select.orderBy;
4948
5513
  const columnTerms = terms.filter((term) => ("column" in term));
4949
- if (columnTerms.length === terms.length && columnTerms.every((term) => projected.has(term.column.name))) {
5514
+ const subRef = (column) => ({ table: "__sub", name: column.name, ...column.castJsonb === undefined ? {} : { castJsonb: column.castJsonb } });
5515
+ const readable = (column) => column.coalesce === undefined ? projected.has(column.name) : column.coalesce.every((member) => projected.has(member.name));
5516
+ if (columnTerms.length === terms.length && columnTerms.every((term) => readable(term.column))) {
4950
5517
  const rendered = columnTerms.map((term) => renderOrderTerm({
4951
5518
  dialect,
4952
5519
  term: {
4953
5520
  ...term,
4954
- column: { table: "__sub", name: term.column.name, ...term.column.castJsonb === undefined ? {} : { castJsonb: term.column.castJsonb } }
5521
+ column: term.column.coalesce === undefined ? subRef(term.column) : { ...term.column, coalesce: term.column.coalesce.map(subRef) }
4955
5522
  },
4956
5523
  collector
4957
5524
  }));
@@ -4959,7 +5526,7 @@ function renderLateralJoin(params) {
4959
5526
  }
4960
5527
  }
4961
5528
  if (join.rowFormat !== "positional" && json.order !== "inAggregate") {
4962
- throw unsupportedCapability28({ dialect: dialect.name, feature: "object-format lateral rows", hint: 'lateral relation rows render positional (rowFormat "positional") on this dialect' });
5529
+ throw unsupportedCapability29({ dialect: dialect.name, feature: "object-format lateral rows", hint: 'lateral relation rows render positional (rowFormat "positional") on this dialect' });
4963
5530
  }
4964
5531
  const rowJson = join.rowFormat === "positional" ? renderPositionalRow({ dialect, join, subAlias }) : `to_json(${subAlias}.*)`;
4965
5532
  const projection = join.agg === "jsonArray" ? `COALESCE(${json.arrayAgg}(${rowJson}${aggOrder}), ${json.emptyArray}) AS ${columnAlias}` : `${rowJson} AS ${columnAlias}`;
@@ -4970,7 +5537,7 @@ function renderCountSubquery(params) {
4970
5537
  const { dialect, subquery, collector } = params;
4971
5538
  const select = subquery.select;
4972
5539
  if (select.columns.length !== 0 || select.selectExpr?.kind !== "countAll" || (select.aggregates?.length ?? 0) > 0 || select.rowNumber !== undefined || select.groupBy !== undefined || select.having !== undefined || select.limit !== undefined || select.offset !== undefined || select.distinctOn !== undefined || select.distinct !== undefined || select.rowLock !== undefined || select.countSubqueries !== undefined || select.scalarSubqueries !== undefined) {
4973
- throw internalError4({ message: "count subquery must be one ungrouped COUNT(*)" });
5540
+ throw internalError5({ message: "count subquery must be one ungrouped COUNT(*)" });
4974
5541
  }
4975
5542
  const inner = renderSelect({ dialect, statement: select, collector });
4976
5543
  return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
@@ -4988,7 +5555,10 @@ function applyReturning(params) {
4988
5555
  if (!dialect.capabilities.returning) {
4989
5556
  const capture = requested.find(isCaptureReturningColumn);
4990
5557
  if (capture !== undefined)
4991
- renderCaptureReturning({ dialect, column: capture });
5558
+ renderCaptureReturning({ dialect, column: capture, collector: params.collector });
5559
+ const other = requested.find(isOtherRowReturningColumn);
5560
+ if (other !== undefined)
5561
+ renderOtherRowReturning({ dialect, column: other });
4992
5562
  return { suffix: "", status: "omitted" };
4993
5563
  }
4994
5564
  const read = (name) => params.qualify === undefined ? name : `${params.qualify}.${name}`;
@@ -4996,7 +5566,9 @@ function applyReturning(params) {
4996
5566
  if (typeof column === "string")
4997
5567
  return read(dialect.quoteIdent({ name: column }));
4998
5568
  if ("capture" in column)
4999
- return renderCaptureReturning({ dialect, column });
5569
+ return renderCaptureReturning({ dialect, column, collector: params.collector });
5570
+ if ("from" in column)
5571
+ return renderOtherRowReturning({ dialect, column });
5000
5572
  if ("transform" in column) {
5001
5573
  const name = dialect.quoteIdent({ name: column.name });
5002
5574
  return `${dialect.jsonTextProjection({ expression: read(name) })} AS ${name}`;
@@ -5009,40 +5581,88 @@ function applyReturning(params) {
5009
5581
  function renderCaptureReturning(params) {
5010
5582
  const { dialect, column } = params;
5011
5583
  if (!dialect.capabilities.rowCapture) {
5012
- throw unsupportedCapability28({ dialect: dialect.name, feature: "row capture (RETURNING old./new.)", hint: "row capture needs PostgreSQL 18 or later; the runtime refuses $transaction({ capture }) on this dialect" });
5584
+ throw unsupportedCapability29({ dialect: dialect.name, feature: "row capture (RETURNING old./new.)", hint: "row capture needs PostgreSQL 18 or later; the runtime refuses $transaction({ capture }) on this dialect" });
5013
5585
  }
5014
- const name = dialect.quoteIdent({ name: column.name });
5015
5586
  const alias = dialect.quoteIdent({ name: column.alias });
5587
+ if (column.capture === "cascade") {
5588
+ if (params.collector === undefined)
5589
+ throw internalError5({ message: `capture cascade column "${column.alias}" rendered without a parameter collector` });
5590
+ const child = dialect.quoteIdent({ name: CAPTURE_CHILD_ALIAS });
5591
+ const match = column.cascade.on.map((pair) => `${child}.${dialect.quoteIdent({ name: pair.child })} = old.${dialect.quoteIdent({ name: pair.parent })}`).join(" AND ");
5592
+ return `${renderCascadeImages({ dialect, cascade: column.cascade, match, collector: params.collector })} AS ${alias}`;
5593
+ }
5594
+ const name = dialect.quoteIdent({ name: column.name });
5016
5595
  if (column.capture === "inserted")
5017
5596
  return `(old.${name} IS NULL) AS ${alias}`;
5018
- return `${textProjection({ dialect, expression: `${column.capture}.${name}` })} AS ${alias}`;
5597
+ if (column.capture === "key")
5598
+ return `old.${name} AS ${alias}`;
5599
+ return `${renderCaptureCell({ dialect, row: column.capture, cell: column })} AS ${alias}`;
5600
+ }
5601
+ var CAPTURE_CHILD_ALIAS = "__vibe_c";
5602
+ var CAPTURE_HOP_ALIAS = "__vibe_h";
5603
+ function renderCaptureCell(params) {
5604
+ const { dialect, row, cell } = params;
5605
+ const name = dialect.quoteIdent({ name: cell.name });
5606
+ if (cell.hop === undefined)
5607
+ return textProjection({ dialect, expression: `${row}.${name}` });
5608
+ if (cell.hop.on.length === 0)
5609
+ throw internalError5({ message: `capture hop to "${cell.hop.table.name}" needs at least one key column` });
5610
+ const related = dialect.quoteIdent({ name: CAPTURE_HOP_ALIAS });
5611
+ const match = cell.hop.on.map((pair) => `${related}.${dialect.quoteIdent({ name: pair.target })} = ${row}.${dialect.quoteIdent({ name: pair.source })}`).join(" AND ");
5612
+ return `(SELECT ${textProjection({ dialect, expression: `${related}.${name}` })} FROM ${renderTable(dialect, cell.hop.table)} AS ${related} WHERE ${match})`;
5613
+ }
5614
+ function renderCascadeImages(params) {
5615
+ const { dialect, cascade, match, collector } = params;
5616
+ if (cascade.cells.length === 0 || cascade.on.length === 0)
5617
+ throw internalError5({ message: `capture cascade into "${cascade.table.name}" needs at least one cell and one key column` });
5618
+ if (!Number.isSafeInteger(cascade.limit) || cascade.limit < 1)
5619
+ throw internalError5({ message: `capture cascade into "${cascade.table.name}" needs a positive integer limit` });
5620
+ const child = dialect.quoteIdent({ name: CAPTURE_CHILD_ALIAS });
5621
+ const item = dialect.quoteIdent({ name: "__vibe_i" });
5622
+ const cells = cascade.cells.map((cell) => renderCaptureCell({ dialect, row: child, cell })).join(", ");
5623
+ return `(SELECT array_agg(${item})::text FROM (SELECT DISTINCT ARRAY[${cells}] AS ${item} FROM ${renderTable(dialect, cascade.table)} AS ${child} WHERE ${match} LIMIT ${collector.add(cascade.limit)}) AS ${dialect.quoteIdent({ name: "__vibe_d" })})`;
5624
+ }
5625
+ function renderOtherRowReturning(params) {
5626
+ const { dialect, column } = params;
5627
+ const fromOld = column.from === "old";
5628
+ if (fromOld ? !dialect.capabilities.rowCapture : !dialect.capabilities.returningReadsJoinedSource) {
5629
+ throw unsupportedCapability29({
5630
+ dialect: dialect.name,
5631
+ feature: fromOld ? "RETURNING old.\u2026 (the row before the write)" : "RETURNING a joined source's columns",
5632
+ hint: fromOld ? "needs PostgreSQL 18 or later" : "read the row inside the write's transaction first, then write it"
5633
+ });
5634
+ }
5635
+ const name = dialect.quoteIdent({ name: column.name });
5636
+ const expression = column.from === "old" ? `old.${name}` : `${dialect.quoteIdent({ name: column.from.source })}.${name}`;
5637
+ const projected = column.projection === "castText" ? textProjection({ dialect, expression }) : column.projection === "jsonText" ? dialect.jsonTextProjection({ expression }) : expression;
5638
+ return `${projected} AS ${dialect.quoteIdent({ name: column.alias })}`;
5019
5639
  }
5020
5640
  function renderKeyTableJoin(params) {
5021
5641
  const { dialect, join, collector } = params;
5022
5642
  const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
5023
5643
  const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
5024
5644
  if (!admitted) {
5025
- throw unsupportedCapability28({
5645
+ throw unsupportedCapability29({
5026
5646
  dialect: dialect.name,
5027
5647
  feature: "relation key table",
5028
5648
  hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
5029
5649
  });
5030
5650
  }
5031
5651
  if (join.columns.length === 0 || join.columns.length !== join.members.length) {
5032
- throw internalError4({ message: `key table needs one member per column, got ${join.members.length} for ${join.columns.length}` });
5652
+ throw internalError5({ message: `key table needs one member per column, got ${join.members.length} for ${join.columns.length}` });
5033
5653
  }
5034
5654
  if (join.rows.length === 0)
5035
- throw internalError4({ message: "key table needs at least one key row" });
5655
+ throw internalError5({ message: "key table needs at least one key row" });
5036
5656
  for (const member of join.members) {
5037
5657
  if (member.kind === "decimal" && !(Number.isInteger(member.scale) && member.scale >= 0 && member.scale <= 30)) {
5038
- throw internalError4({ message: `key table decimal scale must be an integer 0-30, got ${String(member.scale)}` });
5658
+ throw internalError5({ message: `key table decimal scale must be an integer 0-30, got ${String(member.scale)}` });
5039
5659
  }
5040
5660
  }
5041
5661
  const alias = dialect.quoteIdent({ name: join.alias });
5042
5662
  const memberNames = join.members.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
5043
5663
  const rows = join.rows.map((row, ordinal) => {
5044
5664
  if (row.length !== join.members.length) {
5045
- throw internalError4({ message: `key table row has ${row.length} values for ${join.members.length} members` });
5665
+ throw internalError5({ message: `key table row has ${row.length} values for ${join.members.length} members` });
5046
5666
  }
5047
5667
  const cells = row.map((value, index) => {
5048
5668
  const placeholder = collector.add(value);
@@ -5072,10 +5692,10 @@ function renderKeyTableJoin(params) {
5072
5692
  function renderLeftJoin(params) {
5073
5693
  const { dialect, join, collector } = params;
5074
5694
  if (join.on.length === 0) {
5075
- throw internalError4({ message: "left join needs at least one ON pair" });
5695
+ throw internalError5({ message: "left join needs at least one ON pair" });
5076
5696
  }
5077
5697
  if (join.select.columns.length === 0 || join.select.columns.some((column) => column.alias === undefined)) {
5078
- throw internalError4({ message: "a left join's derived select must alias every projected column" });
5698
+ throw internalError5({ message: "a left join's derived select must alias every projected column" });
5079
5699
  }
5080
5700
  const inner = renderSelect({ dialect, statement: join.select, collector });
5081
5701
  const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
@@ -5084,10 +5704,10 @@ function renderLeftJoin(params) {
5084
5704
  function renderSelect(params) {
5085
5705
  const { dialect, statement, collector } = params;
5086
5706
  if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
5087
- throw internalError4({ message: "only countAll can combine with a column list" });
5707
+ throw internalError5({ message: "only countAll can combine with a column list" });
5088
5708
  }
5089
5709
  if (statement.selectExpr !== undefined && statement.aggregates !== undefined && statement.aggregates.length > 0) {
5090
- throw internalError4({ message: "selectExpr and aggregates cannot combine" });
5710
+ throw internalError5({ message: "selectExpr and aggregates cannot combine" });
5091
5711
  }
5092
5712
  const columnList = statement.columns.map((column) => quoteProjection(dialect, column));
5093
5713
  if (statement.selectExpr !== undefined) {
@@ -5095,11 +5715,15 @@ function renderSelect(params) {
5095
5715
  }
5096
5716
  if (statement.aggregates !== undefined) {
5097
5717
  for (const selection of statement.aggregates) {
5098
- const expression = renderAggRef(dialect, selection.agg);
5718
+ const expression = renderAggRef(dialect, selection.agg, collector);
5099
5719
  const projected = selection.castText === true ? textProjection({ dialect, expression }) : expression;
5100
5720
  columnList.push(`${projected} AS ${dialect.quoteIdent({ name: selection.alias })}`);
5101
5721
  }
5102
5722
  }
5723
+ const groupKeys = statement.groupBy?.map((column) => quoteCol(dialect, column)) ?? [];
5724
+ if (statement.groupingSets !== undefined) {
5725
+ columnList.push(`${renderGroupingSetIndex({ keys: groupKeys, grouping: statement.groupingSets })} AS ${dialect.quoteIdent({ name: statement.groupingSets.indexAlias })}`);
5726
+ }
5103
5727
  if (statement.countSubqueries !== undefined) {
5104
5728
  for (const subquery of statement.countSubqueries) {
5105
5729
  columnList.push(renderCountSubquery({ dialect, subquery, collector }));
@@ -5113,7 +5737,7 @@ function renderSelect(params) {
5113
5737
  if (statement.rowNumber !== undefined) {
5114
5738
  const rowNumber = statement.rowNumber;
5115
5739
  if (rowNumber.partitionBy.length === 0 && rowNumber.orderBy.length === 0) {
5116
- throw internalError4({ message: "rowNumber needs a partition column or an order" });
5740
+ throw internalError5({ message: "rowNumber needs a partition column or an order" });
5117
5741
  }
5118
5742
  const partition = rowNumber.partitionBy.length === 0 ? "" : `PARTITION BY ${rowNumber.partitionBy.map((column) => quoteCol(dialect, column)).join(", ")}`;
5119
5743
  const order = rowNumber.orderBy.length === 0 ? "" : `${partition === "" ? "" : " "}ORDER BY ${rowNumber.orderBy.map((term) => renderOrderTerm({ dialect, term, collector })).join(", ")}`;
@@ -5121,11 +5745,11 @@ function renderSelect(params) {
5121
5745
  }
5122
5746
  let distinctPrefix = statement.distinct === true ? "DISTINCT " : "";
5123
5747
  if (statement.distinct === true && statement.distinctOn !== undefined) {
5124
- throw internalError4({ message: "distinct and distinctOn cannot combine" });
5748
+ throw internalError5({ message: "distinct and distinctOn cannot combine" });
5125
5749
  }
5126
5750
  if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
5127
5751
  if (!dialect.capabilities.distinctOn) {
5128
- throw unsupportedCapability28({
5752
+ throw unsupportedCapability29({
5129
5753
  dialect: dialect.name,
5130
5754
  feature: "SELECT DISTINCT ON",
5131
5755
  hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
@@ -5141,12 +5765,12 @@ function renderSelect(params) {
5141
5765
  from = `(${renderUnionPage({ dialect, statement, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
5142
5766
  } else if (statement.keyPage !== undefined) {
5143
5767
  if (statement.fromSelect !== undefined || statement.table.alias !== undefined) {
5144
- throw internalError4({ message: "keyPage uses table.name as the derived-table alias \u2014 fromSelect and table.alias must be unset" });
5768
+ throw internalError5({ message: "keyPage uses table.name as the derived-table alias \u2014 fromSelect and table.alias must be unset" });
5145
5769
  }
5146
5770
  from = `(${renderKeyPage({ dialect, page: statement.keyPage, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
5147
5771
  } else if (statement.fromSelect !== undefined) {
5148
5772
  if (statement.table.alias !== undefined) {
5149
- throw internalError4({ message: "fromSelect uses table.name as the subquery alias \u2014 table.alias must be unset" });
5773
+ throw internalError5({ message: "fromSelect uses table.name as the subquery alias \u2014 table.alias must be unset" });
5150
5774
  }
5151
5775
  const inner = renderSelect({ dialect, statement: statement.fromSelect, collector });
5152
5776
  from = `(${inner.text}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
@@ -5157,7 +5781,7 @@ function renderSelect(params) {
5157
5781
  if (statement.innerJoins !== undefined && statement.innerJoins.length > 0) {
5158
5782
  for (const join of statement.innerJoins) {
5159
5783
  if (join.on.length === 0) {
5160
- throw internalError4({ message: "inner join needs at least one ON pair" });
5784
+ throw internalError5({ message: "inner join needs at least one ON pair" });
5161
5785
  }
5162
5786
  const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
5163
5787
  text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
@@ -5168,7 +5792,7 @@ function renderSelect(params) {
5168
5792
  }
5169
5793
  if (statement.leftJoins !== undefined && statement.leftJoins.length > 0) {
5170
5794
  if (statement.rowLock !== undefined) {
5171
- throw internalError4({ message: "a locking read cannot carry a left join \u2014 postgres refuses FOR UPDATE on the nullable side of an outer join" });
5795
+ throw internalError5({ message: "a locking read cannot carry a left join \u2014 postgres refuses FOR UPDATE on the nullable side of an outer join" });
5172
5796
  }
5173
5797
  for (const join of statement.leftJoins) {
5174
5798
  text += ` ${renderLeftJoin({ dialect, join, collector })}`;
@@ -5176,7 +5800,7 @@ function renderSelect(params) {
5176
5800
  }
5177
5801
  if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
5178
5802
  if (!dialect.capabilities.lateralJoin || dialect.lateralJson === null) {
5179
- throw unsupportedCapability28({
5803
+ throw unsupportedCapability29({
5180
5804
  dialect: dialect.name,
5181
5805
  feature: "LATERAL JOIN relation loading",
5182
5806
  hint: 'use the portable "query" relation strategy on this dialect'
@@ -5190,13 +5814,25 @@ function renderSelect(params) {
5190
5814
  text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
5191
5815
  }
5192
5816
  if (statement.groupBy !== undefined && statement.groupBy.length > 0) {
5193
- text += ` GROUP BY ${statement.groupBy.map((column) => quoteCol(dialect, column)).join(", ")}`;
5817
+ text += ` GROUP BY ${statement.groupingSets === undefined ? groupKeys.join(", ") : renderGroupingSets({ dialect, keys: groupKeys, grouping: statement.groupingSets })}`;
5818
+ } else if (statement.groupingSets !== undefined) {
5819
+ throw internalError5({ message: "grouping sets need groupBy columns" });
5194
5820
  }
5195
5821
  if (statement.having !== undefined) {
5196
5822
  text += ` HAVING ${renderExpr({ dialect, expr: statement.having, collector })}`;
5197
5823
  }
5198
5824
  if (statement.orderBy !== undefined && statement.orderBy.length > 0) {
5199
- const terms = statement.orderBy.map((term) => renderOrderTerm({ dialect, term: "column" in term ? { ...term, column: typedOrderRef({ statement, column: term.column }) } : term, collector }));
5825
+ const terms = statement.orderBy.map((term) => {
5826
+ if (!("column" in term))
5827
+ return renderOrderTerm({ dialect, term, collector });
5828
+ const nullsOutputName = term.nulls === undefined || dialect.orderByNullsMode === "native" ? undefined : groupKeyOutputName({ dialect, statement, column: term.column });
5829
+ return renderOrderTerm({
5830
+ dialect,
5831
+ term: { ...term, column: typedOrderRef({ statement, column: term.column }) },
5832
+ collector,
5833
+ ...nullsOutputName === undefined ? {} : { nullsOutputName }
5834
+ });
5835
+ });
5200
5836
  text += ` ORDER BY ${terms.join(", ")}`;
5201
5837
  }
5202
5838
  if (statement.limit !== undefined) {
@@ -5213,10 +5849,10 @@ function renderSelect(params) {
5213
5849
  if (statement.rowLock !== undefined) {
5214
5850
  const target = statement.table.alias ?? statement.table.name;
5215
5851
  if (statement.rowLock.of !== undefined && statement.rowLock.of.some((name) => name !== target)) {
5216
- throw internalError4({ message: `a row lock's \`of\` list may name only the statement's own table "${target}"` });
5852
+ throw internalError5({ message: `a row lock's \`of\` list may name only the statement's own table "${target}"` });
5217
5853
  }
5218
5854
  if (statement.rowLock.of !== undefined && (statement.fromSelect !== undefined || statement.keyPage !== undefined)) {
5219
- throw internalError4({ message: "a row lock's `of` list cannot name a derived table" });
5855
+ throw internalError5({ message: "a row lock's `of` list cannot name a derived table" });
5220
5856
  }
5221
5857
  const clause = renderRowLockClause({ dialect, lock: statement.rowLock });
5222
5858
  if (clause !== null)
@@ -5232,7 +5868,7 @@ function unnestElement(params) {
5232
5868
  return null;
5233
5869
  if (isJsonParamCell(value)) {
5234
5870
  if (jsonStorage === undefined || (value.storage ?? "jsonb") !== jsonStorage) {
5235
- throw internalError4({ message: `unnest insert column "${column}" got a Json cell its declared type does not take` });
5871
+ throw internalError5({ message: `unnest insert column "${column}" got a Json cell its declared type does not take` });
5236
5872
  }
5237
5873
  return value.text;
5238
5874
  }
@@ -5248,24 +5884,24 @@ function unnestElement(params) {
5248
5884
  return value;
5249
5885
  }
5250
5886
  }
5251
- throw internalError4({ message: `unnest insert column "${column}" got a cell that is not a plain value (DEFAULT, the database clock, a document) \u2014 such rows keep VALUES` });
5887
+ throw internalError5({ message: `unnest insert column "${column}" got a cell that is not a plain value (DEFAULT, the database clock, a document) \u2014 such rows keep VALUES` });
5252
5888
  }
5253
5889
  function renderUnnestRows(params) {
5254
5890
  const { dialect, statement, members, collector } = params;
5255
5891
  if (dialect.bulkInsertForm !== "unnest") {
5256
- throw unsupportedCapability28({
5892
+ throw unsupportedCapability29({
5257
5893
  dialect: dialect.name,
5258
5894
  feature: "a bulk insert bound as typed arrays (INSERT \u2026 SELECT FROM unnest)",
5259
5895
  hint: "this dialect binds createMany rows as VALUES"
5260
5896
  });
5261
5897
  }
5262
5898
  if (statement.columns.length === 0)
5263
- throw internalError4({ message: "an unnest insert needs at least one column" });
5899
+ throw internalError5({ message: "an unnest insert needs at least one column" });
5264
5900
  if (members.length !== statement.columns.length) {
5265
- throw internalError4({ message: `unnest insert has ${members.length} column types for ${statement.columns.length} columns` });
5901
+ throw internalError5({ message: `unnest insert has ${members.length} column types for ${statement.columns.length} columns` });
5266
5902
  }
5267
5903
  if (statement.onConflict !== undefined && !("doNothing" in statement.onConflict)) {
5268
- throw internalError4({ message: "an unnest insert takes no DO UPDATE arm \u2014 upserts bind VALUES" });
5904
+ throw internalError5({ message: "an unnest insert takes no DO UPDATE arm \u2014 upserts bind VALUES" });
5269
5905
  }
5270
5906
  const aliases = statement.columns.map((_column, index) => dialect.quoteIdent({ name: `c${index + 1}` }));
5271
5907
  const arrays = members.map((member, index) => {
@@ -5281,14 +5917,14 @@ function renderUnnestRows(params) {
5281
5917
  function renderInsert(params) {
5282
5918
  const { dialect, statement, collector } = params;
5283
5919
  if (statement.rows.length === 0) {
5284
- throw internalError4({ message: "insert requires at least one row" });
5920
+ throw internalError5({ message: "insert requires at least one row" });
5285
5921
  }
5286
5922
  const defaultValues = statement.columns.length === 0 && dialect.emptyInsert.form === "defaultValues";
5287
5923
  if (defaultValues && statement.rows.length !== 1) {
5288
- throw internalError4({ message: `${dialect.name} inserts an all-default row as DEFAULT VALUES, one row per statement` });
5924
+ throw internalError5({ message: `${dialect.name} inserts an all-default row as DEFAULT VALUES, one row per statement` });
5289
5925
  }
5290
5926
  if (defaultValues && statement.onConflict !== undefined && !(dialect.emptyInsert.form === "defaultValues" && dialect.emptyInsert.conflictClause)) {
5291
- throw unsupportedCapability28({
5927
+ throw unsupportedCapability29({
5292
5928
  dialect: dialect.name,
5293
5929
  feature: "a conflict clause on an all-default row (DEFAULT VALUES)",
5294
5930
  hint: "supply at least one column value, or drop the conflict clause"
@@ -5296,7 +5932,7 @@ function renderInsert(params) {
5296
5932
  }
5297
5933
  for (const row of statement.rows) {
5298
5934
  if (row.length !== statement.columns.length) {
5299
- throw internalError4({
5935
+ throw internalError5({
5300
5936
  message: `insert row has ${row.length} values but ${statement.columns.length} columns`
5301
5937
  });
5302
5938
  }
@@ -5314,7 +5950,7 @@ function renderInsert(params) {
5314
5950
  if (!isSqlDefaultCell(value))
5315
5951
  return collector.add(value);
5316
5952
  if (!dialect.rowDefaultKeyword) {
5317
- throw internalError4({
5953
+ throw internalError5({
5318
5954
  message: `${dialect.name} cannot render a per-row DEFAULT cell \u2014 rows must be grouped by column set`
5319
5955
  });
5320
5956
  }
@@ -5330,7 +5966,7 @@ function renderInsert(params) {
5330
5966
  let target = "";
5331
5967
  if (onConflict.constraint !== undefined) {
5332
5968
  if (dialect.name !== "postgres") {
5333
- throw unsupportedCapability28({
5969
+ throw unsupportedCapability29({
5334
5970
  dialect: dialect.name,
5335
5971
  feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
5336
5972
  hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
@@ -5346,7 +5982,7 @@ function renderInsert(params) {
5346
5982
  text += ` ON CONFLICT${target} DO NOTHING`;
5347
5983
  } else {
5348
5984
  if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
5349
- throw unsupportedCapability28({
5985
+ throw unsupportedCapability29({
5350
5986
  dialect: dialect.name,
5351
5987
  feature: "a refined conflict target (constraint / partial-index predicate) on ON DUPLICATE KEY UPDATE",
5352
5988
  hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
@@ -5354,36 +5990,36 @@ function renderInsert(params) {
5354
5990
  }
5355
5991
  const keepName = onConflict.keepColumn ?? statement.columns[0];
5356
5992
  if (keepName === undefined) {
5357
- throw internalError4({ message: "a skip-duplicates insert of an all-default row must name its keepColumn" });
5993
+ throw internalError5({ message: "a skip-duplicates insert of an all-default row must name its keepColumn" });
5358
5994
  }
5359
5995
  const keep = dialect.quoteIdent({ name: keepName });
5360
5996
  const current = `${dialect.quoteIdent({ name: statement.table.name })}.${keep}`;
5361
5997
  const flag = onConflict.duplicateFlag;
5362
5998
  if (flag !== undefined && !/^\d{1,20}$/.test(flag)) {
5363
- throw internalError4({ message: `duplicateFlag must be a decimal literal, got ${JSON.stringify(flag)}` });
5999
+ throw internalError5({ message: `duplicateFlag must be a decimal literal, got ${JSON.stringify(flag)}` });
5364
6000
  }
5365
6001
  text += flag === undefined ? ` ON DUPLICATE KEY UPDATE ${keep} = ${current}` : ` ON DUPLICATE KEY UPDATE ${keep} = IF(LAST_INSERT_ID(${flag}), ${current}, ${current})`;
5366
6002
  }
5367
6003
  } else {
5368
6004
  const updates = Object.entries(onConflict.update);
5369
6005
  if (updates.length === 0) {
5370
- throw internalError4({ message: "upsert update must set at least one column" });
6006
+ throw internalError5({ message: "upsert update must set at least one column" });
5371
6007
  }
5372
6008
  const selfRef = dialect.quoteIdent({ name: statement.table.name });
5373
6009
  const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true, tableName: statement.table.name });
5374
6010
  if (dialect.upsertForm === "onConflict") {
5375
6011
  if (onConflict.lastInsertIdColumn !== undefined) {
5376
- throw internalError4({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
6012
+ throw internalError5({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
5377
6013
  }
5378
6014
  if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
5379
- throw unsupportedCapability28({
6015
+ throw unsupportedCapability29({
5380
6016
  dialect: dialect.name,
5381
6017
  feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
5382
6018
  hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
5383
6019
  });
5384
6020
  }
5385
6021
  if (onConflict.constraint !== undefined && onConflict.conflictWhere !== undefined) {
5386
- throw internalError4({
6022
+ throw internalError5({
5387
6023
  message: "a named-constraint conflict target already identifies the index \u2014 it cannot carry a partial predicate too"
5388
6024
  });
5389
6025
  }
@@ -5392,7 +6028,7 @@ function renderInsert(params) {
5392
6028
  text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
5393
6029
  if (onConflict.updateWhere !== undefined) {
5394
6030
  if (!dialect.capabilities.conflictUpdateWhere) {
5395
- throw unsupportedCapability28({
6031
+ throw unsupportedCapability29({
5396
6032
  dialect: dialect.name,
5397
6033
  feature: "a guarded conflict update (ON CONFLICT \u2026 DO UPDATE \u2026 WHERE)",
5398
6034
  hint: "run the guarded upsert as a find-then-write in a transaction instead"
@@ -5402,21 +6038,21 @@ function renderInsert(params) {
5402
6038
  }
5403
6039
  } else {
5404
6040
  if (onConflict.updateWhere !== undefined) {
5405
- throw unsupportedCapability28({
6041
+ throw unsupportedCapability29({
5406
6042
  dialect: dialect.name,
5407
6043
  feature: "a guarded conflict update (ON DUPLICATE KEY UPDATE has no WHERE)",
5408
6044
  hint: "run the guarded upsert as a find-then-write in a transaction instead \u2014 the runtime does this when the dialect lacks conflictUpdateWhere"
5409
6045
  });
5410
6046
  }
5411
6047
  if (onConflict.constraint !== undefined) {
5412
- throw unsupportedCapability28({
6048
+ throw unsupportedCapability29({
5413
6049
  dialect: dialect.name,
5414
6050
  feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
5415
6051
  hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
5416
6052
  });
5417
6053
  }
5418
6054
  if (onConflict.conflictWhere !== undefined) {
5419
- throw unsupportedCapability28({
6055
+ throw unsupportedCapability29({
5420
6056
  dialect: dialect.name,
5421
6057
  feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
5422
6058
  hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
@@ -5427,25 +6063,35 @@ function renderInsert(params) {
5427
6063
  }
5428
6064
  }
5429
6065
  }
5430
- const returning = applyReturning({ dialect, requested: statement.returning });
6066
+ const returning = applyReturning({ dialect, requested: statement.returning, collector });
5431
6067
  return { text: text + returning.suffix, values: collector.values, returning: returning.status };
5432
6068
  }
5433
6069
  function renderUpdate(params) {
5434
6070
  const { dialect, statement, collector } = params;
5435
6071
  const assignments = Object.entries(statement.set);
5436
6072
  if (assignments.length === 0) {
5437
- throw internalError4({ message: "update must set at least one column" });
6073
+ throw internalError5({ message: "update must set at least one column" });
5438
6074
  }
5439
6075
  const source = statement.from;
5440
6076
  if (source !== undefined) {
5441
6077
  if (statement.alias === undefined || statement.alias === source.alias)
5442
- throw internalError4({ message: "UPDATE \u2026 FROM needs distinct target and source aliases" });
6078
+ throw internalError5({ message: "UPDATE \u2026 FROM needs distinct target and source aliases" });
5443
6079
  const target = dialect.quoteIdent({ name: statement.alias });
5444
6080
  const joined = dialect.quoteIdent({ name: source.alias });
5445
6081
  const assign = () => renderAssignments({ dialect, entries: assignments, collector, selfRef: target, source: joined, ...dialect.name === "mysql" ? { qualifyWrite: target } : {}, ...params.earlier === undefined ? {} : { steps: params.earlier } });
5446
6082
  const where = () => statement.where === undefined ? "" : ` AND ${renderExpr({ dialect, expr: statement.where, collector })}`;
6083
+ if ("select" in source) {
6084
+ if (!dialect.capabilities.returningReadsJoinedSource) {
6085
+ throw unsupportedCapability29({ dialect: dialect.name, feature: "UPDATE \u2026 FROM (SELECT \u2026) with RETURNING of the joined source", hint: "read the row inside the write's transaction first, then write it" });
6086
+ }
6087
+ const set = assign();
6088
+ const joinedSelect = renderSelect({ dialect, statement: source.select, collector }).text;
6089
+ const text = `UPDATE ${renderTable(dialect, statement.table)} AS ${target} SET ${set} FROM (${joinedSelect}) AS ${joined} WHERE ${renderExpr({ dialect, expr: source.on, collector })}${where()}`;
6090
+ const returning = applyReturning({ dialect, requested: statement.returning, qualify: target });
6091
+ return { text: text + returning.suffix, values: collector.values, returning: returning.status };
6092
+ }
5447
6093
  const text = dialect.name === "mysql" ? `UPDATE ${renderTable(dialect, statement.table)} AS ${target} JOIN ${renderTable(dialect, source.table)} AS ${joined} ON ${renderExpr({ dialect, expr: source.on, collector })} SET ${assign()}${statement.where === undefined ? "" : ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`}` : `UPDATE ${renderTable(dialect, statement.table)} AS ${target} SET ${assign()} FROM ${renderTable(dialect, source.table)} AS ${joined} WHERE ${renderExpr({ dialect, expr: source.on, collector })}${where()}`;
5448
- const returning = applyReturning({ dialect, requested: statement.returning, ...dialect.name === "postgres" ? { qualify: target } : {} });
6094
+ const returning = applyReturning({ dialect, requested: statement.returning, collector, ...dialect.name === "postgres" ? { qualify: target } : {} });
5449
6095
  return { text: text + returning.suffix, values: collector.values, returning: returning.status };
5450
6096
  }
5451
6097
  const setSql = renderAssignments({ dialect, entries: assignments, collector, ...params.earlier === undefined ? {} : { steps: params.earlier } });
@@ -5453,7 +6099,7 @@ function renderUpdate(params) {
5453
6099
  if (statement.where !== undefined) {
5454
6100
  text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
5455
6101
  }
5456
- const returning = applyReturning({ dialect, requested: statement.returning });
6102
+ const returning = applyReturning({ dialect, requested: statement.returning, collector });
5457
6103
  return { text: text + returning.suffix, values: collector.values, returning: returning.status };
5458
6104
  }
5459
6105
  function renderIncomingRowColumn(params) {
@@ -5469,23 +6115,23 @@ function renderAssignments(params) {
5469
6115
  const read = selfRef === undefined ? column : `${selfRef}.${column}`;
5470
6116
  if (isSourceColumnRef(value)) {
5471
6117
  if (params.source === undefined)
5472
- throw internalError4({ message: `an UPDATE set value reads the joined source's column ${value.column}, but the statement joins no source` });
6118
+ throw internalError5({ message: `an UPDATE set value reads the joined source's column ${value.column}, but the statement joins no source` });
5473
6119
  return `${column} = ${params.source}.${dialect.quoteIdent({ name: value.column })}`;
5474
6120
  }
5475
6121
  if (isStepColumnRef(value)) {
5476
6122
  if (params.steps === undefined || !params.steps.has(value.step)) {
5477
- throw internalError4({ message: `an UPDATE set value reads step "${value.step}", which is not an earlier chain step` });
6123
+ throw internalError5({ message: `an UPDATE set value reads step "${value.step}", which is not an earlier chain step` });
5478
6124
  }
5479
6125
  return `${column} = (SELECT ${dialect.quoteIdent({ name: value.column })} FROM ${dialect.quoteIdent({ name: value.step })})`;
5480
6126
  }
5481
6127
  if (isSqlFragment(value)) {
5482
6128
  if (params.allowExcluded !== true || selfRef === undefined)
5483
- throw internalError4({ message: "SQL assignment fragments require a conflict update" });
6129
+ throw internalError5({ message: "SQL assignment fragments require a conflict update" });
5484
6130
  return `${column} = ${renderSqlFragment({ dialect, fragment: value, collector, conflict: { table: params.tableName } })}`;
5485
6131
  }
5486
6132
  if (isExcludedRef(value)) {
5487
6133
  if (params.allowExcluded !== true) {
5488
- throw internalError4({
6134
+ throw internalError5({
5489
6135
  message: `an incoming-row reference (${value.column}) is only meaningful inside an upsert's update arm`
5490
6136
  });
5491
6137
  }
@@ -5493,9 +6139,12 @@ function renderAssignments(params) {
5493
6139
  }
5494
6140
  if (isSqlKeepCell(value)) {
5495
6141
  if (params.allowExcluded !== true || selfRef === undefined)
5496
- throw internalError4({ message: "a keep-column cell is only meaningful inside an upsert's update arm" });
6142
+ throw internalError5({ message: "a keep-column cell is only meaningful inside an upsert's update arm" });
5497
6143
  return `${column} = ${read}`;
5498
6144
  }
6145
+ if (isJsonPatchOperation(value)) {
6146
+ return `${column} = ${renderJsonPatch({ dialect, patch: value, read, collector })}`;
6147
+ }
5499
6148
  if (isJsonParamCell(value)) {
5500
6149
  return `${column} = ${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
5501
6150
  }
@@ -5523,7 +6172,7 @@ function renderDelete(params) {
5523
6172
  if (statement.where !== undefined) {
5524
6173
  text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
5525
6174
  }
5526
- const returning = applyReturning({ dialect, requested: statement.returning });
6175
+ const returning = applyReturning({ dialect, requested: statement.returning, collector });
5527
6176
  return { text: text + returning.suffix, values: collector.values, returning: returning.status };
5528
6177
  }
5529
6178
  var CHAIN_ROWS_ALIAS = "__vibe_rows";
@@ -5531,19 +6180,19 @@ var CHAIN_ONE_ALIAS = "__vibe_one";
5531
6180
  function renderChain(params) {
5532
6181
  const { dialect, statement, collector } = params;
5533
6182
  if (dialect.statementChain === null) {
5534
- throw unsupportedCapability28({
6183
+ throw unsupportedCapability29({
5535
6184
  dialect: dialect.name,
5536
6185
  feature: "a statement chain (data-modifying WITH steps)",
5537
6186
  hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
5538
6187
  });
5539
6188
  }
5540
6189
  if (statement.steps.length === 0)
5541
- throw internalError4({ message: "a statement chain needs at least one step" });
6190
+ throw internalError5({ message: "a statement chain needs at least one step" });
5542
6191
  const defined = new Set;
5543
6192
  const steps = [];
5544
6193
  for (const step of statement.steps) {
5545
6194
  if (step.name === CHAIN_ROWS_ALIAS || step.name === CHAIN_ONE_ALIAS || defined.has(step.name)) {
5546
- throw internalError4({ message: `chain step name "${step.name}" is reserved or repeated` });
6195
+ throw internalError5({ message: `chain step name "${step.name}" is reserved or repeated` });
5547
6196
  }
5548
6197
  const body = renderChainStep({ dialect, statement: step.statement, collector, earlier: defined });
5549
6198
  steps.push(`${dialect.quoteIdent({ name: step.name })} AS (${body})`);
@@ -5552,11 +6201,11 @@ function renderChain(params) {
5552
6201
  const result = statement.result;
5553
6202
  const captures = result.capture ?? [];
5554
6203
  if (captures.length > 0 && !dialect.capabilities.rowCapture) {
5555
- throw unsupportedCapability28({ dialect: dialect.name, feature: "row capture (RETURNING old./new.)", hint: "row capture needs PostgreSQL 18 or later; the runtime refuses $transaction({ capture }) on this dialect" });
6204
+ throw unsupportedCapability29({ dialect: dialect.name, feature: "row capture (RETURNING old./new.)", hint: "row capture needs PostgreSQL 18 or later; the runtime refuses $transaction({ capture }) on this dialect" });
5556
6205
  }
5557
6206
  for (const alias of captureAliases({ captures })) {
5558
6207
  if (result.columns.includes(alias) || alias === result.verdict?.alias || alias === result.tally?.alias) {
5559
- throw internalError4({ message: `chain capture alias "${alias}" collides with a result column` });
6208
+ throw internalError5({ message: `chain capture alias "${alias}" collides with a result column` });
5560
6209
  }
5561
6210
  }
5562
6211
  if (result.rowsFrom.length === 0 && result.columns.length === 0 && result.tally !== undefined && result.verdict === undefined) {
@@ -5564,11 +6213,11 @@ function renderChain(params) {
5564
6213
  return { text: `WITH ${steps.join(", ")} SELECT ${[tally, ...renderChainCaptures({ dialect, captures, defined, collector })].join(", ")}`, values: collector.values, returning: "none" };
5565
6214
  }
5566
6215
  if (result.rowsFrom.length === 0 || result.columns.length === 0) {
5567
- throw internalError4({ message: "a chain result needs at least one source step and one column" });
6216
+ throw internalError5({ message: "a chain result needs at least one source step and one column" });
5568
6217
  }
5569
6218
  for (const name of result.rowsFrom) {
5570
6219
  if (!defined.has(name))
5571
- throw internalError4({ message: `chain result reads undefined step "${name}"` });
6220
+ throw internalError5({ message: `chain result reads undefined step "${name}"` });
5572
6221
  }
5573
6222
  const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
5574
6223
  const guarded = result.verdict !== undefined || result.tally !== undefined || result.veto !== undefined && result.veto.length > 0;
@@ -5579,7 +6228,7 @@ function renderChain(params) {
5579
6228
  const extra = [];
5580
6229
  for (const alias of [result.verdict?.alias, result.tally?.alias]) {
5581
6230
  if (alias !== undefined && result.columns.includes(alias)) {
5582
- throw internalError4({ message: `chain result alias "${alias}" collides with a result column` });
6231
+ throw internalError5({ message: `chain result alias "${alias}" collides with a result column` });
5583
6232
  }
5584
6233
  }
5585
6234
  const rows = dialect.quoteIdent({ name: CHAIN_ROWS_ALIAS });
@@ -5624,6 +6273,8 @@ function captureAliases(params) {
5624
6273
  if (entry.inserted !== undefined)
5625
6274
  out.push(`${entry.alias}_ia`);
5626
6275
  }
6276
+ for (const index of (entry.cascades ?? []).keys())
6277
+ out.push(`${entry.alias}_c${index}`);
5627
6278
  }
5628
6279
  return out;
5629
6280
  }
@@ -5633,7 +6284,7 @@ function renderChainCaptures(params) {
5633
6284
  const out = [];
5634
6285
  for (const entry of captures) {
5635
6286
  if (!defined.has(entry.step))
5636
- throw internalError4({ message: `chain capture reads undefined step "${entry.step}"` });
6287
+ throw internalError5({ message: `chain capture reads undefined step "${entry.step}"` });
5637
6288
  const step = quote(entry.step);
5638
6289
  const marker = entry.inserted === undefined ? undefined : quote(entry.inserted);
5639
6290
  out.push(`(SELECT count(*) FROM ${step}) AS ${quote(`${entry.alias}_n`)}`);
@@ -5641,23 +6292,36 @@ function renderChainCaptures(params) {
5641
6292
  out.push(`(SELECT count(*) FROM ${step} WHERE ${marker}) AS ${quote(`${entry.alias}_i`)}`);
5642
6293
  const before = entry.before ?? [];
5643
6294
  const after = entry.after ?? [];
5644
- if (before.length === 0 && after.length === 0)
5645
- continue;
5646
- if (!Number.isSafeInteger(entry.limit) || entry.limit < 1)
5647
- throw internalError4({ message: `chain capture "${entry.alias}" needs a positive integer limit` });
5648
- const limit = collector.add(entry.limit);
5649
- const item = quote("__vibe_i");
5650
- const image = (columns, filter) => `(SELECT array_agg(${item})::text FROM (SELECT DISTINCT ARRAY[${columns.map(quote).join(", ")}] AS ${item} FROM ${step}${filter} LIMIT ${limit}) AS ${quote("__vibe_d")})`;
5651
- const updatedRows = marker === undefined ? "" : ` WHERE NOT ${marker}`;
5652
- if (before.length > 0)
5653
- out.push(`${image(before, updatedRows)} AS ${quote(`${entry.alias}_b`)}`);
5654
- if (after.length > 0 && (marker === undefined || entry.insertedOnly !== true))
5655
- out.push(`${image(after, updatedRows)} AS ${quote(`${entry.alias}_a`)}`);
5656
- if (after.length > 0 && marker !== undefined)
5657
- out.push(`${image(after, ` WHERE ${marker}`)} AS ${quote(`${entry.alias}_ia`)}`);
6295
+ out.push(...renderImageAggregates({ dialect, entry, step, marker, before, after, collector }));
6296
+ for (const [index, cascade] of (entry.cascades ?? []).entries()) {
6297
+ const keys = quote("__vibe_k");
6298
+ const child = quote(CAPTURE_CHILD_ALIAS);
6299
+ const match = `EXISTS (SELECT 1 FROM ${step} AS ${keys} WHERE ${cascade.on.map((pair) => `${keys}.${quote(pair.parent)} = ${child}.${quote(pair.child)}`).join(" AND ")})`;
6300
+ out.push(`${renderCascadeImages({ dialect, cascade, match, collector })} AS ${quote(`${entry.alias}_c${index}`)}`);
6301
+ }
5658
6302
  }
5659
6303
  return out;
5660
6304
  }
6305
+ function renderImageAggregates(params) {
6306
+ const { dialect, entry, step, marker, before, after, collector } = params;
6307
+ const quote = (name) => dialect.quoteIdent({ name });
6308
+ const out = [];
6309
+ if (before.length === 0 && after.length === 0)
6310
+ return out;
6311
+ if (!Number.isSafeInteger(entry.limit) || entry.limit < 1)
6312
+ throw internalError5({ message: `chain capture "${entry.alias}" needs a positive integer limit` });
6313
+ const limit = collector.add(entry.limit);
6314
+ const item = quote("__vibe_i");
6315
+ const image = (columns, filter) => `(SELECT array_agg(${item})::text FROM (SELECT DISTINCT ARRAY[${columns.map(quote).join(", ")}] AS ${item} FROM ${step}${filter} LIMIT ${limit}) AS ${quote("__vibe_d")})`;
6316
+ const updatedRows = marker === undefined ? "" : ` WHERE NOT ${marker}`;
6317
+ if (before.length > 0)
6318
+ out.push(`${image(before, updatedRows)} AS ${quote(`${entry.alias}_b`)}`);
6319
+ if (after.length > 0 && (marker === undefined || entry.insertedOnly !== true))
6320
+ out.push(`${image(after, updatedRows)} AS ${quote(`${entry.alias}_a`)}`);
6321
+ if (after.length > 0 && marker !== undefined)
6322
+ out.push(`${image(after, ` WHERE ${marker}`)} AS ${quote(`${entry.alias}_ia`)}`);
6323
+ return out;
6324
+ }
5661
6325
  function renderSelectCell(params) {
5662
6326
  const { dialect, value, collector } = params;
5663
6327
  if (isJsonParamCell(value))
@@ -5665,21 +6329,21 @@ function renderSelectCell(params) {
5665
6329
  if (isDbNowMarker(value))
5666
6330
  return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
5667
6331
  if (isSqlDefaultCell(value)) {
5668
- throw internalError4({ message: "DEFAULT is not allowed in an INSERT \u2026 SELECT list \u2014 omit the column instead" });
6332
+ throw internalError5({ message: "DEFAULT is not allowed in an INSERT \u2026 SELECT list \u2014 omit the column instead" });
5669
6333
  }
5670
6334
  return collector.add(value);
5671
6335
  }
5672
6336
  function renderRowSource(params) {
5673
6337
  const { dialect, source, collector } = params;
5674
6338
  if (source.columns.length === 0 || source.rows.length === 0) {
5675
- throw internalError4({ message: "a row source needs at least one column and one row" });
6339
+ throw internalError5({ message: "a row source needs at least one column and one row" });
5676
6340
  }
5677
6341
  const typedBy = renderTable(dialect, source.typedBy);
5678
6342
  const columns = source.columns.map((name) => dialect.quoteIdent({ name }));
5679
6343
  const rows = [`(${columns.map((column) => `(SELECT ${column} FROM ${typedBy} WHERE FALSE)`).join(", ")})`];
5680
6344
  for (const row of source.rows) {
5681
6345
  if (row.length !== source.columns.length) {
5682
- throw internalError4({ message: `row source row has ${row.length} values but ${source.columns.length} columns` });
6346
+ throw internalError5({ message: `row source row has ${row.length} values but ${source.columns.length} columns` });
5683
6347
  }
5684
6348
  rows.push(`(${row.map((value) => renderSelectCell({ dialect, value, collector })).join(", ")})`);
5685
6349
  }
@@ -5691,12 +6355,12 @@ function renderTallyColumn(params) {
5691
6355
  const { dialect, result, defined, collector } = params;
5692
6356
  const tally = result.tally;
5693
6357
  if (tally === undefined)
5694
- throw internalError4({ message: "a chain veto needs a tally column to guard" });
6358
+ throw internalError5({ message: "a chain veto needs a tally column to guard" });
5695
6359
  if (tally.steps.length === 0)
5696
- throw internalError4({ message: "a chain tally needs at least one step" });
6360
+ throw internalError5({ message: "a chain tally needs at least one step" });
5697
6361
  const step = (name) => {
5698
6362
  if (!defined.has(name))
5699
- throw internalError4({ message: `chain tally or veto reads undefined step "${name}"` });
6363
+ throw internalError5({ message: `chain tally or veto reads undefined step "${name}"` });
5700
6364
  return dialect.quoteIdent({ name });
5701
6365
  };
5702
6366
  const count = (name) => `(SELECT count(*) FROM ${step(name)})`;
@@ -5706,14 +6370,14 @@ function renderTallyColumn(params) {
5706
6370
  return `${sum} AS ${dialect.quoteIdent({ name: tally.alias })}`;
5707
6371
  const arms = vetoes.map((veto) => {
5708
6372
  if (!CHAIN_VETO_TOKEN_PATTERN.test(veto.token))
5709
- throw internalError4({ message: `chain veto token "${veto.token}" is not a lower-case word` });
6373
+ throw internalError5({ message: `chain veto token "${veto.token}" is not a lower-case word` });
5710
6374
  if (typeof veto.nonce !== "string" || veto.nonce === "")
5711
- throw internalError4({ message: `chain veto "${veto.token}" needs a non-empty nonce` });
6375
+ throw internalError5({ message: `chain veto "${veto.token}" needs a non-empty nonce` });
5712
6376
  const minRows = veto.minRows ?? 1;
5713
6377
  if (!Number.isSafeInteger(minRows) || minRows < 1)
5714
- throw internalError4({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
6378
+ throw internalError5({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
5715
6379
  if (veto.exactRows !== undefined && (!Number.isSafeInteger(veto.exactRows) || veto.exactRows < 0 || veto.minRows !== undefined)) {
5716
- throw internalError4({ message: `chain veto "${veto.token}" needs a nonnegative safe integer exactRows without minRows` });
6380
+ throw internalError5({ message: `chain veto "${veto.token}" needs a nonnegative safe integer exactRows without minRows` });
5717
6381
  }
5718
6382
  const when = veto.exactRows !== undefined ? `WHEN ${count(veto.step)} <> ${veto.exactRows}` : minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
5719
6383
  return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
@@ -5723,25 +6387,25 @@ function renderTallyColumn(params) {
5723
6387
  function renderInsertSelect(params) {
5724
6388
  const { dialect, statement, collector } = params;
5725
6389
  if (statement.columns.length === 0)
5726
- throw internalError4({ message: "insert requires at least one column" });
6390
+ throw internalError5({ message: "insert requires at least one column" });
5727
6391
  if (statement.cells.length !== statement.columns.length) {
5728
- throw internalError4({
6392
+ throw internalError5({
5729
6393
  message: `insert row has ${statement.cells.length} values but ${statement.columns.length} columns`
5730
6394
  });
5731
6395
  }
5732
6396
  const from = statement.from ?? [];
5733
6397
  for (const name of from) {
5734
6398
  if (!params.earlier.has(name))
5735
- throw internalError4({ message: `INSERT \u2026 SELECT reads step "${name}" before it is defined` });
6399
+ throw internalError5({ message: `INSERT \u2026 SELECT reads step "${name}" before it is defined` });
5736
6400
  }
5737
6401
  const source = statement.source;
5738
6402
  if (source !== undefined && (params.earlier.has(source.alias) || from.includes(source.alias))) {
5739
- throw internalError4({ message: `row source alias "${source.alias}" collides with a chain step` });
6403
+ throw internalError5({ message: `row source alias "${source.alias}" collides with a chain step` });
5740
6404
  }
5741
6405
  const cells = statement.cells.map((value) => {
5742
6406
  if (isStepColumnRef(value)) {
5743
6407
  if (!from.includes(value.step) && value.step !== source?.alias) {
5744
- throw internalError4({ message: `cell reads step "${value.step}", which the INSERT \u2026 SELECT does not name in FROM` });
6408
+ throw internalError5({ message: `cell reads step "${value.step}", which the INSERT \u2026 SELECT does not name in FROM` });
5745
6409
  }
5746
6410
  return `${dialect.quoteIdent({ name: value.step })}.${dialect.quoteIdent({ name: value.column })}`;
5747
6411
  }
@@ -5760,9 +6424,9 @@ function renderInsertSelect(params) {
5760
6424
  const conflict = statement.conflictDoNothing;
5761
6425
  if (conflict !== undefined) {
5762
6426
  if (conflict.columns.length === 0)
5763
- throw internalError4({ message: "an INSERT \u2026 SELECT conflict clause needs at least one column" });
6427
+ throw internalError5({ message: "an INSERT \u2026 SELECT conflict clause needs at least one column" });
5764
6428
  if (dialect.doNothingForm !== "onConflictDoNothing") {
5765
- throw unsupportedCapability28({
6429
+ throw unsupportedCapability29({
5766
6430
  dialect: dialect.name,
5767
6431
  feature: "ON CONFLICT DO NOTHING on an INSERT \u2026 SELECT chain step",
5768
6432
  hint: "gate on SqlDialect.statementChain and keep the multi-statement path"
@@ -5770,10 +6434,10 @@ function renderInsertSelect(params) {
5770
6434
  }
5771
6435
  text += ` ON CONFLICT (${conflict.columns.map((name) => dialect.quoteIdent({ name })).join(", ")}) DO NOTHING`;
5772
6436
  }
5773
- return text + applyReturning({ dialect, requested: statement.returning }).suffix;
6437
+ return text + applyReturning({ dialect, requested: statement.returning, collector }).suffix;
5774
6438
  }
5775
6439
  // packages/sql/src/dialects/postgres-type-identity.ts
5776
- import { unsupportedCapability as unsupportedCapability29 } from "@vibeorm/schema";
6440
+ import { unsupportedCapability as unsupportedCapability30 } from "@vibeorm/schema";
5777
6441
  var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
5778
6442
  var MODIFIER_VALUE = `(?:[+-]?\\d+|${IDENTIFIER}|'(?:[^'\\x00]|'')*')`;
5779
6443
  var TYPE_NAME = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*(\\(\\s*${MODIFIER_VALUE}(?:\\s*,\\s*${MODIFIER_VALUE})*\\s*\\))?\\s*((?:\\[\\s*\\]\\s*)*)$`);
@@ -5800,13 +6464,13 @@ function parsePostgresTypeName(params) {
5800
6464
  }
5801
6465
  function renderPostgresTypeName(params) {
5802
6466
  if (params.dialect.name !== "postgres")
5803
- throw unsupportedCapability29({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
6467
+ throw unsupportedCapability30({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
5804
6468
  const { dialect, type } = params;
5805
6469
  return `${type.namespace === undefined ? "" : `${dialect.quoteIdent({ name: type.namespace })}.`}${dialect.quoteIdent({ name: type.name })}${type.modifier}${type.array ? "[]" : ""}`;
5806
6470
  }
5807
6471
  function renderPostgresTypeIdentity(params) {
5808
6472
  if (params.dialect.name !== "postgres")
5809
- throw unsupportedCapability29({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
6473
+ throw unsupportedCapability30({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
5810
6474
  const collector = new ParamCollector(params.dialect);
5811
6475
  const { source } = params;
5812
6476
  let text;
@@ -5819,11 +6483,11 @@ function renderPostgresTypeIdentity(params) {
5819
6483
  return { text, values: collector.values, returning: "none" };
5820
6484
  }
5821
6485
  // packages/sql/src/dialects/postgres-default-identity.ts
5822
- import { unsupportedCapability as unsupportedCapability30 } from "@vibeorm/schema";
6486
+ import { unsupportedCapability as unsupportedCapability31 } from "@vibeorm/schema";
5823
6487
  function renderPostgresDefaultFunctionIdentity(params) {
5824
6488
  const { dialect, table, column } = params;
5825
6489
  if (dialect.name !== "postgres")
5826
- throw unsupportedCapability30({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
6490
+ throw unsupportedCapability31({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
5827
6491
  const collector = new ParamCollector(dialect);
5828
6492
  const namespace = collector.add(table.namespace);
5829
6493
  const name = collector.add(table.name);
@@ -5847,28 +6511,28 @@ WHERE n.nspname = ${namespace} AND c.relname = ${name} AND a.attname = ${field}
5847
6511
  };
5848
6512
  }
5849
6513
  // packages/sql/src/dialects/constraint-owned-index.ts
5850
- import { unsupportedCapability as unsupportedCapability31 } from "@vibeorm/schema";
6514
+ import { unsupportedCapability as unsupportedCapability32 } from "@vibeorm/schema";
5851
6515
  function renderConstraintOwnedIndexDrop(params) {
5852
6516
  if (params.dialect.name !== "postgres")
5853
- throw unsupportedCapability31({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
6517
+ throw unsupportedCapability32({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
5854
6518
  const quote = (name) => params.dialect.quoteIdent({ name });
5855
6519
  return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
5856
6520
  }
5857
6521
  // packages/sql/src/dialects/mysql-check-probe.ts
5858
- import { unsupportedCapability as unsupportedCapability32 } from "@vibeorm/schema";
6522
+ import { unsupportedCapability as unsupportedCapability33 } from "@vibeorm/schema";
5859
6523
  function renderMysqlCheckProbe(params) {
5860
6524
  if (params.dialect.name !== "mysql")
5861
- throw unsupportedCapability32({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
6525
+ throw unsupportedCapability33({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
5862
6526
  const q = (name) => params.dialect.quoteIdent({ name });
5863
6527
  const probe = q(params.probe);
5864
6528
  switch (params.operation) {
5865
6529
  case "clone":
5866
6530
  if (params.table === undefined)
5867
- throw unsupportedCapability32({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
6531
+ throw unsupportedCapability33({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
5868
6532
  return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
5869
6533
  case "add": {
5870
6534
  if (params.expression === undefined || params.definition === undefined)
5871
- throw unsupportedCapability32({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
6535
+ throw unsupportedCapability33({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
5872
6536
  const parts = tableDefinitionParts({ sql: params.definition, backslashEscapes: true });
5873
6537
  return { text: `CREATE TEMPORARY TABLE ${probe} (
5874
6538
  ${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
@@ -5914,33 +6578,33 @@ function sqliteTableConstraints(params) {
5914
6578
  return result;
5915
6579
  }
5916
6580
  // packages/sql/src/dialects/migration-visibility.ts
5917
- import { unsupportedCapability as unsupportedCapability34 } from "@vibeorm/schema";
6581
+ import { unsupportedCapability as unsupportedCapability35 } from "@vibeorm/schema";
5918
6582
 
5919
6583
  // packages/sql/src/dialects/migration-inventory.ts
5920
- import { unsupportedCapability as unsupportedCapability33 } from "@vibeorm/schema";
6584
+ import { unsupportedCapability as unsupportedCapability34 } from "@vibeorm/schema";
5921
6585
  var MYSQL_NO_ROW_LIMIT = " LIMIT 18446744073709551615";
5922
6586
  function renderSqliteColumnInventory(params) {
5923
6587
  if (params.dialect.name !== "sqlite")
5924
- throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
6588
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
5925
6589
  const collector = new ParamCollector(params.dialect);
5926
6590
  return { text: `SELECT name, hidden, pk, dflt_value AS definition FROM pragma_table_xinfo(${collector.add(params.table)}, ${collector.add(params.namespace)}) ORDER BY cid`, values: collector.values, returning: "none" };
5927
6591
  }
5928
6592
  function renderSqliteForeignKeyInventory(params) {
5929
6593
  if (params.dialect.name !== "sqlite")
5930
- throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
6594
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
5931
6595
  const collector = new ParamCollector(params.dialect);
5932
6596
  return { text: `SELECT id, seq, "from", "to", "table", on_update, on_delete, match FROM pragma_foreign_key_list(${collector.add(params.table)}, ${collector.add(params.namespace)}) ORDER BY id, seq`, values: collector.values, returning: "none" };
5933
6597
  }
5934
6598
  function renderSqliteColumnInventoryBatch(params) {
5935
6599
  if (params.dialect.name !== "sqlite")
5936
- throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
6600
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
5937
6601
  const collector = new ParamCollector(params.dialect);
5938
6602
  const request = params.dialect.quoteIdent({ name: "vibe_request" });
5939
6603
  return { text: `SELECT j.key AS ${request}, x.name AS name, x.hidden AS hidden, x.pk AS pk, x.dflt_value AS definition FROM json_each(${collector.add(JSON.stringify(params.tables))}) j LEFT JOIN pragma_table_xinfo(j.value, ${collector.add(params.namespace)}) x ORDER BY j.key, x.cid`, values: collector.values, returning: "none" };
5940
6604
  }
5941
6605
  function renderSqliteForeignKeyInventoryBatch(params) {
5942
6606
  if (params.dialect.name !== "sqlite")
5943
- throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
6607
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
5944
6608
  const collector = new ParamCollector(params.dialect);
5945
6609
  const request = params.dialect.quoteIdent({ name: "vibe_request" });
5946
6610
  return { text: `SELECT j.key AS ${request}, f.id AS id, f.seq AS seq, f."from" AS "from", f."to" AS "to", f."table" AS "table", f.on_update AS on_update, f.on_delete AS on_delete, f.match AS match FROM json_each(${collector.add(JSON.stringify(params.tables))}) j LEFT JOIN pragma_foreign_key_list(j.value, ${collector.add(params.namespace)}) f ORDER BY j.key, f.id, f.seq`, values: collector.values, returning: "none" };
@@ -5953,15 +6617,15 @@ var SQLITE_INTROSPECTION_PRAGMA_ORDER = Object.freeze({
5953
6617
  });
5954
6618
  function renderSqliteIntrospectionPragmaBatch(params) {
5955
6619
  if (params.dialect.name !== "sqlite")
5956
- throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteIntrospectionPragmaBatch" });
6620
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteIntrospectionPragmaBatch" });
5957
6621
  const order = SQLITE_INTROSPECTION_PRAGMA_ORDER[params.pragma];
5958
6622
  if (order === undefined)
5959
- throw unsupportedCapability33({ dialect: "sqlite", feature: `introspectionPragma:${String(params.pragma)}` });
6623
+ throw unsupportedCapability34({ dialect: "sqlite", feature: `introspectionPragma:${String(params.pragma)}` });
5960
6624
  return { text: `SELECT j.key AS ${params.dialect.quoteIdent({ name: "vibe_request" })}, p.* FROM json_each(${params.dialect.quoteStringLiteral({ value: JSON.stringify(params.names) })}) j LEFT JOIN pragma_${params.pragma}(j.value) p ORDER BY j.key, ${order}`, values: [], returning: "none" };
5961
6625
  }
5962
6626
  function renderSqliteRebuildObjects(params) {
5963
6627
  if (params.dialect.name !== "sqlite")
5964
- throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
6628
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
5965
6629
  return { text: "SELECT name, tbl_name, type, sql FROM main.sqlite_schema WHERE type IN ('index', 'trigger') AND sql IS NOT NULL ORDER BY type, name", values: [], returning: "none" };
5966
6630
  }
5967
6631
  function renderMigrationInventory(params) {
@@ -6003,13 +6667,13 @@ ORDER BY kind, parent, name${MYSQL_NO_ROW_LIMIT}`, values: collector.values, ret
6003
6667
  // packages/sql/src/dialects/migration-visibility.ts
6004
6668
  function renderMigrationVisibility(params) {
6005
6669
  if (params.dialect.name !== "mysql")
6006
- throw unsupportedCapability34({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
6670
+ throw unsupportedCapability35({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
6007
6671
  const collector = new ParamCollector(params.dialect);
6008
6672
  const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
6009
6673
  return { text: `SELECT PRIVILEGE_TYPE AS privilege FROM information_schema.USER_PRIVILEGES WHERE GRANTEE = ${grantee} UNION SELECT PRIVILEGE_TYPE AS privilege FROM information_schema.SCHEMA_PRIVILEGES WHERE GRANTEE = ${grantee} AND ${collector.add(params.namespace)} LIKE TABLE_SCHEMA${MYSQL_NO_ROW_LIMIT}`, values: collector.values, returning: "none" };
6010
6674
  }
6011
6675
  // packages/sql/src/dialects/migration-objects.ts
6012
- import { internalError as internalError5, unsupportedCapability as unsupportedCapability35 } from "@vibeorm/schema";
6676
+ import { internalError as internalError6, unsupportedCapability as unsupportedCapability36 } from "@vibeorm/schema";
6013
6677
  function renderMigrationObjectQuery(params) {
6014
6678
  return objectQuery({ ...params, collector: new ParamCollector(params.dialect) });
6015
6679
  }
@@ -6017,7 +6681,7 @@ function objectQuery(params) {
6017
6681
  const { dialect, identity: object, collector } = params;
6018
6682
  if (object.kind === "namespace") {
6019
6683
  if (dialect.name !== "postgres")
6020
- throw unsupportedCapability35({ dialect: dialect.name, feature: "fixedNamespaces" });
6684
+ throw unsupportedCapability36({ dialect: dialect.name, feature: "fixedNamespaces" });
6021
6685
  return { text: `SELECT n.nspname::text AS "name" FROM pg_catalog.pg_namespace n WHERE n.nspname = ${collector.add(object.name)}`, values: collector.values, returning: "none" };
6022
6686
  }
6023
6687
  const physical = "table" in object ? object.table : object.name;
@@ -6065,18 +6729,18 @@ function objectQuery(params) {
6065
6729
  }
6066
6730
  } else if (dialect.name === "mysql") {
6067
6731
  if (object.kind === "constraint" && object.constraintType === "exclusion")
6068
- throw unsupportedCapability35({ dialect: "mysql", feature: "exclusionConstraints" });
6732
+ throw unsupportedCapability36({ dialect: "mysql", feature: "exclusionConstraints" });
6069
6733
  const mysql = mysqlFactsText({ dialect, identity: object, namespace, name, collector });
6070
6734
  if (mysql === undefined)
6071
- throw unsupportedCapability35({ dialect: "mysql", feature: `inspect:${object.kind}` });
6735
+ throw unsupportedCapability36({ dialect: "mysql", feature: `inspect:${object.kind}` });
6072
6736
  text = mysql;
6073
6737
  } else {
6074
6738
  if (physical.namespace !== "main" && physical.namespace !== "temp")
6075
- throw unsupportedCapability35({ dialect: "sqlite", feature: "attachedSchemaInspection" });
6739
+ throw unsupportedCapability36({ dialect: "sqlite", feature: "attachedSchemaInspection" });
6076
6740
  const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
6077
6741
  const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
6078
6742
  if (["function", "sequence", "postgresExtension", "enum"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
6079
- throw unsupportedCapability35({ dialect: "sqlite", feature: `inspect:${object.kind}` });
6743
+ throw unsupportedCapability36({ dialect: "sqlite", feature: `inspect:${object.kind}` });
6080
6744
  const sqliteParams = new ParamCollector(dialect);
6081
6745
  if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
6082
6746
  text = `SELECT x.type AS type, x."notnull" AS "notNull", x.pk AS primaryKeyPosition, x.hidden AS generated, x.dflt_value AS definition, (SELECT sql FROM ${catalog} WHERE name = ${sqliteParams.add(physical.name)} AND type = 'table') AS tableDefinition FROM pragma_table_xinfo(${sqliteParams.add(physical.name)}, ${sqliteParams.add(physical.namespace)}) x WHERE x.name = ${sqliteParams.add(object.column)}`;
@@ -6151,7 +6815,7 @@ function mysqlBatches(params) {
6151
6815
  function renderMysqlObjectBatch(params) {
6152
6816
  const { dialect } = params;
6153
6817
  if (dialect.name !== "mysql")
6154
- throw unsupportedCapability35({ dialect: dialect.name, feature: "batchedMigrationInspection" });
6818
+ throw unsupportedCapability36({ dialect: dialect.name, feature: "batchedMigrationInspection" });
6155
6819
  const position = dialect.quoteIdent({ name: "vibe_position" });
6156
6820
  const groups = new Map;
6157
6821
  for (const [index, identity] of params.identities.entries()) {
@@ -6171,7 +6835,7 @@ function renderMysqlObjectBatch(params) {
6171
6835
  function renderMysqlObjectMembersBatch(params) {
6172
6836
  const { dialect } = params;
6173
6837
  if (dialect.name !== "mysql")
6174
- throw unsupportedCapability35({ dialect: dialect.name, feature: "batchedMigrationInspection" });
6838
+ throw unsupportedCapability36({ dialect: dialect.name, feature: "batchedMigrationInspection" });
6175
6839
  const position = dialect.quoteIdent({ name: "vibe_position" });
6176
6840
  const ordinal = dialect.quoteIdent({ name: "vibe_ordinal" });
6177
6841
  const groups = new Map;
@@ -6191,7 +6855,7 @@ function renderMigrationDependencies(params) {
6191
6855
  }
6192
6856
  function dependencyQuery(params) {
6193
6857
  if (params.dialect.name !== "postgres")
6194
- throw unsupportedCapability35({ dialect: params.dialect.name, feature: "catalogDependencies" });
6858
+ throw unsupportedCapability36({ dialect: params.dialect.name, feature: "catalogDependencies" });
6195
6859
  const object = params.identity;
6196
6860
  const collector = params.collector;
6197
6861
  if (object.kind === "namespace") {
@@ -6248,7 +6912,7 @@ function inspectionReferences(params) {
6248
6912
  function renderMigrationObjectBatch(params) {
6249
6913
  const { dialect } = params;
6250
6914
  if (dialect.name !== "postgres")
6251
- throw unsupportedCapability35({ dialect: dialect.name, feature: "batchedMigrationInspection" });
6915
+ throw unsupportedCapability36({ dialect: dialect.name, feature: "batchedMigrationInspection" });
6252
6916
  const groups = new Map;
6253
6917
  for (const [position, identity] of params.identities.entries()) {
6254
6918
  const kind = identity.kind === "constraint" ? `${identity.kind}:${identity.constraintType}` : identity.kind === "generatedColumn" ? "columnDefault" : identity.kind;
@@ -6261,7 +6925,7 @@ function renderMigrationObjectBatch(params) {
6261
6925
  const facts = objectQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
6262
6926
  const dependencies = dependencyQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
6263
6927
  if (JSON.stringify(dependencies.values) !== JSON.stringify(facts.values))
6264
- throw internalError5({ message: "Batched catalog queries bind their identity in different orders", meta: { kind: identity.kind } });
6928
+ throw internalError6({ message: "Batched catalog queries bind their identity in different orders", meta: { kind: identity.kind } });
6265
6929
  const columns = facts.values.map((value, index) => `${dialect.quoteIdent({ name: `p${index + 1}` })} ${Array.isArray(value) ? "text[]" : "text"}`);
6266
6930
  const requests = group.map(({ identity, position }) => ({ position, ...Object.fromEntries(renderMigrationObjectQuery({ dialect, identity }).values.map((value, index) => [`p${index + 1}`, value])) }));
6267
6931
  const collector = new ParamCollector(dialect);
@@ -6281,16 +6945,16 @@ function renderMigrationNamespace(params) {
6281
6945
  }
6282
6946
  function renderCheckProbe(params) {
6283
6947
  if (params.dialect.name !== "postgres")
6284
- throw unsupportedCapability35({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
6948
+ throw unsupportedCapability36({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
6285
6949
  const q = (name) => params.dialect.quoteIdent({ name });
6286
6950
  return { text: `ALTER TABLE ${q(params.table.namespace)}.${q(params.table.name)} ADD CONSTRAINT ${q(params.name)} CHECK (${params.expression}) NOT VALID`, values: [], returning: "none" };
6287
6951
  }
6288
6952
  // packages/sql/src/dialects/postgres-migration.ts
6289
- import { VibeError as VibeError24, unsupportedCapability as unsupportedCapability36 } from "@vibeorm/schema";
6953
+ import { VibeError as VibeError25, unsupportedCapability as unsupportedCapability37 } from "@vibeorm/schema";
6290
6954
  var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
6291
6955
  function requirePostgres2(params) {
6292
6956
  if (params.dialect.name !== "postgres") {
6293
- throw unsupportedCapability36({ dialect: params.dialect.name, feature: params.feature });
6957
+ throw unsupportedCapability37({ dialect: params.dialect.name, feature: params.feature });
6294
6958
  }
6295
6959
  }
6296
6960
  function renderMigrationSessionRead(params) {
@@ -6305,7 +6969,7 @@ function renderMigrationSessionSet(params) {
6305
6969
  requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
6306
6970
  const { milliseconds, setting } = params;
6307
6971
  if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
6308
- throw new VibeError24({
6972
+ throw new VibeError25({
6309
6973
  code: "VIBE_MIGRATION",
6310
6974
  message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
6311
6975
  meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
@@ -6400,34 +7064,45 @@ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${table} AND a.att
6400
7064
  export {
6401
7065
  ADVISORY_KEY_BOUNDS,
6402
7066
  ADVISORY_LOCK_RESULT_COLUMN,
7067
+ AUTOMATIC_SHADOW_NAME_PATTERN,
6403
7068
  CHAIN_VETO_PREFIX,
6404
7069
  CHAIN_VETO_TOKEN_PATTERN,
7070
+ COALESCE_REF_NAME,
6405
7071
  DIALECT_CAPABILITIES,
6406
7072
  DIALECT_CODECS,
6407
7073
  JSON_TRANSPORT,
6408
7074
  MODULE_JOURNAL_COLUMN,
6409
7075
  MYSQL_CAPABILITIES,
6410
7076
  MYSQL_CODECS,
7077
+ MYSQL_SCALAR_COLUMN_TYPES,
6411
7078
  NATIVE_RLS,
6412
7079
  NATIVE_RLS_PROBE_POLICY,
6413
7080
  POSTGRES_CAPABILITIES,
6414
7081
  POSTGRES_CODECS,
6415
7082
  POSTGRES_DIAGNOSTIC_SQL,
7083
+ POSTGRES_EXACT_EQUIVALENTS,
7084
+ POSTGRES_NATIVE_ALIASES,
7085
+ POSTGRES_SCALAR_COLUMN_TYPES,
6416
7086
  POSTGRES_WORKLOAD_LIMITS,
6417
7087
  ParamCollector,
6418
7088
  RLS_READINESS_SQL,
6419
7089
  SHADOW_CAPABILITIES,
6420
7090
  SHADOW_DATABASE,
7091
+ SHADOW_MARKER_CAPABILITIES,
6421
7092
  SQLITE_CAPABILITIES,
6422
7093
  SQLITE_CODECS,
7094
+ SQLITE_SCALAR_STORAGE_CLASSES,
6423
7095
  SQLITE_SESSION_SQL,
6424
7096
  SQL_DEFAULT_CELL,
6425
7097
  SQL_DIALECTS,
6426
7098
  SQL_KEEP_CELL,
6427
7099
  assertIndexExpressionVersion,
6428
7100
  assertSqlParamBuilding,
7101
+ automaticShadowMarkerText,
7102
+ canonicalColumnType,
6429
7103
  chainVetoToken,
6430
7104
  cloneTimestampDate,
7105
+ coalesceRef,
6431
7106
  compareTimestampDates,
6432
7107
  compileExclusion,
6433
7108
  compileReusableSqlCommand,
@@ -6464,10 +7139,13 @@ export {
6464
7139
  isFixedDiagnostic,
6465
7140
  isJsonListParameter,
6466
7141
  isJsonParamCell,
7142
+ isJsonPatchOperation,
6467
7143
  isListSetOperation,
6468
7144
  isNativeEnumArrayParameter,
7145
+ isOtherRowReturningColumn,
6469
7146
  isReusableSqlCommand,
6470
7147
  isReusableSqlQuery,
7148
+ isRollupChain,
6471
7149
  isSetOperation,
6472
7150
  isSourceColumnRef,
6473
7151
  isSqlBindingCollector,
@@ -6481,17 +7159,22 @@ export {
6481
7159
  isTimeZoneName,
6482
7160
  jsonListParameter,
6483
7161
  jsonParamCell,
7162
+ jsonPatchVetoOf,
6484
7163
  keyListBindsUntypedRows,
6485
7164
  localDateCodec,
6486
7165
  mapConflictColumns,
6487
7166
  mysqlDialect,
6488
7167
  nativeEnumArrayParameter,
7168
+ normalizeMysqlNativeType,
7169
+ normalizePostgresNativeType,
7170
+ parseAutomaticShadowMarker,
6489
7171
  parseDateTimeText,
6490
7172
  parseExtendedDateTimeText,
6491
7173
  parseJsonText,
6492
7174
  parsePgArrayLiteral,
6493
7175
  parsePgArrayLiteralNested,
6494
7176
  parsePostgresTypeName,
7177
+ planJsonPatch,
6495
7178
  postgresArrayElementFromText,
6496
7179
  postgresBulkInsertArrayCastOf,
6497
7180
  postgresDialect,
@@ -6502,6 +7185,7 @@ export {
6502
7185
  registerSqlHandleCheck,
6503
7186
  renderAdvisoryLock,
6504
7187
  renderAdvisoryLocks,
7188
+ renderAutomaticShadowOperation,
6505
7189
  renderCaptureReadiness,
6506
7190
  renderCheckProbe,
6507
7191
  renderColumnStateQuery,
@@ -6609,4 +7293,4 @@ export {
6609
7293
  toPgArrayLiteral
6610
7294
  };
6611
7295
 
6612
- //# debugId=D4C3C7B84AC363A964756E2164756E21
7296
+ //# debugId=B21A2ED0EE62B5B964756E2164756E21