@vibeorm/sql 5.7.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) {
@@ -672,6 +1026,27 @@ function scalar(type, valid) {
672
1026
  }
673
1027
  var stringDecoder = scalar("String", (value) => typeof value === "string");
674
1028
  var dateDecoder = scalar("DateTime", (value) => value instanceof Date && Number.isFinite(value.getTime()));
1029
+ var bigIntDecoder = scalar("BigInt", (value) => typeof value === "bigint");
1030
+ var SAFE_INTEGER_LIMIT = BigInt(Number.MAX_SAFE_INTEGER);
1031
+ var bigIntNumberDecoder = registerScalar({ identity: JSON.stringify(["scalar", "BigInt", "number"]), contract: { scalar: "BigInt", nullable: false, list: false }, decoder: Object.freeze({
1032
+ decode: (params) => {
1033
+ const exact = bigIntDecoder.decode(params);
1034
+ if (exact > SAFE_INTEGER_LIMIT || exact < -SAFE_INTEGER_LIMIT)
1035
+ refuse(false, `BigInt ${exact.toString()} is beyond Number.MAX_SAFE_INTEGER, so it cannot read as a safe number`);
1036
+ return Number(exact);
1037
+ },
1038
+ encode: (params) => {
1039
+ const { value } = params;
1040
+ const unsafe = `${String(value)} is not a safe integer, so it is refused instead of rounded`;
1041
+ if (typeof value === "number" && !Number.isSafeInteger(value))
1042
+ refuse(true, unsafe);
1043
+ const encoded = bigIntDecoder.encode(params);
1044
+ const exact = BigInt(String(encoded));
1045
+ if (exact > SAFE_INTEGER_LIMIT || exact < -SAFE_INTEGER_LIMIT)
1046
+ refuse(true, unsafe);
1047
+ return encoded;
1048
+ }
1049
+ }) });
675
1050
  var extendedDecoder = registerScalar({ identity: "extended-date-time", contract: { scalar: "DateTime", nullable: false, list: false }, decoder: Object.freeze({
676
1051
  decode: (params) => {
677
1052
  if (params.value === "infinity" || params.value === Infinity)
@@ -752,7 +1127,8 @@ var decode = Object.freeze({
752
1127
  boolean: scalar("Boolean", (value) => typeof value === "boolean"),
753
1128
  int: scalar("Int", (value) => typeof value === "number" && Number.isSafeInteger(value)),
754
1129
  float: scalar("Float", (value) => typeof value === "number" && Number.isFinite(value)),
755
- bigInt: scalar("BigInt", (value) => typeof value === "bigint"),
1130
+ bigInt: bigIntDecoder,
1131
+ bigIntNumber: bigIntNumberDecoder,
756
1132
  decimal: scalar("Decimal", (value) => typeof value === "string" && /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)),
757
1133
  dateTime: dateDecoder,
758
1134
  extendedDateTime: extendedDecoder,
@@ -1171,7 +1547,7 @@ function renderPostgresPhysicalCatalog(params) {
1171
1547
  let text;
1172
1548
  switch (params.query) {
1173
1549
  case "tables":
1174
- 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`;
1175
1551
  break;
1176
1552
  case "views":
1177
1553
  text = `SELECT table_name FROM information_schema.views WHERE table_schema = ${namespace} AND ${extensionOwnedFilter} ORDER BY table_name`;
@@ -1192,7 +1568,7 @@ function renderPostgresPhysicalCatalog(params) {
1192
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`;
1193
1569
  break;
1194
1570
  case "indexes":
1195
- 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`;
1196
1572
  break;
1197
1573
  }
1198
1574
  return { text, values: collector.values, returning: "none" };
@@ -1788,7 +2164,7 @@ function extractIndexKeyList({ sql, open, close, tokens, wrapped = false, remove
1788
2164
  }
1789
2165
  return keys.length === 0 ? undefined : keys;
1790
2166
  }
1791
- function extractStandaloneIndexKeys({ definition, index, postgres = false }) {
2167
+ function extractStandaloneIndexKeys({ definition, index, postgres = false, included }) {
1792
2168
  const tokens = tableDefinitionTokens({ sql: definition });
1793
2169
  const top = tokens.filter((token) => token.depth === 0);
1794
2170
  let position = 0;
@@ -1829,6 +2205,16 @@ function extractStandaloneIndexKeys({ definition, index, postgres = false }) {
1829
2205
  const close = top[position++];
1830
2206
  if (open?.kind !== "symbol" || open.value !== "(" || close?.kind !== "symbol" || close.value !== ")")
1831
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;
1832
2218
  if (postgres && word({ value: "NULLS" }) && (!word({ value: "NOT" }) || !word({ value: "DISTINCT" })))
1833
2219
  return;
1834
2220
  const tail = top[position];
@@ -1855,8 +2241,8 @@ function extractSqliteIndexKeys({ definition, index }) {
1855
2241
  }
1856
2242
 
1857
2243
  // packages/sql/src/dialects/postgres-index-expressions.ts
1858
- function extractPostgresIndexKeys({ definition, index }) {
1859
- return extractStandaloneIndexKeys({ definition, index, postgres: true });
2244
+ function extractPostgresIndexKeys({ definition, index, included }) {
2245
+ return extractStandaloneIndexKeys({ definition, index, postgres: true, ...included === undefined ? {} : { included } });
1860
2246
  }
1861
2247
  function renderPostgresIndexExpressionProbe({ ...params }) {
1862
2248
  const { dialect, operation, probe } = params;
@@ -2125,10 +2511,10 @@ function renderIndexKeys({ dialect, keys, unique }) {
2125
2511
  return `${expression}${key.desc ? " DESC" : ""}`;
2126
2512
  });
2127
2513
  }
2128
- function extractIndexKeys({ dialect, definition, index, backslashEscapes = true }) {
2514
+ function extractIndexKeys({ dialect, definition, index, backslashEscapes = true, included }) {
2129
2515
  switch (dialect) {
2130
2516
  case "postgres":
2131
- return extractPostgresIndexKeys({ definition, index });
2517
+ return extractPostgresIndexKeys({ definition, index, ...included === undefined ? {} : { included } });
2132
2518
  case "sqlite":
2133
2519
  return extractSqliteIndexKeys({ definition, index });
2134
2520
  case "mysql":
@@ -2296,6 +2682,57 @@ UNION ALL SELECT 'EXTERNAL FOREIGN KEY', CONCAT(TABLE_NAME, '.', CONSTRAINT_NAME
2296
2682
  }
2297
2683
  }
2298
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
+ }
2299
2736
  // packages/sql/src/module-journal.ts
2300
2737
  var MODULE_JOURNAL_COLUMN = "module_origin";
2301
2738
  function renderModuleJournal(params) {
@@ -2333,10 +2770,10 @@ import {
2333
2770
  } from "@vibeorm/schema";
2334
2771
 
2335
2772
  // packages/sql/src/identifiers.ts
2336
- import { VibeError as VibeError12 } from "@vibeorm/schema";
2773
+ import { VibeError as VibeError13 } from "@vibeorm/schema";
2337
2774
  function assertNoNul(params) {
2338
2775
  if (params.name.includes("\x00")) {
2339
- throw new VibeError12({
2776
+ throw new VibeError13({
2340
2777
  code: "VIBE_VALIDATION",
2341
2778
  message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
2342
2779
  meta: { name: params.name.replaceAll("\x00", "\\0") }
@@ -2378,6 +2815,7 @@ var sqliteDialect = {
2378
2815
  jsonPathMode: "sqliteExtract",
2379
2816
  supportedJsonOps: [
2380
2817
  "equals",
2818
+ "exists",
2381
2819
  "string_contains",
2382
2820
  "string_starts_with",
2383
2821
  "string_ends_with",
@@ -2482,11 +2920,11 @@ var SQLITE_SESSION_SQL = Object.freeze({
2482
2920
  foreignKeysOn: "PRAGMA foreign_keys = ON"
2483
2921
  });
2484
2922
  // packages/sql/src/composable.ts
2485
- import { VibeError as VibeError13 } from "@vibeorm/schema";
2923
+ import { VibeError as VibeError14 } from "@vibeorm/schema";
2486
2924
  var fragments = new WeakMap;
2487
2925
  registerSqlHandleCheck({ check: (value) => fragments.has(value) });
2488
2926
  function invalid4(message) {
2489
- throw new VibeError13({ code: "VIBE_VALIDATION", message });
2927
+ throw new VibeError14({ code: "VIBE_VALIDATION", message });
2490
2928
  }
2491
2929
  function construct(parts) {
2492
2930
  const snapshot = Object.freeze([...parts]);
@@ -2584,12 +3022,12 @@ function renderSqlFragment(params) {
2584
3022
  const only = parts.length === 1 ? parts[0] : undefined;
2585
3023
  if (recording && only !== undefined && typeof only !== "string" && "value" in only) {
2586
3024
  if (only.decoder !== undefined && resultScalarContract({ decoder: only.decoder }) === undefined)
2587
- 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" });
2588
3026
  if (only.value === null) {
2589
3027
  if (only.decoder !== undefined)
2590
3028
  recordSqlBindingCheck({ collector, check: () => {
2591
3029
  if (encode({ ...only, value: snapshotSqlValue(only.value) }) !== null)
2592
- 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" });
2593
3031
  } });
2594
3032
  return "NULL";
2595
3033
  }
@@ -2653,14 +3091,14 @@ function sqlUsesIncoming(params) {
2653
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 }))));
2654
3092
  }
2655
3093
  // packages/sql/src/query-plan.ts
2656
- import { VibeError as VibeError14 } from "@vibeorm/schema";
3094
+ import { VibeError as VibeError15 } from "@vibeorm/schema";
2657
3095
  var queries = new WeakMap;
2658
3096
  var commands = new WeakMap;
2659
3097
  var reusableQueries = new WeakMap;
2660
3098
  var reusableCommands = new WeakMap;
2661
3099
  registerSqlHandleCheck({ check: (value) => queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) });
2662
3100
  function invalid5(message) {
2663
- throw new VibeError14({ code: "VIBE_VALIDATION", message });
3101
+ throw new VibeError15({ code: "VIBE_VALIDATION", message });
2664
3102
  }
2665
3103
  function createSqlQuery(params) {
2666
3104
  if (typeof params.render !== "function" || typeof params.decoder?.decode !== "function")
@@ -2751,14 +3189,14 @@ function compileSqlCommand(params) {
2751
3189
  return { text, values: collector.values };
2752
3190
  }
2753
3191
  // packages/sql/src/exclusion.ts
2754
- 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";
2755
3193
  function invalid6(rule) {
2756
- 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}` } });
2757
3195
  }
2758
3196
  function compileExclusion(params) {
2759
3197
  const { exclusion: spec, model, dialect } = params;
2760
3198
  if (!dialect.capabilities.exclusionConstraints)
2761
- throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
3199
+ throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
2762
3200
  if (!spec.name || spec.name.includes("\x00"))
2763
3201
  invalid6("name");
2764
3202
  if (!["int8range", "tsrange", "tstzrange"].includes(spec.range.type))
@@ -2811,13 +3249,13 @@ function compileExclusion(params) {
2811
3249
  }
2812
3250
  function renderExclusionSetup(params) {
2813
3251
  if (!params.dialect.capabilities.exclusionConstraints)
2814
- throw unsupportedCapability14({ dialect: params.dialect.name, feature: "exclusionConstraints" });
3252
+ throw unsupportedCapability15({ dialect: params.dialect.name, feature: "exclusionConstraints" });
2815
3253
  return params.exclusion.requiredExtensions.map((name) => `CREATE EXTENSION IF NOT EXISTS ${params.dialect.quoteIdent({ name })};`);
2816
3254
  }
2817
3255
  function renderExclusionDdl(params) {
2818
3256
  const { dialect, table, exclusion, operation } = params;
2819
3257
  if (!dialect.capabilities.exclusionConstraints)
2820
- throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
3258
+ throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
2821
3259
  const target = `ALTER TABLE ${dialect.quoteIdent({ name: table })}`;
2822
3260
  if (operation === "drop")
2823
3261
  return [`${target} DROP CONSTRAINT ${dialect.quoteIdent({ name: exclusion.name })};`];
@@ -2826,111 +3264,18 @@ function renderExclusionDdl(params) {
2826
3264
  function renderExclusionCatalog(params) {
2827
3265
  const { dialect, probe } = params;
2828
3266
  if (!dialect.capabilities.exclusionConstraints)
2829
- throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
3267
+ throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
2830
3268
  const scope = probe === undefined ? `n.nspname = ${dialect.quoteStringLiteral({ value: params.namespace ?? "public" })}` : `t.oid = ${dialect.quoteStringLiteral({ value: probe })}::regclass`;
2831
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`;
2832
3270
  }
2833
3271
  function renderExclusionProbe(params) {
2834
3272
  const { dialect, table, probe, definition } = params;
2835
3273
  if (!dialect.capabilities.exclusionConstraints)
2836
- throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
3274
+ throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
2837
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}`];
2838
3276
  }
2839
- // packages/sql/src/ast.ts
2840
- var SET_OPERATORS = ["increment", "decrement", "multiply", "divide"];
2841
- function isSetOperation(value) {
2842
- if (typeof value !== "object" || value === null || Array.isArray(value))
2843
- return false;
2844
- const keys = Object.keys(value);
2845
- if (!keys.includes("__op") || !keys.includes("value"))
2846
- return false;
2847
- const candidate = value;
2848
- if (keys.length !== 2 && !(keys.length === 3 && keys.includes("numericType") && candidate.numericType === "Decimal"))
2849
- return false;
2850
- 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");
2851
- }
2852
- var LIST_SET_OPERATORS = ["append", "concat"];
2853
- function isListSetOperation(value) {
2854
- if (typeof value !== "object" || value === null || Array.isArray(value))
2855
- return false;
2856
- const keys = Object.keys(value);
2857
- if (keys.length !== 2 || !keys.includes("__listOp") || !keys.includes("value"))
2858
- return false;
2859
- const candidate = value;
2860
- return typeof candidate.__listOp === "string" && LIST_SET_OPERATORS.includes(candidate.__listOp);
2861
- }
2862
- function dbNowMarker(params) {
2863
- return { __dbNow: true, offsetSeconds: params.offsetSeconds };
2864
- }
2865
- function isDbNowMarker(value) {
2866
- if (typeof value !== "object" || value === null || Array.isArray(value))
2867
- return false;
2868
- if (!Object.hasOwn(value, "__dbNow"))
2869
- return false;
2870
- const keys = Object.keys(value);
2871
- if (keys.length !== 2 || !keys.includes("__dbNow") || !keys.includes("offsetSeconds"))
2872
- return false;
2873
- const candidate = value;
2874
- return candidate.__dbNow === true && typeof candidate.offsetSeconds === "number" && Number.isFinite(candidate.offsetSeconds);
2875
- }
2876
- var SQL_DEFAULT_CELL = Object.freeze({ __sqlDefault: true });
2877
- var SQL_KEEP_CELL = Object.freeze({ __sqlKeep: true });
2878
- function isSqlKeepCell(value) {
2879
- return value === SQL_KEEP_CELL;
2880
- }
2881
- function isSqlDefaultCell(value) {
2882
- return value === SQL_DEFAULT_CELL;
2883
- }
2884
- function jsonParamCell(params) {
2885
- return params.storage === "json" ? { __jsonParam: true, text: params.text, storage: "json" } : { __jsonParam: true, text: params.text };
2886
- }
2887
- function isJsonParamCell(value) {
2888
- return typeof value === "object" && value !== null && value.__jsonParam === true;
2889
- }
2890
- function excludedRef(params) {
2891
- return { __excluded: true, column: params.column };
2892
- }
2893
- function isExcludedRef(value) {
2894
- return typeof value === "object" && value !== null && value.__excluded === true;
2895
- }
2896
- function returningColumnName(column) {
2897
- return typeof column === "string" ? column : ("capture" in column) ? column.alias : column.name;
2898
- }
2899
- function isCaptureReturningColumn(column) {
2900
- return typeof column !== "string" && "capture" in column;
2901
- }
2902
- var ADVISORY_LOCK_RESULT_COLUMN = "acquired";
2903
- function stepColumnRef(params) {
2904
- return { __stepColumn: true, step: params.step, column: params.column };
2905
- }
2906
- function sourceColumnRef(params) {
2907
- return Object.freeze({ __sourceColumn: true, column: params.column });
2908
- }
2909
- function isSourceColumnRef(value) {
2910
- if (typeof value !== "object" || value === null)
2911
- return false;
2912
- const candidate = value;
2913
- return candidate.__sourceColumn === true && typeof candidate.column === "string" && Object.keys(value).length === 2;
2914
- }
2915
- function isStepColumnRef(value) {
2916
- return typeof value === "object" && value !== null && value.__stepColumn === true;
2917
- }
2918
- var CHAIN_VETO_PREFIX = "__vibe_veto:";
2919
- var CHAIN_VETO_TOKEN_PATTERN = /^[a-z][a-z0-9_]*$/;
2920
- function chainVetoToken(params) {
2921
- const { message, nonce } = params;
2922
- if (nonce === "")
2923
- return;
2924
- for (let start = message.indexOf(CHAIN_VETO_PREFIX);start >= 0; start = message.indexOf(CHAIN_VETO_PREFIX, start + 1)) {
2925
- const rest = message.slice(start + CHAIN_VETO_PREFIX.length);
2926
- const match = /^([a-z][a-z0-9_]*):/.exec(rest);
2927
- if (match !== null && rest.startsWith(`${nonce}:`, match[0].length))
2928
- return match[1];
2929
- }
2930
- return;
2931
- }
2932
3277
  // packages/sql/src/locks.ts
2933
- import { internalError, unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
3278
+ import { internalError as internalError2, unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
2934
3279
  var ROW_LOCK_PROSE = Object.freeze({
2935
3280
  strengths: Object.freeze({
2936
3281
  update: "FOR UPDATE",
@@ -2950,7 +3295,7 @@ function renderRowLockClause(params) {
2950
3295
  if (syntax === null) {
2951
3296
  if (lock.strength === "update" && lock.wait === "wait")
2952
3297
  return null;
2953
- throw unsupportedCapability15({
3298
+ throw unsupportedCapability16({
2954
3299
  dialect: dialect.name,
2955
3300
  feature: `a ${describeRowLock({ lock })} locking read`,
2956
3301
  hint: `${dialect.name} has no locking-read clause at all`
@@ -2958,7 +3303,7 @@ function renderRowLockClause(params) {
2958
3303
  }
2959
3304
  const strengthText = syntax.strengths[lock.strength];
2960
3305
  if (strengthText === undefined) {
2961
- throw unsupportedCapability15({
3306
+ throw unsupportedCapability16({
2962
3307
  dialect: dialect.name,
2963
3308
  feature: `the ${describeRowLock({ lock: { ...lock, wait: "wait" } })} row-lock strength`,
2964
3309
  hint: `${dialect.name} can express: ${Object.keys(syntax.strengths).join(", ")}`
@@ -2966,7 +3311,7 @@ function renderRowLockClause(params) {
2966
3311
  }
2967
3312
  const waitText = syntax.wait[lock.wait];
2968
3313
  if (waitText === undefined) {
2969
- throw unsupportedCapability15({
3314
+ throw unsupportedCapability16({
2970
3315
  dialect: dialect.name,
2971
3316
  feature: `the "${lock.wait}" lock wait policy`,
2972
3317
  hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
@@ -2974,7 +3319,7 @@ function renderRowLockClause(params) {
2974
3319
  }
2975
3320
  if (lock.of !== undefined) {
2976
3321
  if (lock.of.length === 0)
2977
- 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" });
2978
3323
  const tables = lock.of.map((name) => dialect.quoteIdent({ name })).join(", ");
2979
3324
  return `${strengthText} OF ${tables}${waitText}`;
2980
3325
  }
@@ -2991,7 +3336,7 @@ function renderAdvisoryLock(params) {
2991
3336
  const { dialect, statement, collector } = params;
2992
3337
  const functions = dialect.advisoryLockFunctions;
2993
3338
  if (functions === null) {
2994
- throw unsupportedCapability15({
3339
+ throw unsupportedCapability16({
2995
3340
  dialect: dialect.name,
2996
3341
  feature: "advisory locks",
2997
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`
@@ -3012,11 +3357,11 @@ function renderAdvisoryLocks(params) {
3012
3357
  const { dialect, keys, mode } = params;
3013
3358
  const functions = dialect.advisoryLockFunctions;
3014
3359
  if (functions === null)
3015
- throw unsupportedCapability15({ dialect: dialect.name, feature: "advisory locks" });
3360
+ throw unsupportedCapability16({ dialect: dialect.name, feature: "advisory locks" });
3016
3361
  if (mode !== "exclusive" && mode !== "shared")
3017
- throw internalError({ message: "advisory lock mode must be exclusive or shared" });
3362
+ throw internalError2({ message: "advisory lock mode must be exclusive or shared" });
3018
3363
  if (keys.some((key) => typeof key !== "bigint" || key < ADVISORY_KEY_BOUNDS.single.min || key > ADVISORY_KEY_BOUNDS.single.max)) {
3019
- 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" });
3020
3365
  }
3021
3366
  const collector = new ParamCollector(dialect);
3022
3367
  const binding = collector.add(JSON.stringify(keys.map((key) => key.toString())));
@@ -3033,7 +3378,7 @@ function renderAdvisoryLocks(params) {
3033
3378
  function assertInt32(params) {
3034
3379
  const { value, part } = params;
3035
3380
  if (!Number.isInteger(value) || value < ADVISORY_KEY_BOUNDS.pair.min || value > ADVISORY_KEY_BOUNDS.pair.max) {
3036
- throw internalError({
3381
+ throw internalError2({
3037
3382
  message: `advisory lock ${part} must be a 32-bit integer, got ${String(value)}`,
3038
3383
  meta: { part, value }
3039
3384
  });
@@ -3079,6 +3424,7 @@ var postgresDialect = {
3079
3424
  jsonPathMode: "operators",
3080
3425
  supportedJsonOps: [
3081
3426
  "equals",
3427
+ "exists",
3082
3428
  "string_contains",
3083
3429
  "string_starts_with",
3084
3430
  "string_ends_with",
@@ -3173,6 +3519,7 @@ var mysqlDialect = {
3173
3519
  jsonPathMode: "mysqlExtract",
3174
3520
  supportedJsonOps: [
3175
3521
  "equals",
3522
+ "exists",
3176
3523
  "string_contains",
3177
3524
  "string_starts_with",
3178
3525
  "string_ends_with",
@@ -3203,11 +3550,12 @@ var mysqlDialect = {
3203
3550
  renderEnumOrdinal({ column }) {
3204
3551
  return `${column} + 0`;
3205
3552
  },
3206
- renderEnumMinMax({ fn, column, result }) {
3553
+ renderEnumMinMax({ fn, column, result, when }) {
3207
3554
  const aggregate = fn === "min" ? "MIN" : "MAX";
3555
+ const only = (value) => when === undefined ? value : `CASE WHEN ${when} THEN ${value} END`;
3208
3556
  if (result === "ordinal")
3209
- return `${aggregate}(${column} + 0)`;
3210
- 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)`;
3211
3559
  },
3212
3560
  placeholder() {
3213
3561
  return "?";
@@ -3231,7 +3579,7 @@ function getSqlDialect(params) {
3231
3579
  return SQL_DIALECTS[params.dialect];
3232
3580
  }
3233
3581
  // packages/sql/src/key-list.ts
3234
- import { unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
3582
+ import { unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
3235
3583
  var SCALAR_ELEMENT_TYPES = Object.freeze({
3236
3584
  String: "text",
3237
3585
  Boolean: "bool",
@@ -3302,7 +3650,7 @@ function postgresKeyListElementType(params) {
3302
3650
  const { dialect, member } = params;
3303
3651
  const type = postgresKeyListElementTypeOf({ dialect, member });
3304
3652
  if (type === undefined) {
3305
- throw unsupportedCapability16({
3653
+ throw unsupportedCapability17({
3306
3654
  dialect: dialect.name,
3307
3655
  feature: `the key-list filter (IN) on a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} member`,
3308
3656
  hint: "IN binds every member as a typed array; filter this field with equality inside OR instead"
@@ -3323,7 +3671,7 @@ function postgresBulkInsertArrayCast(params) {
3323
3671
  const { dialect, member } = params;
3324
3672
  const cast = postgresBulkInsertArrayCastOf({ dialect, member });
3325
3673
  if (cast === undefined) {
3326
- throw unsupportedCapability16({
3674
+ throw unsupportedCapability17({
3327
3675
  dialect: dialect.name,
3328
3676
  feature: `a bulk insert binding a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} column as a typed array`,
3329
3677
  hint: "the query builder keeps VALUES for this column type"
@@ -3343,7 +3691,7 @@ function renderLocalDateCheck({ dialect, table, column }) {
3343
3691
  };
3344
3692
  }
3345
3693
  // packages/sql/src/date-truncation.ts
3346
- 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";
3347
3695
  var TIME_ZONE_NAME = /^[A-Za-z][A-Za-z0-9_+\-/]{0,63}$/;
3348
3696
  function isTimeZoneName(params) {
3349
3697
  return typeof params.value === "string" && TIME_ZONE_NAME.test(params.value);
@@ -3351,17 +3699,17 @@ function isTimeZoneName(params) {
3351
3699
  function renderDateTruncation(params) {
3352
3700
  const { dialect, reference, truncation } = params;
3353
3701
  if (!dialect.capabilities.dateTruncation) {
3354
- throw unsupportedCapability17({
3702
+ throw unsupportedCapability18({
3355
3703
  dialect: dialect.name,
3356
3704
  feature: "a truncated groupBy key ({ field, truncate, timeZone })",
3357
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"
3358
3706
  });
3359
3707
  }
3360
3708
  if (!DATE_TRUNCATION_UNITS.includes(truncation.unit)) {
3361
- throw internalError2({ message: `unknown date truncation unit ${JSON.stringify(truncation.unit)}` });
3709
+ throw internalError3({ message: `unknown date truncation unit ${JSON.stringify(truncation.unit)}` });
3362
3710
  }
3363
3711
  if (!isTimeZoneName({ value: truncation.timeZone })) {
3364
- throw new VibeError16({
3712
+ throw new VibeError17({
3365
3713
  code: "VIBE_VALIDATION",
3366
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"`,
3367
3715
  meta: { timeZone: truncation.timeZone }
@@ -3375,20 +3723,129 @@ function renderDateTruncation(params) {
3375
3723
  case "timestamp":
3376
3724
  return `(date_trunc(${unit}, ${reference} AT TIME ZONE 'UTC', ${zone}) AT TIME ZONE 'UTC')`;
3377
3725
  default:
3378
- 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];
3379
3836
  }
3380
3837
  }
3381
3838
  // packages/sql/src/generated-columns.ts
3382
- import { VibeError as VibeError17, unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
3839
+ import { VibeError as VibeError18, unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
3383
3840
 
3384
3841
  // packages/sql/src/dialects/postgres-generated-columns.ts
3385
- import { unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
3842
+ import { unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
3386
3843
  function renderPostgresGeneratedColumnClause({ generated }) {
3387
3844
  return `GENERATED ALWAYS AS (${generated.expression}) STORED`;
3388
3845
  }
3389
3846
  function renderPostgresGeneratedColumnProbe({ dialect, probe }) {
3390
3847
  if (dialect.name !== "postgres")
3391
- throw unsupportedCapability18({ dialect: dialect.name, feature: "rollbackGeneratedColumnProbe" });
3848
+ throw unsupportedCapability19({ dialect: dialect.name, feature: "rollbackGeneratedColumnProbe" });
3392
3849
  const q = ({ name }) => dialect.quoteIdent({ name });
3393
3850
  let text;
3394
3851
  switch (probe.operation) {
@@ -3409,20 +3866,20 @@ function renderPostgresGeneratedColumnProbe({ dialect, probe }) {
3409
3866
  }
3410
3867
  function renderPostgresDropGeneratedExpression({ dialect, table, column }) {
3411
3868
  if (dialect.name !== "postgres")
3412
- throw unsupportedCapability18({ dialect: dialect.name, feature: "dropGeneratedExpression" });
3869
+ throw unsupportedCapability19({ dialect: dialect.name, feature: "dropGeneratedExpression" });
3413
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" };
3414
3871
  }
3415
3872
 
3416
3873
  // packages/sql/src/generated-columns.ts
3417
3874
  function renderGeneratedColumnClause({ dialect, generated }) {
3418
3875
  if (!("generatedColumns" in dialect.capabilities) || dialect.capabilities.generatedColumns !== true) {
3419
- throw unsupportedCapability19({ dialect: dialect.name, feature: "generatedColumns" });
3876
+ throw unsupportedCapability20({ dialect: dialect.name, feature: "generatedColumns" });
3420
3877
  }
3421
3878
  if (generated.storage !== "stored") {
3422
- throw unsupportedCapability19({ dialect: dialect.name, feature: "virtualGeneratedColumns" });
3879
+ throw unsupportedCapability20({ dialect: dialect.name, feature: "virtualGeneratedColumns" });
3423
3880
  }
3424
3881
  if (typeof generated.expression !== "string" || generated.expression.trim().length === 0 || generated.expression.includes("\x00")) {
3425
- 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" } });
3426
3883
  }
3427
3884
  switch (dialect.name) {
3428
3885
  case "postgres":
@@ -3434,10 +3891,10 @@ function renderGeneratedColumnClause({ dialect, generated }) {
3434
3891
  }
3435
3892
  }
3436
3893
  // packages/sql/src/row-rules.ts
3437
- 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";
3438
3895
 
3439
3896
  // packages/sql/src/dialects/postgres-row-rules.ts
3440
- import { VibeError as VibeError18, unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
3897
+ import { VibeError as VibeError19, unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
3441
3898
  function comparison({ dialect, field }) {
3442
3899
  const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
3443
3900
  const cast = field.type === "Json" ? `::pg_catalog.jsonb${field.isList ? "[]" : ""}` : "";
@@ -3445,7 +3902,7 @@ function comparison({ dialect, field }) {
3445
3902
  }
3446
3903
  function renderPostgresRowRule({ dialect, context }) {
3447
3904
  if (dialect.name !== "postgres")
3448
- throw unsupportedCapability20({ dialect: dialect.name, feature: "postgresRowRules" });
3905
+ throw unsupportedCapability21({ dialect: dialect.name, feature: "postgresRowRules" });
3449
3906
  const { rule, table, functionIdentity, triggerIdentity, protectedFields, gateField } = context;
3450
3907
  const qualify = ({ name }) => `${dialect.quoteIdent({ name: name.namespace })}.${dialect.quoteIdent({ name: name.name })}`;
3451
3908
  const functionName = qualify({ name: functionIdentity.name });
@@ -3462,7 +3919,7 @@ END;`;
3462
3919
  } else {
3463
3920
  const terms = protectedFields.map((field) => comparison({ dialect, field }));
3464
3921
  if (terms.length === 0) {
3465
- 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 } });
3466
3923
  }
3467
3924
  const condition = gateField === undefined ? terms.join(`
3468
3925
  OR `) : `OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
@@ -3497,19 +3954,19 @@ FOR EACH ROW EXECUTE FUNCTION ${functionName}();`;
3497
3954
  }
3498
3955
 
3499
3956
  // packages/sql/src/dialects/sqlite-row-rules.ts
3500
- 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";
3501
3958
  function renderSqliteRowRule({ dialect, context }) {
3502
3959
  if (dialect.name !== "sqlite")
3503
- throw unsupportedCapability21({ dialect: dialect.name, feature: "sqliteRowRules" });
3960
+ throw unsupportedCapability22({ dialect: dialect.name, feature: "sqliteRowRules" });
3504
3961
  const { rule, table, triggers, protectedFields, gateField } = context;
3505
3962
  if (table.namespace !== "main")
3506
- throw unsupportedCapability21({ dialect: dialect.name, feature: "fixedNamespaces" });
3963
+ throw unsupportedCapability22({ dialect: dialect.name, feature: "fixedNamespaces" });
3507
3964
  const terms = protectedFields.map((field) => {
3508
3965
  const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
3509
3966
  return `OLD.${column} IS NOT NEW.${column}`;
3510
3967
  });
3511
3968
  if (rule.kind !== "appendOnly" && terms.length === 0)
3512
- 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 } });
3513
3970
  const condition = rule.kind === "appendOnly" ? "" : gateField === undefined ? `WHEN ${terms.join(`
3514
3971
  OR `)}
3515
3972
  ` : `WHEN OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
@@ -3534,11 +3991,11 @@ END;`,
3534
3991
  }
3535
3992
 
3536
3993
  // packages/sql/src/dialects/mysql-row-rules.ts
3537
- 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";
3538
3995
  function diagnosticLiteral({ name, context, dialect }) {
3539
3996
  const message = `Check constraint '${name}' is violated.`;
3540
3997
  if (mysqlRowRuleMessageByteLength({ triggerName: name }) > MYSQL_ROW_RULE_MESSAGE_MAX_BYTES)
3541
- 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 } });
3542
3999
  if (message.includes("\\")) {
3543
4000
  const hex = Array.from(new TextEncoder().encode(message), (byte) => byte.toString(16).padStart(2, "0")).join("");
3544
4001
  return `_utf8mb4 X'${hex}'`;
@@ -3547,11 +4004,11 @@ function diagnosticLiteral({ name, context, dialect }) {
3547
4004
  }
3548
4005
  function renderMysqlRowRule({ dialect, context }) {
3549
4006
  if (dialect.name !== "mysql")
3550
- throw unsupportedCapability22({ dialect: dialect.name, feature: "mysqlRowRules" });
4007
+ throw unsupportedCapability23({ dialect: dialect.name, feature: "mysqlRowRules" });
3551
4008
  const { rule, table, triggers, protectedFields, gateField, placement } = context;
3552
4009
  for (const name of Object.keys(placement ?? {}))
3553
4010
  if (!triggers.some(({ identity }) => identity.name === name))
3554
- 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 } });
3555
4012
  const position = ({ name }) => {
3556
4013
  const entry = placement?.[name];
3557
4014
  if (entry === undefined)
@@ -3564,7 +4021,7 @@ function renderMysqlRowRule({ dialect, context }) {
3564
4021
  return `NOT (OLD.${column} <=> NEW.${column})`;
3565
4022
  });
3566
4023
  if (rule.kind !== "appendOnly" && terms.length === 0)
3567
- 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 } });
3568
4025
  const condition = gateField === undefined ? terms.join(`
3569
4026
  OR `) : `OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
3570
4027
  ${terms.join(`
@@ -3593,10 +4050,10 @@ END;`, values: [] };
3593
4050
  function renderRowRule({ schema, model, rule, dialect, placement }) {
3594
4051
  const issue = rowRuleShapeIssue({ value: rule });
3595
4052
  if (issue !== undefined) {
3596
- 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 } });
3597
4054
  }
3598
4055
  if (placement !== undefined && Object.keys(placement).length > 0 && dialect.name !== "mysql")
3599
- throw unsupportedCapability23({ dialect: dialect.name, feature: "rowRuleTriggerPlacement" });
4056
+ throw unsupportedCapability24({ dialect: dialect.name, feature: "rowRuleTriggerPlacement" });
3600
4057
  const normalized = normalizeRowRules({ schema, model, rules: [rule], dialect: dialect.name })[0];
3601
4058
  switch (dialect.name) {
3602
4059
  case "postgres": {
@@ -3604,7 +4061,7 @@ function renderRowRule({ schema, model, rule, dialect, placement }) {
3604
4061
  const functionIdentity = objects.find((object) => object.role === "function")?.identity;
3605
4062
  const triggerIdentity = objects.find((object) => object.role === "trigger")?.identity;
3606
4063
  if (functionIdentity?.kind !== "function" || triggerIdentity?.kind !== "trigger")
3607
- 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 } });
3608
4065
  const gate = rowRuleGateField({ rule: normalized });
3609
4066
  const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
3610
4067
  return renderPostgresRowRule({
@@ -3625,15 +4082,15 @@ function renderRowRule({ schema, model, rule, dialect, placement }) {
3625
4082
  const objects = rowRuleIdentities({ schema, model, rule: normalized, dialect: dialect.name });
3626
4083
  const triggers = objects.map(({ role, identity }) => {
3627
4084
  if (role !== "trigger" || identity.kind !== "trigger")
3628
- 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 } });
3629
4086
  const event = ["update", "delete"].find((event) => identity.name === rowRuleTriggerName({ rule: normalized, event }));
3630
4087
  if (event === undefined)
3631
- 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 } });
3632
4089
  return { event, identity: { kind: "trigger", table: identity.table, name: identity.name } };
3633
4090
  });
3634
4091
  const table = triggers[0]?.identity.table;
3635
4092
  if (table === undefined)
3636
- 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 } });
3637
4094
  const gate = rowRuleGateField({ rule: normalized });
3638
4095
  const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
3639
4096
  const context = {
@@ -3664,15 +4121,16 @@ function renderSqliteJsonEquality(params) {
3664
4121
  }
3665
4122
 
3666
4123
  // packages/sql/src/dialects/diagnostics.ts
3667
- import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
4124
+ import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as unsupportedCapability25 } from "@vibeorm/schema";
3668
4125
  var QUERIES = Object.freeze({
3669
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"` },
3670
4128
  sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
3671
4129
  });
3672
4130
  function renderDiagnostic(params) {
3673
4131
  const query = QUERIES[params.query];
3674
4132
  if (params.dialect.name !== query.dialect) {
3675
- 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}` });
3676
4134
  }
3677
4135
  return { text: query.text, values: [], returning: "none" };
3678
4136
  }
@@ -3683,7 +4141,7 @@ var CAPTURE_READINESS_SQL = `SELECT pg_catalog.current_setting('server_version_n
3683
4141
  function renderCaptureReadiness(params) {
3684
4142
  const { dialect, tables } = params;
3685
4143
  if (dialect.name !== "postgres") {
3686
- 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" });
3687
4145
  }
3688
4146
  const entries = tables.map((entry, index) => {
3689
4147
  const { name, namespace } = entry.table;
@@ -3697,7 +4155,7 @@ function renderCaptureReadiness(params) {
3697
4155
  }
3698
4156
 
3699
4157
  // packages/sql/src/dialects/postgres-diagnostics.ts
3700
- import { VibeError as VibeError22, unsupportedCapability as unsupportedCapability25 } from "@vibeorm/schema";
4158
+ import { VibeError as VibeError23, unsupportedCapability as unsupportedCapability26 } from "@vibeorm/schema";
3701
4159
  function renderPostgresExplain(params) {
3702
4160
  const { dialect, query, options } = params;
3703
4161
  requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
@@ -3732,7 +4190,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
3732
4190
  });
3733
4191
  function requirePostgres(params) {
3734
4192
  if (params.dialect.name !== "postgres") {
3735
- throw unsupportedCapability25({
4193
+ throw unsupportedCapability26({
3736
4194
  dialect: params.dialect.name,
3737
4195
  feature: params.feature,
3738
4196
  hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
@@ -3750,7 +4208,7 @@ function renderPostgresWorkload(params) {
3750
4208
  if (query === "statementSnapshot") {
3751
4209
  const bounded = limit ?? 20;
3752
4210
  if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
3753
- throw new VibeError22({
4211
+ throw new VibeError23({
3754
4212
  code: "VIBE_CONFIG",
3755
4213
  message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
3756
4214
  meta: { limit }
@@ -3769,7 +4227,7 @@ function renderPostgresWorkload(params) {
3769
4227
  function requireArg(params) {
3770
4228
  const { value, name, query } = params;
3771
4229
  if (value === undefined || value.length === 0) {
3772
- throw new VibeError22({
4230
+ throw new VibeError23({
3773
4231
  code: "VIBE_CONFIG",
3774
4232
  message: `the "${query}" diagnostic read requires a ${name}`,
3775
4233
  meta: { query, argument: name }
@@ -3779,10 +4237,10 @@ function requireArg(params) {
3779
4237
  }
3780
4238
 
3781
4239
  // packages/sql/src/dialects/postgres-rls-readiness.ts
3782
- import { unsupportedCapability as unsupportedCapability27 } from "@vibeorm/schema";
4240
+ import { unsupportedCapability as unsupportedCapability28 } from "@vibeorm/schema";
3783
4241
 
3784
4242
  // packages/sql/src/dialects/postgres-rls.ts
3785
- 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";
3786
4244
  var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
3787
4245
  var COLUMN_TYPES = {
3788
4246
  String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
@@ -3795,18 +4253,18 @@ var COLUMN_TYPES = {
3795
4253
  };
3796
4254
  function requireRls(params) {
3797
4255
  if (!params.dialect.capabilities.nativeRowLevelSecurity) {
3798
- throw unsupportedCapability26({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
4256
+ throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
3799
4257
  }
3800
4258
  }
3801
4259
  function identifier2(params) {
3802
4260
  if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
3803
- 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" });
3804
4262
  }
3805
4263
  return params.dialect.quoteIdent({ name: params.name });
3806
4264
  }
3807
4265
  function tableRef(params) {
3808
4266
  if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
3809
- 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" });
3810
4268
  }
3811
4269
  return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
3812
4270
  }
@@ -3847,7 +4305,7 @@ function renderRlsColumnType(params) {
3847
4305
  if (native !== undefined)
3848
4306
  return native;
3849
4307
  }
3850
- 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}` });
3851
4309
  }
3852
4310
  function protectedModel(params) {
3853
4311
  requireRls(params);
@@ -3855,7 +4313,7 @@ function protectedModel(params) {
3855
4313
  assertValidSchemaIR({ schema: params.schema });
3856
4314
  const model = params.schema.models.find((entry) => entry.name === params.model);
3857
4315
  if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
3858
- 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 } });
3859
4317
  }
3860
4318
  return model;
3861
4319
  }
@@ -3888,7 +4346,7 @@ function predicate(params) {
3888
4346
  }).join(" AND ");
3889
4347
  return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
3890
4348
  }
3891
- 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" });
3892
4350
  }
3893
4351
  function renderRlsPolicy(params) {
3894
4352
  const model = protectedModel(params);
@@ -3929,7 +4387,7 @@ function renderRlsContext(params) {
3929
4387
  }
3930
4388
  function runtimeRoleOf(params) {
3931
4389
  if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
3932
- 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" });
3933
4391
  }
3934
4392
  return params.role;
3935
4393
  }
@@ -4086,7 +4544,7 @@ ORDER BY c.relname, p.polname`,
4086
4544
  });
4087
4545
  function requireRls2(params) {
4088
4546
  if (!params.dialect.capabilities.nativeRowLevelSecurity) {
4089
- throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
4547
+ throw unsupportedCapability28({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
4090
4548
  }
4091
4549
  }
4092
4550
  function renderRlsReadinessQuery(params) {
@@ -4118,7 +4576,7 @@ function renderRlsPolicyPath(params) {
4118
4576
  }
4119
4577
 
4120
4578
  // packages/sql/src/render.ts
4121
- 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";
4122
4580
 
4123
4581
  // packages/sql/src/like.ts
4124
4582
  function escapeLikePattern(params) {
@@ -4206,21 +4664,29 @@ function renderStatement(params) {
4206
4664
  }
4207
4665
  }
4208
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
+ }
4209
4673
  const name = dialect.quoteIdent({ name: column.name });
4210
4674
  const ref = column.table === undefined ? name : `${dialect.quoteIdent({ name: column.table })}.${name}`;
4211
4675
  if (column.truncate !== undefined)
4212
4676
  return renderDateTruncation({ dialect, reference: ref, truncation: column.truncate });
4677
+ if (column.groupKey !== undefined)
4678
+ return renderGroupKey({ dialect, reference: ref, key: column.groupKey });
4213
4679
  if (column.enumOrdinal === true)
4214
4680
  return dialect.renderEnumOrdinal({ column: ref });
4215
4681
  if (column.castReal === true) {
4216
4682
  if (dialect.name !== "sqlite") {
4217
- throw internalError4({ message: "ColRef.castReal is a sqlite Decimal construct" });
4683
+ throw internalError5({ message: "ColRef.castReal is a sqlite Decimal construct" });
4218
4684
  }
4219
4685
  return `CAST(${ref} AS REAL)`;
4220
4686
  }
4221
4687
  if (column.castJsonb !== undefined) {
4222
4688
  if (dialect.name !== "postgres") {
4223
- throw internalError4({ message: "ColRef.castJsonb is a postgres json construct" });
4689
+ throw internalError5({ message: "ColRef.castJsonb is a postgres json construct" });
4224
4690
  }
4225
4691
  return `${ref}::${column.castJsonb}`;
4226
4692
  }
@@ -4228,7 +4694,9 @@ function quoteCol(dialect, column) {
4228
4694
  }
4229
4695
  function typedOrderRef(params) {
4230
4696
  const { statement, column } = params;
4231
- 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)
4232
4700
  return column;
4233
4701
  const renamed = statement.columns.some((projected) => (projected.alias ?? projected.name) === column.name && (projected.transform !== undefined || projected.alias !== undefined));
4234
4702
  return renamed ? { ...column, table: statement.table.alias ?? statement.table.name } : column;
@@ -4237,14 +4705,14 @@ function jsonCellCast(dialect, cell) {
4237
4705
  if (cell.storage !== "json" || dialect.jsonParamCast === "")
4238
4706
  return dialect.jsonParamCast;
4239
4707
  if (dialect.name !== "postgres") {
4240
- throw internalError4({ message: "JsonParamCell storage json is a postgres construct" });
4708
+ throw internalError5({ message: "JsonParamCell storage json is a postgres construct" });
4241
4709
  }
4242
4710
  return "::text::json";
4243
4711
  }
4244
4712
  function quoteProjection(dialect, column) {
4245
4713
  if (column.transform === undefined) {
4246
4714
  const base = quoteCol(dialect, column);
4247
- const alias = column.alias ?? (column.truncate === undefined ? undefined : column.name);
4715
+ const alias = column.alias ?? (column.truncate === undefined && column.groupKey === undefined ? undefined : column.name);
4248
4716
  if (alias === undefined)
4249
4717
  return base;
4250
4718
  return `${base} AS ${dialect.quoteIdent({ name: alias })}`;
@@ -4261,7 +4729,7 @@ function quoteProjection(dialect, column) {
4261
4729
  if (column.transform === "encodeBase64" && dialect.name === "mysql")
4262
4730
  return `TO_BASE64(${ref}) AS ${alias}`;
4263
4731
  if (dialect.name !== "postgres") {
4264
- throw internalError4({ message: `${column.transform} is a postgres construct` });
4732
+ throw internalError5({ message: `${column.transform} is a postgres construct` });
4265
4733
  }
4266
4734
  if (column.transform === "encodeArrayBase64")
4267
4735
  return `encode(array_send(${ref}), 'base64') AS ${alias}`;
@@ -4285,7 +4753,7 @@ function renderTable(dialect, table) {
4285
4753
  }
4286
4754
  function assertCount(value, what) {
4287
4755
  if (!Number.isSafeInteger(value) || value < 0) {
4288
- 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}` });
4289
4757
  }
4290
4758
  }
4291
4759
  var COMPARISON_SQL2 = {
@@ -4309,7 +4777,9 @@ var AGG_FN_SQL = {
4309
4777
  min: "MIN",
4310
4778
  max: "MAX"
4311
4779
  };
4312
- function renderAggRef(dialect, agg) {
4780
+ function renderAggRef(dialect, agg, collector) {
4781
+ if (agg.filter !== undefined)
4782
+ return renderFilteredAggregate({ dialect, agg, filter: agg.filter, collector });
4313
4783
  if (agg.fn === "countAll")
4314
4784
  return "COUNT(*)";
4315
4785
  const column = quoteCol(dialect, agg.column);
@@ -4318,10 +4788,112 @@ function renderAggRef(dialect, agg) {
4318
4788
  }
4319
4789
  return `${AGG_FN_SQL[agg.fn]}(${column})`;
4320
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
+ }
4321
4893
  function assertScalarArrays(dialect, feature) {
4322
4894
  if (dialect.capabilities.scalarArrays)
4323
4895
  return;
4324
- throw unsupportedCapability28({
4896
+ throw unsupportedCapability29({
4325
4897
  dialect: dialect.name,
4326
4898
  feature,
4327
4899
  hint: "only postgres has native array columns \u2014 model the list as a related table instead"
@@ -4333,7 +4905,7 @@ function renderExpr(params) {
4333
4905
  case "cmp": {
4334
4906
  const op = COMPARISON_SQL2[expr.op];
4335
4907
  if (op === undefined) {
4336
- throw internalError4({ message: `unknown comparison operator "${expr.op}"` });
4908
+ throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
4337
4909
  }
4338
4910
  if (isDbNowMarker(expr.value)) {
4339
4911
  return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
@@ -4344,7 +4916,7 @@ function renderExpr(params) {
4344
4916
  case "cmpCol": {
4345
4917
  const op = COMPARISON_SQL2[expr.op];
4346
4918
  if (op === undefined) {
4347
- throw internalError4({ message: `unknown comparison operator "${expr.op}"` });
4919
+ throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
4348
4920
  }
4349
4921
  return `${quoteCol(dialect, expr.left)} ${op} ${quoteCol(dialect, expr.right)}`;
4350
4922
  }
@@ -4368,11 +4940,11 @@ function renderExpr(params) {
4368
4940
  }
4369
4941
  case "tupleIn": {
4370
4942
  if (expr.columns.length === 0) {
4371
- throw internalError4({ message: "tupleIn needs at least one column" });
4943
+ throw internalError5({ message: "tupleIn needs at least one column" });
4372
4944
  }
4373
4945
  for (const row of expr.rows) {
4374
4946
  if (row.length !== expr.columns.length) {
4375
- throw internalError4({
4947
+ throw internalError5({
4376
4948
  message: `tupleIn row has ${row.length} values for ${expr.columns.length} columns`
4377
4949
  });
4378
4950
  }
@@ -4398,7 +4970,7 @@ function renderExpr(params) {
4398
4970
  case "rowCompare": {
4399
4971
  const op = COMPARISON_SQL2[expr.op];
4400
4972
  if (op === undefined || expr.columns.length === 0 || expr.values.length !== expr.columns.length) {
4401
- 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)` });
4402
4974
  }
4403
4975
  const columns = expr.columns.map((column) => quoteCol(dialect, column));
4404
4976
  const placeholders = expr.values.map((value) => collector.add(value));
@@ -4406,9 +4978,9 @@ function renderExpr(params) {
4406
4978
  }
4407
4979
  case "inSelect": {
4408
4980
  if (expr.columns.length === 0)
4409
- throw internalError4({ message: "inSelect needs at least one column" });
4981
+ throw internalError5({ message: "inSelect needs at least one column" });
4410
4982
  if (dialect.name === "mysql" && (expr.select.limit !== undefined || expr.select.offset !== undefined)) {
4411
- throw unsupportedCapability28({
4983
+ throw unsupportedCapability29({
4412
4984
  dialect: dialect.name,
4413
4985
  feature: "LIMIT inside an IN subquery",
4414
4986
  hint: "mysql refuses LIMIT directly inside an IN subquery (error 1235); wrap the limited select in a derived table"
@@ -4479,9 +5051,9 @@ function renderExpr(params) {
4479
5051
  case "aggCmp": {
4480
5052
  const op = COMPARISON_SQL2[expr.op];
4481
5053
  if (op === undefined) {
4482
- throw internalError4({ message: `unknown comparison operator "${expr.op}"` });
5054
+ throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
4483
5055
  }
4484
- return `${renderAggRef(dialect, expr.agg)} ${op} ${collector.add(expr.value)}`;
5056
+ return `${renderAggRef(dialect, expr.agg, collector)} ${op} ${collector.add(expr.value)}`;
4485
5057
  }
4486
5058
  case "arrayHas": {
4487
5059
  assertScalarArrays(dialect, 'the scalar-list filter "has"');
@@ -4534,19 +5106,6 @@ function renderFragment(params) {
4534
5106
  }
4535
5107
  return text;
4536
5108
  }
4537
- function jsonPathString(path) {
4538
- let out = "$";
4539
- for (const segment of path) {
4540
- if (typeof segment === "number") {
4541
- out += `[${segment}]`;
4542
- } else if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment)) {
4543
- out += `.${segment}`;
4544
- } else {
4545
- out += `."${segment.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
4546
- }
4547
- }
4548
- return out;
4549
- }
4550
5109
  function listComparison(params) {
4551
5110
  const { dialect, column, value, collector } = params;
4552
5111
  if (isJsonListParameter(value) && value.storage === "json") {
@@ -4561,13 +5120,13 @@ function renderKeyList(params) {
4561
5120
  const { dialect, expr, collector } = params;
4562
5121
  const arity = expr.columns.length;
4563
5122
  if (arity === 0)
4564
- throw internalError4({ message: "keyList needs at least one column" });
5123
+ throw internalError5({ message: "keyList needs at least one column" });
4565
5124
  if (expr.members.length !== arity) {
4566
- 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` });
4567
5126
  }
4568
5127
  for (const row of expr.rows) {
4569
5128
  if (row.length !== arity)
4570
- 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` });
4571
5130
  }
4572
5131
  if (expr.rows.length === 0)
4573
5132
  return "1 = 0";
@@ -4599,25 +5158,25 @@ function renderKeyPage(params) {
4599
5158
  const { select } = page;
4600
5159
  const arity = page.keyColumns.length;
4601
5160
  if (arity === 0 || page.members.length !== arity) {
4602
- 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` });
4603
5162
  }
4604
5163
  if (page.rows.length === 0)
4605
- throw internalError4({ message: "keyPage needs at least one key" });
5164
+ throw internalError5({ message: "keyPage needs at least one key" });
4606
5165
  for (const row of page.rows) {
4607
5166
  if (row.length !== arity)
4608
- 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` });
4609
5168
  }
4610
5169
  if (page.identity.length === 0)
4611
- throw internalError4({ message: "keyPage needs the child's identity columns" });
5170
+ throw internalError5({ message: "keyPage needs the child's identity columns" });
4612
5171
  const order = select.orderBy ?? [];
4613
5172
  if (order.length === 0 || order.some((term) => !("column" in term))) {
4614
- 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" });
4615
5174
  }
4616
5175
  if (select.table.alias !== undefined || Object.keys(select).some((member) => !KEY_PAGE_SELECT_MEMBERS.has(member))) {
4617
- 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" });
4618
5177
  }
4619
5178
  if (dialect.keyPageStrategy === "window") {
4620
- throw unsupportedCapability28({
5179
+ throw unsupportedCapability29({
4621
5180
  dialect: dialect.name,
4622
5181
  feature: "a per-key page (keyPage)",
4623
5182
  hint: "the runtime keeps the ROW_NUMBER window on this dialect"
@@ -4645,7 +5204,7 @@ function renderKeyPage(params) {
4645
5204
  const keys = dialect.quoteIdent({ name: keyAlias });
4646
5205
  if (dialect.keyPageStrategy === "lateral") {
4647
5206
  if (keyListBindsUntypedRows({ members: page.members })) {
4648
- throw unsupportedCapability28({
5207
+ throw unsupportedCapability29({
4649
5208
  dialect: dialect.name,
4650
5209
  feature: "a per-key page over a DateTime key without a declared native type",
4651
5210
  hint: "the runtime keeps the ROW_NUMBER window for such keys"
@@ -4673,22 +5232,22 @@ function renderKeyPage(params) {
4673
5232
  function renderKeyRows(params) {
4674
5233
  const { dialect, statement, keys, collector } = params;
4675
5234
  if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.fromUnion !== undefined || statement.table.alias !== undefined) {
4676
- 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" });
4677
5236
  }
4678
5237
  if (statement.rowLock !== undefined)
4679
- throw internalError4({ message: "a locking read cannot read from key rows" });
5238
+ throw internalError5({ message: "a locking read cannot read from key rows" });
4680
5239
  const arity = keys.columns.length;
4681
5240
  if (arity === 0 || keys.members.length !== arity) {
4682
- 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` });
4683
5242
  }
4684
5243
  if (keys.rows.length === 0)
4685
- throw internalError4({ message: "fromKeys needs at least one key row" });
5244
+ throw internalError5({ message: "fromKeys needs at least one key row" });
4686
5245
  for (const row of keys.rows) {
4687
5246
  if (row.length !== arity)
4688
- 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` });
4689
5248
  }
4690
5249
  if (dialect.keyListStrategy !== "unnest" || keyListBindsUntypedRows({ members: keys.members })) {
4691
- throw unsupportedCapability28({
5250
+ throw unsupportedCapability29({
4692
5251
  dialect: dialect.name,
4693
5252
  feature: dialect.keyListStrategy !== "unnest" ? "a key-rows FROM (fromKeys)" : "a key-rows FROM over a DateTime key without a declared native type",
4694
5253
  hint: "the runtime keeps the per-relation loads for this write"
@@ -4704,20 +5263,20 @@ function renderUnionPage(params) {
4704
5263
  const { dialect, statement, collector } = params;
4705
5264
  const branches = statement.fromUnion ?? [];
4706
5265
  if (branches.length < 2)
4707
- throw internalError4({ message: "fromUnion needs at least two branches" });
5266
+ throw internalError5({ message: "fromUnion needs at least two branches" });
4708
5267
  if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.table.alias !== undefined) {
4709
- 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" });
4710
5269
  }
4711
5270
  if (statement.rowLock !== undefined)
4712
- 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" });
4713
5272
  const outputNames = (branch) => branch.columns.map((column) => column.alias ?? column.name).join("\x00");
4714
5273
  const names = outputNames(branches[0]);
4715
5274
  return branches.map((branch, index) => {
4716
5275
  if (branch.columns.length === 0 || Object.keys(branch).some((member) => !UNION_BRANCH_MEMBERS.has(member))) {
4717
- 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]" });
4718
5277
  }
4719
5278
  if (outputNames(branch) !== names)
4720
- 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" });
4721
5280
  const inner = renderSelect({ dialect, statement: branch, collector });
4722
5281
  return `SELECT * FROM (${inner.text}) AS ${dialect.quoteIdent({ name: `__vibe_u${index}` })}`;
4723
5282
  }).join(" UNION ALL ");
@@ -4725,7 +5284,7 @@ function renderUnionPage(params) {
4725
5284
  function jsonStringPattern(params) {
4726
5285
  const { expr } = params;
4727
5286
  if (typeof expr.value !== "string") {
4728
- throw internalError4({ message: `json ${expr.op} expects a string operand` });
5287
+ throw internalError5({ message: `json ${expr.op} expects a string operand` });
4729
5288
  }
4730
5289
  if (expr.op === "string_contains")
4731
5290
  return containsPattern({ raw: expr.value });
@@ -4746,10 +5305,14 @@ function renderJsonLike(params) {
4746
5305
  base = `${base}${dialect.likeEscapeClause}`;
4747
5306
  return expr.negate === true ? `NOT (${base})` : base;
4748
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
+ }
4749
5312
  function renderJsonFilter(params) {
4750
5313
  const { dialect, expr, collector } = params;
4751
5314
  if (!dialect.supportedJsonOps.includes(expr.op)) {
4752
- throw unsupportedCapability28({
5315
+ throw unsupportedCapability29({
4753
5316
  dialect: dialect.name,
4754
5317
  feature: `the json filter "${expr.op}"`,
4755
5318
  hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
@@ -4761,6 +5324,16 @@ function renderJsonFilter(params) {
4761
5324
  const pathParam = `${collector.add(expr.path.map(String))}::text[]`;
4762
5325
  const extracted = expr.storage === "json" ? `(${column} #> ${pathParam})::jsonb` : `(${column} #> ${pathParam})`;
4763
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
+ }
4764
5337
  case "equals":
4765
5338
  return `${extracted} ${cmp} ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
4766
5339
  case "string_contains":
@@ -4798,6 +5371,8 @@ function renderJsonFilter(params) {
4798
5371
  return expr.negate === true ? `NOT (${base})` : base;
4799
5372
  };
4800
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 });
4801
5376
  case "equals":
4802
5377
  return compareExtracted(jsonPathString(expr.path), expr.value);
4803
5378
  case "string_contains":
@@ -4816,7 +5391,7 @@ function renderJsonFilter(params) {
4816
5391
  const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
4817
5392
  const tests = elements.map((element) => {
4818
5393
  if (element === null || typeof element === "object" && element !== null) {
4819
- throw unsupportedCapability28({
5394
+ throw unsupportedCapability29({
4820
5395
  dialect: dialect.name,
4821
5396
  feature: "json array_contains with object/array/null elements",
4822
5397
  hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
@@ -4830,12 +5405,16 @@ function renderJsonFilter(params) {
4830
5405
  return tests.length === 0 ? guard : `(${[guard, ...tests].join(" AND ")})`;
4831
5406
  }
4832
5407
  default:
4833
- 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` });
4834
5409
  }
4835
5410
  }
4836
5411
  const column = quoteCol(dialect, expr.column);
4837
5412
  const pathString = jsonPathString(expr.path);
4838
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
+ }
4839
5418
  case "equals":
4840
5419
  return `JSON_EXTRACT(${column}, ${collector.add(pathString)}) ${cmp} CAST(${collector.add(JSON.stringify(expr.value))} AS JSON)`;
4841
5420
  case "string_contains":
@@ -4852,10 +5431,18 @@ function renderJsonFilter(params) {
4852
5431
  return `JSON_EXTRACT(${column}, ${collector.add(`${pathString}[last]`)}) ${cmp} CAST(${collector.add(JSON.stringify(expr.value))} AS JSON)`;
4853
5432
  }
4854
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
+ }
4855
5442
  function renderOrderTerm(params) {
4856
- const { dialect, term, collector } = params;
5443
+ const { dialect, term, collector, nullsOutputName } = params;
4857
5444
  if ("agg" in term) {
4858
- return `${renderAggRef(dialect, term.agg)} ${term.direction === "asc" ? "ASC" : "DESC"}`;
5445
+ return `${renderAggRef(dialect, term.agg, collector)} ${term.direction === "asc" ? "ASC" : "DESC"}`;
4859
5446
  }
4860
5447
  const expression = "select" in term ? () => renderScalarSubquery({ dialect, subquery: term, collector }) : () => quoteCol(dialect, term.column);
4861
5448
  const direction = term.direction === "asc" ? "ASC" : "DESC";
@@ -4868,7 +5455,7 @@ function renderOrderTerm(params) {
4868
5455
  if (term.nulls === "first" === (term.direction === "asc")) {
4869
5456
  return `${expression()} ${direction}`;
4870
5457
  }
4871
- const tested = expression();
5458
+ const tested = nullsOutputName === undefined ? expression() : dialect.quoteIdent({ name: nullsOutputName });
4872
5459
  const nullsTerm = term.nulls === "first" ? `${tested} IS NULL DESC` : `${tested} IS NULL`;
4873
5460
  return `${nullsTerm}, ${expression()} ${direction}`;
4874
5461
  }
@@ -4885,11 +5472,11 @@ function renderPositionalRow(params) {
4885
5472
  const position = join.select.rowNumber;
4886
5473
  const positioned = position !== undefined && json.order === "positionSlot" && join.agg === "jsonArray" && position.partitionBy.length === 0 && position.orderBy.length > 0;
4887
5474
  if (join.select.columns.length === 0 || join.select.selectExpr !== undefined || (join.select.aggregates?.length ?? 0) > 0 || position !== undefined && !positioned) {
4888
- 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" });
4889
5476
  }
4890
5477
  const names = [...join.select.columns.map((column) => projectionOutputName({ column })), ...positioned ? [position.alias] : []];
4891
5478
  if (new Set(names).size !== names.length) {
4892
- throw internalError4({ message: "positional lateral projection has duplicate output names" });
5479
+ throw internalError5({ message: "positional lateral projection has duplicate output names" });
4893
5480
  }
4894
5481
  const values = names.map((name) => `${subAlias}.${dialect.quoteIdent({ name })}`);
4895
5482
  const chunks = [];
@@ -4897,14 +5484,14 @@ function renderPositionalRow(params) {
4897
5484
  chunks.push(`${json.rowArray}(${values.slice(index, index + JSON_TRANSPORT.chunkSize).join(", ")})`);
4898
5485
  }
4899
5486
  if (chunks.length > JSON_TRANSPORT.chunkSize) {
4900
- throw internalError4({ message: "positional lateral projection exceeds the transport width limit" });
5487
+ throw internalError5({ message: "positional lateral projection exceeds the transport width limit" });
4901
5488
  }
4902
5489
  return chunks.length === 1 ? chunks[0] : `${json.rowArray}(${chunks.join(", ")})`;
4903
5490
  }
4904
5491
  function lateralJsonOf(params) {
4905
5492
  const json = params.dialect.lateralJson;
4906
5493
  if (json === null)
4907
- throw internalError4({ message: "lateral JSON rendering on a dialect without LATERAL" });
5494
+ throw internalError5({ message: "lateral JSON rendering on a dialect without LATERAL" });
4908
5495
  return json;
4909
5496
  }
4910
5497
  function renderLateralJoin(params) {
@@ -4912,7 +5499,7 @@ function renderLateralJoin(params) {
4912
5499
  const inner = renderSelect({ dialect, statement: join.select, collector });
4913
5500
  if (join.agg === "count") {
4914
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) {
4915
- 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" });
4916
5503
  }
4917
5504
  return `LEFT JOIN LATERAL (${inner.text}) AS ${dialect.quoteIdent({ name: join.alias })} ON true`;
4918
5505
  }
@@ -4924,12 +5511,14 @@ function renderLateralJoin(params) {
4924
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)));
4925
5512
  const terms = join.select.orderBy;
4926
5513
  const columnTerms = terms.filter((term) => ("column" in term));
4927
- 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))) {
4928
5517
  const rendered = columnTerms.map((term) => renderOrderTerm({
4929
5518
  dialect,
4930
5519
  term: {
4931
5520
  ...term,
4932
- 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) }
4933
5522
  },
4934
5523
  collector
4935
5524
  }));
@@ -4937,7 +5526,7 @@ function renderLateralJoin(params) {
4937
5526
  }
4938
5527
  }
4939
5528
  if (join.rowFormat !== "positional" && json.order !== "inAggregate") {
4940
- 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' });
4941
5530
  }
4942
5531
  const rowJson = join.rowFormat === "positional" ? renderPositionalRow({ dialect, join, subAlias }) : `to_json(${subAlias}.*)`;
4943
5532
  const projection = join.agg === "jsonArray" ? `COALESCE(${json.arrayAgg}(${rowJson}${aggOrder}), ${json.emptyArray}) AS ${columnAlias}` : `${rowJson} AS ${columnAlias}`;
@@ -4948,7 +5537,7 @@ function renderCountSubquery(params) {
4948
5537
  const { dialect, subquery, collector } = params;
4949
5538
  const select = subquery.select;
4950
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) {
4951
- throw internalError4({ message: "count subquery must be one ungrouped COUNT(*)" });
5540
+ throw internalError5({ message: "count subquery must be one ungrouped COUNT(*)" });
4952
5541
  }
4953
5542
  const inner = renderSelect({ dialect, statement: select, collector });
4954
5543
  return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
@@ -4966,7 +5555,10 @@ function applyReturning(params) {
4966
5555
  if (!dialect.capabilities.returning) {
4967
5556
  const capture = requested.find(isCaptureReturningColumn);
4968
5557
  if (capture !== undefined)
4969
- 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 });
4970
5562
  return { suffix: "", status: "omitted" };
4971
5563
  }
4972
5564
  const read = (name) => params.qualify === undefined ? name : `${params.qualify}.${name}`;
@@ -4974,7 +5566,9 @@ function applyReturning(params) {
4974
5566
  if (typeof column === "string")
4975
5567
  return read(dialect.quoteIdent({ name: column }));
4976
5568
  if ("capture" in column)
4977
- return renderCaptureReturning({ dialect, column });
5569
+ return renderCaptureReturning({ dialect, column, collector: params.collector });
5570
+ if ("from" in column)
5571
+ return renderOtherRowReturning({ dialect, column });
4978
5572
  if ("transform" in column) {
4979
5573
  const name = dialect.quoteIdent({ name: column.name });
4980
5574
  return `${dialect.jsonTextProjection({ expression: read(name) })} AS ${name}`;
@@ -4987,40 +5581,88 @@ function applyReturning(params) {
4987
5581
  function renderCaptureReturning(params) {
4988
5582
  const { dialect, column } = params;
4989
5583
  if (!dialect.capabilities.rowCapture) {
4990
- 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" });
4991
5585
  }
4992
- const name = dialect.quoteIdent({ name: column.name });
4993
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 });
4994
5595
  if (column.capture === "inserted")
4995
5596
  return `(old.${name} IS NULL) AS ${alias}`;
4996
- 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 })}`;
4997
5639
  }
4998
5640
  function renderKeyTableJoin(params) {
4999
5641
  const { dialect, join, collector } = params;
5000
5642
  const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
5001
5643
  const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
5002
5644
  if (!admitted) {
5003
- throw unsupportedCapability28({
5645
+ throw unsupportedCapability29({
5004
5646
  dialect: dialect.name,
5005
5647
  feature: "relation key table",
5006
5648
  hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
5007
5649
  });
5008
5650
  }
5009
5651
  if (join.columns.length === 0 || join.columns.length !== join.members.length) {
5010
- 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}` });
5011
5653
  }
5012
5654
  if (join.rows.length === 0)
5013
- throw internalError4({ message: "key table needs at least one key row" });
5655
+ throw internalError5({ message: "key table needs at least one key row" });
5014
5656
  for (const member of join.members) {
5015
5657
  if (member.kind === "decimal" && !(Number.isInteger(member.scale) && member.scale >= 0 && member.scale <= 30)) {
5016
- 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)}` });
5017
5659
  }
5018
5660
  }
5019
5661
  const alias = dialect.quoteIdent({ name: join.alias });
5020
5662
  const memberNames = join.members.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
5021
5663
  const rows = join.rows.map((row, ordinal) => {
5022
5664
  if (row.length !== join.members.length) {
5023
- 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` });
5024
5666
  }
5025
5667
  const cells = row.map((value, index) => {
5026
5668
  const placeholder = collector.add(value);
@@ -5050,10 +5692,10 @@ function renderKeyTableJoin(params) {
5050
5692
  function renderLeftJoin(params) {
5051
5693
  const { dialect, join, collector } = params;
5052
5694
  if (join.on.length === 0) {
5053
- throw internalError4({ message: "left join needs at least one ON pair" });
5695
+ throw internalError5({ message: "left join needs at least one ON pair" });
5054
5696
  }
5055
5697
  if (join.select.columns.length === 0 || join.select.columns.some((column) => column.alias === undefined)) {
5056
- 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" });
5057
5699
  }
5058
5700
  const inner = renderSelect({ dialect, statement: join.select, collector });
5059
5701
  const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
@@ -5062,10 +5704,10 @@ function renderLeftJoin(params) {
5062
5704
  function renderSelect(params) {
5063
5705
  const { dialect, statement, collector } = params;
5064
5706
  if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
5065
- throw internalError4({ message: "only countAll can combine with a column list" });
5707
+ throw internalError5({ message: "only countAll can combine with a column list" });
5066
5708
  }
5067
5709
  if (statement.selectExpr !== undefined && statement.aggregates !== undefined && statement.aggregates.length > 0) {
5068
- throw internalError4({ message: "selectExpr and aggregates cannot combine" });
5710
+ throw internalError5({ message: "selectExpr and aggregates cannot combine" });
5069
5711
  }
5070
5712
  const columnList = statement.columns.map((column) => quoteProjection(dialect, column));
5071
5713
  if (statement.selectExpr !== undefined) {
@@ -5073,11 +5715,15 @@ function renderSelect(params) {
5073
5715
  }
5074
5716
  if (statement.aggregates !== undefined) {
5075
5717
  for (const selection of statement.aggregates) {
5076
- const expression = renderAggRef(dialect, selection.agg);
5718
+ const expression = renderAggRef(dialect, selection.agg, collector);
5077
5719
  const projected = selection.castText === true ? textProjection({ dialect, expression }) : expression;
5078
5720
  columnList.push(`${projected} AS ${dialect.quoteIdent({ name: selection.alias })}`);
5079
5721
  }
5080
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
+ }
5081
5727
  if (statement.countSubqueries !== undefined) {
5082
5728
  for (const subquery of statement.countSubqueries) {
5083
5729
  columnList.push(renderCountSubquery({ dialect, subquery, collector }));
@@ -5091,7 +5737,7 @@ function renderSelect(params) {
5091
5737
  if (statement.rowNumber !== undefined) {
5092
5738
  const rowNumber = statement.rowNumber;
5093
5739
  if (rowNumber.partitionBy.length === 0 && rowNumber.orderBy.length === 0) {
5094
- throw internalError4({ message: "rowNumber needs a partition column or an order" });
5740
+ throw internalError5({ message: "rowNumber needs a partition column or an order" });
5095
5741
  }
5096
5742
  const partition = rowNumber.partitionBy.length === 0 ? "" : `PARTITION BY ${rowNumber.partitionBy.map((column) => quoteCol(dialect, column)).join(", ")}`;
5097
5743
  const order = rowNumber.orderBy.length === 0 ? "" : `${partition === "" ? "" : " "}ORDER BY ${rowNumber.orderBy.map((term) => renderOrderTerm({ dialect, term, collector })).join(", ")}`;
@@ -5099,11 +5745,11 @@ function renderSelect(params) {
5099
5745
  }
5100
5746
  let distinctPrefix = statement.distinct === true ? "DISTINCT " : "";
5101
5747
  if (statement.distinct === true && statement.distinctOn !== undefined) {
5102
- throw internalError4({ message: "distinct and distinctOn cannot combine" });
5748
+ throw internalError5({ message: "distinct and distinctOn cannot combine" });
5103
5749
  }
5104
5750
  if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
5105
5751
  if (!dialect.capabilities.distinctOn) {
5106
- throw unsupportedCapability28({
5752
+ throw unsupportedCapability29({
5107
5753
  dialect: dialect.name,
5108
5754
  feature: "SELECT DISTINCT ON",
5109
5755
  hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
@@ -5119,12 +5765,12 @@ function renderSelect(params) {
5119
5765
  from = `(${renderUnionPage({ dialect, statement, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
5120
5766
  } else if (statement.keyPage !== undefined) {
5121
5767
  if (statement.fromSelect !== undefined || statement.table.alias !== undefined) {
5122
- 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" });
5123
5769
  }
5124
5770
  from = `(${renderKeyPage({ dialect, page: statement.keyPage, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
5125
5771
  } else if (statement.fromSelect !== undefined) {
5126
5772
  if (statement.table.alias !== undefined) {
5127
- 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" });
5128
5774
  }
5129
5775
  const inner = renderSelect({ dialect, statement: statement.fromSelect, collector });
5130
5776
  from = `(${inner.text}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
@@ -5135,7 +5781,7 @@ function renderSelect(params) {
5135
5781
  if (statement.innerJoins !== undefined && statement.innerJoins.length > 0) {
5136
5782
  for (const join of statement.innerJoins) {
5137
5783
  if (join.on.length === 0) {
5138
- throw internalError4({ message: "inner join needs at least one ON pair" });
5784
+ throw internalError5({ message: "inner join needs at least one ON pair" });
5139
5785
  }
5140
5786
  const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
5141
5787
  text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
@@ -5146,7 +5792,7 @@ function renderSelect(params) {
5146
5792
  }
5147
5793
  if (statement.leftJoins !== undefined && statement.leftJoins.length > 0) {
5148
5794
  if (statement.rowLock !== undefined) {
5149
- 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" });
5150
5796
  }
5151
5797
  for (const join of statement.leftJoins) {
5152
5798
  text += ` ${renderLeftJoin({ dialect, join, collector })}`;
@@ -5154,7 +5800,7 @@ function renderSelect(params) {
5154
5800
  }
5155
5801
  if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
5156
5802
  if (!dialect.capabilities.lateralJoin || dialect.lateralJson === null) {
5157
- throw unsupportedCapability28({
5803
+ throw unsupportedCapability29({
5158
5804
  dialect: dialect.name,
5159
5805
  feature: "LATERAL JOIN relation loading",
5160
5806
  hint: 'use the portable "query" relation strategy on this dialect'
@@ -5168,13 +5814,25 @@ function renderSelect(params) {
5168
5814
  text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
5169
5815
  }
5170
5816
  if (statement.groupBy !== undefined && statement.groupBy.length > 0) {
5171
- 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" });
5172
5820
  }
5173
5821
  if (statement.having !== undefined) {
5174
5822
  text += ` HAVING ${renderExpr({ dialect, expr: statement.having, collector })}`;
5175
5823
  }
5176
5824
  if (statement.orderBy !== undefined && statement.orderBy.length > 0) {
5177
- 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
+ });
5178
5836
  text += ` ORDER BY ${terms.join(", ")}`;
5179
5837
  }
5180
5838
  if (statement.limit !== undefined) {
@@ -5191,10 +5849,10 @@ function renderSelect(params) {
5191
5849
  if (statement.rowLock !== undefined) {
5192
5850
  const target = statement.table.alias ?? statement.table.name;
5193
5851
  if (statement.rowLock.of !== undefined && statement.rowLock.of.some((name) => name !== target)) {
5194
- 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}"` });
5195
5853
  }
5196
5854
  if (statement.rowLock.of !== undefined && (statement.fromSelect !== undefined || statement.keyPage !== undefined)) {
5197
- 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" });
5198
5856
  }
5199
5857
  const clause = renderRowLockClause({ dialect, lock: statement.rowLock });
5200
5858
  if (clause !== null)
@@ -5210,7 +5868,7 @@ function unnestElement(params) {
5210
5868
  return null;
5211
5869
  if (isJsonParamCell(value)) {
5212
5870
  if (jsonStorage === undefined || (value.storage ?? "jsonb") !== jsonStorage) {
5213
- 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` });
5214
5872
  }
5215
5873
  return value.text;
5216
5874
  }
@@ -5226,24 +5884,24 @@ function unnestElement(params) {
5226
5884
  return value;
5227
5885
  }
5228
5886
  }
5229
- 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` });
5230
5888
  }
5231
5889
  function renderUnnestRows(params) {
5232
5890
  const { dialect, statement, members, collector } = params;
5233
5891
  if (dialect.bulkInsertForm !== "unnest") {
5234
- throw unsupportedCapability28({
5892
+ throw unsupportedCapability29({
5235
5893
  dialect: dialect.name,
5236
5894
  feature: "a bulk insert bound as typed arrays (INSERT \u2026 SELECT FROM unnest)",
5237
5895
  hint: "this dialect binds createMany rows as VALUES"
5238
5896
  });
5239
5897
  }
5240
5898
  if (statement.columns.length === 0)
5241
- throw internalError4({ message: "an unnest insert needs at least one column" });
5899
+ throw internalError5({ message: "an unnest insert needs at least one column" });
5242
5900
  if (members.length !== statement.columns.length) {
5243
- 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` });
5244
5902
  }
5245
5903
  if (statement.onConflict !== undefined && !("doNothing" in statement.onConflict)) {
5246
- 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" });
5247
5905
  }
5248
5906
  const aliases = statement.columns.map((_column, index) => dialect.quoteIdent({ name: `c${index + 1}` }));
5249
5907
  const arrays = members.map((member, index) => {
@@ -5259,14 +5917,14 @@ function renderUnnestRows(params) {
5259
5917
  function renderInsert(params) {
5260
5918
  const { dialect, statement, collector } = params;
5261
5919
  if (statement.rows.length === 0) {
5262
- throw internalError4({ message: "insert requires at least one row" });
5920
+ throw internalError5({ message: "insert requires at least one row" });
5263
5921
  }
5264
5922
  const defaultValues = statement.columns.length === 0 && dialect.emptyInsert.form === "defaultValues";
5265
5923
  if (defaultValues && statement.rows.length !== 1) {
5266
- 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` });
5267
5925
  }
5268
5926
  if (defaultValues && statement.onConflict !== undefined && !(dialect.emptyInsert.form === "defaultValues" && dialect.emptyInsert.conflictClause)) {
5269
- throw unsupportedCapability28({
5927
+ throw unsupportedCapability29({
5270
5928
  dialect: dialect.name,
5271
5929
  feature: "a conflict clause on an all-default row (DEFAULT VALUES)",
5272
5930
  hint: "supply at least one column value, or drop the conflict clause"
@@ -5274,7 +5932,7 @@ function renderInsert(params) {
5274
5932
  }
5275
5933
  for (const row of statement.rows) {
5276
5934
  if (row.length !== statement.columns.length) {
5277
- throw internalError4({
5935
+ throw internalError5({
5278
5936
  message: `insert row has ${row.length} values but ${statement.columns.length} columns`
5279
5937
  });
5280
5938
  }
@@ -5292,7 +5950,7 @@ function renderInsert(params) {
5292
5950
  if (!isSqlDefaultCell(value))
5293
5951
  return collector.add(value);
5294
5952
  if (!dialect.rowDefaultKeyword) {
5295
- throw internalError4({
5953
+ throw internalError5({
5296
5954
  message: `${dialect.name} cannot render a per-row DEFAULT cell \u2014 rows must be grouped by column set`
5297
5955
  });
5298
5956
  }
@@ -5308,7 +5966,7 @@ function renderInsert(params) {
5308
5966
  let target = "";
5309
5967
  if (onConflict.constraint !== undefined) {
5310
5968
  if (dialect.name !== "postgres") {
5311
- throw unsupportedCapability28({
5969
+ throw unsupportedCapability29({
5312
5970
  dialect: dialect.name,
5313
5971
  feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
5314
5972
  hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
@@ -5324,7 +5982,7 @@ function renderInsert(params) {
5324
5982
  text += ` ON CONFLICT${target} DO NOTHING`;
5325
5983
  } else {
5326
5984
  if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
5327
- throw unsupportedCapability28({
5985
+ throw unsupportedCapability29({
5328
5986
  dialect: dialect.name,
5329
5987
  feature: "a refined conflict target (constraint / partial-index predicate) on ON DUPLICATE KEY UPDATE",
5330
5988
  hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
@@ -5332,36 +5990,36 @@ function renderInsert(params) {
5332
5990
  }
5333
5991
  const keepName = onConflict.keepColumn ?? statement.columns[0];
5334
5992
  if (keepName === undefined) {
5335
- 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" });
5336
5994
  }
5337
5995
  const keep = dialect.quoteIdent({ name: keepName });
5338
5996
  const current = `${dialect.quoteIdent({ name: statement.table.name })}.${keep}`;
5339
5997
  const flag = onConflict.duplicateFlag;
5340
5998
  if (flag !== undefined && !/^\d{1,20}$/.test(flag)) {
5341
- 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)}` });
5342
6000
  }
5343
6001
  text += flag === undefined ? ` ON DUPLICATE KEY UPDATE ${keep} = ${current}` : ` ON DUPLICATE KEY UPDATE ${keep} = IF(LAST_INSERT_ID(${flag}), ${current}, ${current})`;
5344
6002
  }
5345
6003
  } else {
5346
6004
  const updates = Object.entries(onConflict.update);
5347
6005
  if (updates.length === 0) {
5348
- throw internalError4({ message: "upsert update must set at least one column" });
6006
+ throw internalError5({ message: "upsert update must set at least one column" });
5349
6007
  }
5350
6008
  const selfRef = dialect.quoteIdent({ name: statement.table.name });
5351
6009
  const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true, tableName: statement.table.name });
5352
6010
  if (dialect.upsertForm === "onConflict") {
5353
6011
  if (onConflict.lastInsertIdColumn !== undefined) {
5354
- throw internalError4({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
6012
+ throw internalError5({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
5355
6013
  }
5356
6014
  if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
5357
- throw unsupportedCapability28({
6015
+ throw unsupportedCapability29({
5358
6016
  dialect: dialect.name,
5359
6017
  feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
5360
6018
  hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
5361
6019
  });
5362
6020
  }
5363
6021
  if (onConflict.constraint !== undefined && onConflict.conflictWhere !== undefined) {
5364
- throw internalError4({
6022
+ throw internalError5({
5365
6023
  message: "a named-constraint conflict target already identifies the index \u2014 it cannot carry a partial predicate too"
5366
6024
  });
5367
6025
  }
@@ -5370,7 +6028,7 @@ function renderInsert(params) {
5370
6028
  text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
5371
6029
  if (onConflict.updateWhere !== undefined) {
5372
6030
  if (!dialect.capabilities.conflictUpdateWhere) {
5373
- throw unsupportedCapability28({
6031
+ throw unsupportedCapability29({
5374
6032
  dialect: dialect.name,
5375
6033
  feature: "a guarded conflict update (ON CONFLICT \u2026 DO UPDATE \u2026 WHERE)",
5376
6034
  hint: "run the guarded upsert as a find-then-write in a transaction instead"
@@ -5380,21 +6038,21 @@ function renderInsert(params) {
5380
6038
  }
5381
6039
  } else {
5382
6040
  if (onConflict.updateWhere !== undefined) {
5383
- throw unsupportedCapability28({
6041
+ throw unsupportedCapability29({
5384
6042
  dialect: dialect.name,
5385
6043
  feature: "a guarded conflict update (ON DUPLICATE KEY UPDATE has no WHERE)",
5386
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"
5387
6045
  });
5388
6046
  }
5389
6047
  if (onConflict.constraint !== undefined) {
5390
- throw unsupportedCapability28({
6048
+ throw unsupportedCapability29({
5391
6049
  dialect: dialect.name,
5392
6050
  feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
5393
6051
  hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
5394
6052
  });
5395
6053
  }
5396
6054
  if (onConflict.conflictWhere !== undefined) {
5397
- throw unsupportedCapability28({
6055
+ throw unsupportedCapability29({
5398
6056
  dialect: dialect.name,
5399
6057
  feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
5400
6058
  hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
@@ -5405,25 +6063,35 @@ function renderInsert(params) {
5405
6063
  }
5406
6064
  }
5407
6065
  }
5408
- const returning = applyReturning({ dialect, requested: statement.returning });
6066
+ const returning = applyReturning({ dialect, requested: statement.returning, collector });
5409
6067
  return { text: text + returning.suffix, values: collector.values, returning: returning.status };
5410
6068
  }
5411
6069
  function renderUpdate(params) {
5412
6070
  const { dialect, statement, collector } = params;
5413
6071
  const assignments = Object.entries(statement.set);
5414
6072
  if (assignments.length === 0) {
5415
- throw internalError4({ message: "update must set at least one column" });
6073
+ throw internalError5({ message: "update must set at least one column" });
5416
6074
  }
5417
6075
  const source = statement.from;
5418
6076
  if (source !== undefined) {
5419
6077
  if (statement.alias === undefined || statement.alias === source.alias)
5420
- 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" });
5421
6079
  const target = dialect.quoteIdent({ name: statement.alias });
5422
6080
  const joined = dialect.quoteIdent({ name: source.alias });
5423
6081
  const assign = () => renderAssignments({ dialect, entries: assignments, collector, selfRef: target, source: joined, ...dialect.name === "mysql" ? { qualifyWrite: target } : {}, ...params.earlier === undefined ? {} : { steps: params.earlier } });
5424
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
+ }
5425
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()}`;
5426
- 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 } : {} });
5427
6095
  return { text: text + returning.suffix, values: collector.values, returning: returning.status };
5428
6096
  }
5429
6097
  const setSql = renderAssignments({ dialect, entries: assignments, collector, ...params.earlier === undefined ? {} : { steps: params.earlier } });
@@ -5431,7 +6099,7 @@ function renderUpdate(params) {
5431
6099
  if (statement.where !== undefined) {
5432
6100
  text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
5433
6101
  }
5434
- const returning = applyReturning({ dialect, requested: statement.returning });
6102
+ const returning = applyReturning({ dialect, requested: statement.returning, collector });
5435
6103
  return { text: text + returning.suffix, values: collector.values, returning: returning.status };
5436
6104
  }
5437
6105
  function renderIncomingRowColumn(params) {
@@ -5447,23 +6115,23 @@ function renderAssignments(params) {
5447
6115
  const read = selfRef === undefined ? column : `${selfRef}.${column}`;
5448
6116
  if (isSourceColumnRef(value)) {
5449
6117
  if (params.source === undefined)
5450
- 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` });
5451
6119
  return `${column} = ${params.source}.${dialect.quoteIdent({ name: value.column })}`;
5452
6120
  }
5453
6121
  if (isStepColumnRef(value)) {
5454
6122
  if (params.steps === undefined || !params.steps.has(value.step)) {
5455
- 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` });
5456
6124
  }
5457
6125
  return `${column} = (SELECT ${dialect.quoteIdent({ name: value.column })} FROM ${dialect.quoteIdent({ name: value.step })})`;
5458
6126
  }
5459
6127
  if (isSqlFragment(value)) {
5460
6128
  if (params.allowExcluded !== true || selfRef === undefined)
5461
- throw internalError4({ message: "SQL assignment fragments require a conflict update" });
6129
+ throw internalError5({ message: "SQL assignment fragments require a conflict update" });
5462
6130
  return `${column} = ${renderSqlFragment({ dialect, fragment: value, collector, conflict: { table: params.tableName } })}`;
5463
6131
  }
5464
6132
  if (isExcludedRef(value)) {
5465
6133
  if (params.allowExcluded !== true) {
5466
- throw internalError4({
6134
+ throw internalError5({
5467
6135
  message: `an incoming-row reference (${value.column}) is only meaningful inside an upsert's update arm`
5468
6136
  });
5469
6137
  }
@@ -5471,9 +6139,12 @@ function renderAssignments(params) {
5471
6139
  }
5472
6140
  if (isSqlKeepCell(value)) {
5473
6141
  if (params.allowExcluded !== true || selfRef === undefined)
5474
- 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" });
5475
6143
  return `${column} = ${read}`;
5476
6144
  }
6145
+ if (isJsonPatchOperation(value)) {
6146
+ return `${column} = ${renderJsonPatch({ dialect, patch: value, read, collector })}`;
6147
+ }
5477
6148
  if (isJsonParamCell(value)) {
5478
6149
  return `${column} = ${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
5479
6150
  }
@@ -5501,7 +6172,7 @@ function renderDelete(params) {
5501
6172
  if (statement.where !== undefined) {
5502
6173
  text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
5503
6174
  }
5504
- const returning = applyReturning({ dialect, requested: statement.returning });
6175
+ const returning = applyReturning({ dialect, requested: statement.returning, collector });
5505
6176
  return { text: text + returning.suffix, values: collector.values, returning: returning.status };
5506
6177
  }
5507
6178
  var CHAIN_ROWS_ALIAS = "__vibe_rows";
@@ -5509,19 +6180,19 @@ var CHAIN_ONE_ALIAS = "__vibe_one";
5509
6180
  function renderChain(params) {
5510
6181
  const { dialect, statement, collector } = params;
5511
6182
  if (dialect.statementChain === null) {
5512
- throw unsupportedCapability28({
6183
+ throw unsupportedCapability29({
5513
6184
  dialect: dialect.name,
5514
6185
  feature: "a statement chain (data-modifying WITH steps)",
5515
6186
  hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
5516
6187
  });
5517
6188
  }
5518
6189
  if (statement.steps.length === 0)
5519
- throw internalError4({ message: "a statement chain needs at least one step" });
6190
+ throw internalError5({ message: "a statement chain needs at least one step" });
5520
6191
  const defined = new Set;
5521
6192
  const steps = [];
5522
6193
  for (const step of statement.steps) {
5523
6194
  if (step.name === CHAIN_ROWS_ALIAS || step.name === CHAIN_ONE_ALIAS || defined.has(step.name)) {
5524
- 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` });
5525
6196
  }
5526
6197
  const body = renderChainStep({ dialect, statement: step.statement, collector, earlier: defined });
5527
6198
  steps.push(`${dialect.quoteIdent({ name: step.name })} AS (${body})`);
@@ -5530,11 +6201,11 @@ function renderChain(params) {
5530
6201
  const result = statement.result;
5531
6202
  const captures = result.capture ?? [];
5532
6203
  if (captures.length > 0 && !dialect.capabilities.rowCapture) {
5533
- 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" });
5534
6205
  }
5535
6206
  for (const alias of captureAliases({ captures })) {
5536
6207
  if (result.columns.includes(alias) || alias === result.verdict?.alias || alias === result.tally?.alias) {
5537
- 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` });
5538
6209
  }
5539
6210
  }
5540
6211
  if (result.rowsFrom.length === 0 && result.columns.length === 0 && result.tally !== undefined && result.verdict === undefined) {
@@ -5542,11 +6213,11 @@ function renderChain(params) {
5542
6213
  return { text: `WITH ${steps.join(", ")} SELECT ${[tally, ...renderChainCaptures({ dialect, captures, defined, collector })].join(", ")}`, values: collector.values, returning: "none" };
5543
6214
  }
5544
6215
  if (result.rowsFrom.length === 0 || result.columns.length === 0) {
5545
- 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" });
5546
6217
  }
5547
6218
  for (const name of result.rowsFrom) {
5548
6219
  if (!defined.has(name))
5549
- throw internalError4({ message: `chain result reads undefined step "${name}"` });
6220
+ throw internalError5({ message: `chain result reads undefined step "${name}"` });
5550
6221
  }
5551
6222
  const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
5552
6223
  const guarded = result.verdict !== undefined || result.tally !== undefined || result.veto !== undefined && result.veto.length > 0;
@@ -5557,7 +6228,7 @@ function renderChain(params) {
5557
6228
  const extra = [];
5558
6229
  for (const alias of [result.verdict?.alias, result.tally?.alias]) {
5559
6230
  if (alias !== undefined && result.columns.includes(alias)) {
5560
- 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` });
5561
6232
  }
5562
6233
  }
5563
6234
  const rows = dialect.quoteIdent({ name: CHAIN_ROWS_ALIAS });
@@ -5602,6 +6273,8 @@ function captureAliases(params) {
5602
6273
  if (entry.inserted !== undefined)
5603
6274
  out.push(`${entry.alias}_ia`);
5604
6275
  }
6276
+ for (const index of (entry.cascades ?? []).keys())
6277
+ out.push(`${entry.alias}_c${index}`);
5605
6278
  }
5606
6279
  return out;
5607
6280
  }
@@ -5611,7 +6284,7 @@ function renderChainCaptures(params) {
5611
6284
  const out = [];
5612
6285
  for (const entry of captures) {
5613
6286
  if (!defined.has(entry.step))
5614
- throw internalError4({ message: `chain capture reads undefined step "${entry.step}"` });
6287
+ throw internalError5({ message: `chain capture reads undefined step "${entry.step}"` });
5615
6288
  const step = quote(entry.step);
5616
6289
  const marker = entry.inserted === undefined ? undefined : quote(entry.inserted);
5617
6290
  out.push(`(SELECT count(*) FROM ${step}) AS ${quote(`${entry.alias}_n`)}`);
@@ -5619,23 +6292,36 @@ function renderChainCaptures(params) {
5619
6292
  out.push(`(SELECT count(*) FROM ${step} WHERE ${marker}) AS ${quote(`${entry.alias}_i`)}`);
5620
6293
  const before = entry.before ?? [];
5621
6294
  const after = entry.after ?? [];
5622
- if (before.length === 0 && after.length === 0)
5623
- continue;
5624
- if (!Number.isSafeInteger(entry.limit) || entry.limit < 1)
5625
- throw internalError4({ message: `chain capture "${entry.alias}" needs a positive integer limit` });
5626
- const limit = collector.add(entry.limit);
5627
- const item = quote("__vibe_i");
5628
- 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")})`;
5629
- const updatedRows = marker === undefined ? "" : ` WHERE NOT ${marker}`;
5630
- if (before.length > 0)
5631
- out.push(`${image(before, updatedRows)} AS ${quote(`${entry.alias}_b`)}`);
5632
- if (after.length > 0 && (marker === undefined || entry.insertedOnly !== true))
5633
- out.push(`${image(after, updatedRows)} AS ${quote(`${entry.alias}_a`)}`);
5634
- if (after.length > 0 && marker !== undefined)
5635
- 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
+ }
5636
6302
  }
5637
6303
  return out;
5638
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
+ }
5639
6325
  function renderSelectCell(params) {
5640
6326
  const { dialect, value, collector } = params;
5641
6327
  if (isJsonParamCell(value))
@@ -5643,21 +6329,21 @@ function renderSelectCell(params) {
5643
6329
  if (isDbNowMarker(value))
5644
6330
  return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
5645
6331
  if (isSqlDefaultCell(value)) {
5646
- 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" });
5647
6333
  }
5648
6334
  return collector.add(value);
5649
6335
  }
5650
6336
  function renderRowSource(params) {
5651
6337
  const { dialect, source, collector } = params;
5652
6338
  if (source.columns.length === 0 || source.rows.length === 0) {
5653
- 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" });
5654
6340
  }
5655
6341
  const typedBy = renderTable(dialect, source.typedBy);
5656
6342
  const columns = source.columns.map((name) => dialect.quoteIdent({ name }));
5657
6343
  const rows = [`(${columns.map((column) => `(SELECT ${column} FROM ${typedBy} WHERE FALSE)`).join(", ")})`];
5658
6344
  for (const row of source.rows) {
5659
6345
  if (row.length !== source.columns.length) {
5660
- 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` });
5661
6347
  }
5662
6348
  rows.push(`(${row.map((value) => renderSelectCell({ dialect, value, collector })).join(", ")})`);
5663
6349
  }
@@ -5669,12 +6355,12 @@ function renderTallyColumn(params) {
5669
6355
  const { dialect, result, defined, collector } = params;
5670
6356
  const tally = result.tally;
5671
6357
  if (tally === undefined)
5672
- 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" });
5673
6359
  if (tally.steps.length === 0)
5674
- throw internalError4({ message: "a chain tally needs at least one step" });
6360
+ throw internalError5({ message: "a chain tally needs at least one step" });
5675
6361
  const step = (name) => {
5676
6362
  if (!defined.has(name))
5677
- throw internalError4({ message: `chain tally or veto reads undefined step "${name}"` });
6363
+ throw internalError5({ message: `chain tally or veto reads undefined step "${name}"` });
5678
6364
  return dialect.quoteIdent({ name });
5679
6365
  };
5680
6366
  const count = (name) => `(SELECT count(*) FROM ${step(name)})`;
@@ -5684,14 +6370,14 @@ function renderTallyColumn(params) {
5684
6370
  return `${sum} AS ${dialect.quoteIdent({ name: tally.alias })}`;
5685
6371
  const arms = vetoes.map((veto) => {
5686
6372
  if (!CHAIN_VETO_TOKEN_PATTERN.test(veto.token))
5687
- 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` });
5688
6374
  if (typeof veto.nonce !== "string" || veto.nonce === "")
5689
- 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` });
5690
6376
  const minRows = veto.minRows ?? 1;
5691
6377
  if (!Number.isSafeInteger(minRows) || minRows < 1)
5692
- 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` });
5693
6379
  if (veto.exactRows !== undefined && (!Number.isSafeInteger(veto.exactRows) || veto.exactRows < 0 || veto.minRows !== undefined)) {
5694
- 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` });
5695
6381
  }
5696
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}`;
5697
6383
  return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
@@ -5701,25 +6387,25 @@ function renderTallyColumn(params) {
5701
6387
  function renderInsertSelect(params) {
5702
6388
  const { dialect, statement, collector } = params;
5703
6389
  if (statement.columns.length === 0)
5704
- throw internalError4({ message: "insert requires at least one column" });
6390
+ throw internalError5({ message: "insert requires at least one column" });
5705
6391
  if (statement.cells.length !== statement.columns.length) {
5706
- throw internalError4({
6392
+ throw internalError5({
5707
6393
  message: `insert row has ${statement.cells.length} values but ${statement.columns.length} columns`
5708
6394
  });
5709
6395
  }
5710
6396
  const from = statement.from ?? [];
5711
6397
  for (const name of from) {
5712
6398
  if (!params.earlier.has(name))
5713
- 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` });
5714
6400
  }
5715
6401
  const source = statement.source;
5716
6402
  if (source !== undefined && (params.earlier.has(source.alias) || from.includes(source.alias))) {
5717
- 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` });
5718
6404
  }
5719
6405
  const cells = statement.cells.map((value) => {
5720
6406
  if (isStepColumnRef(value)) {
5721
6407
  if (!from.includes(value.step) && value.step !== source?.alias) {
5722
- 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` });
5723
6409
  }
5724
6410
  return `${dialect.quoteIdent({ name: value.step })}.${dialect.quoteIdent({ name: value.column })}`;
5725
6411
  }
@@ -5738,9 +6424,9 @@ function renderInsertSelect(params) {
5738
6424
  const conflict = statement.conflictDoNothing;
5739
6425
  if (conflict !== undefined) {
5740
6426
  if (conflict.columns.length === 0)
5741
- 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" });
5742
6428
  if (dialect.doNothingForm !== "onConflictDoNothing") {
5743
- throw unsupportedCapability28({
6429
+ throw unsupportedCapability29({
5744
6430
  dialect: dialect.name,
5745
6431
  feature: "ON CONFLICT DO NOTHING on an INSERT \u2026 SELECT chain step",
5746
6432
  hint: "gate on SqlDialect.statementChain and keep the multi-statement path"
@@ -5748,10 +6434,10 @@ function renderInsertSelect(params) {
5748
6434
  }
5749
6435
  text += ` ON CONFLICT (${conflict.columns.map((name) => dialect.quoteIdent({ name })).join(", ")}) DO NOTHING`;
5750
6436
  }
5751
- return text + applyReturning({ dialect, requested: statement.returning }).suffix;
6437
+ return text + applyReturning({ dialect, requested: statement.returning, collector }).suffix;
5752
6438
  }
5753
6439
  // packages/sql/src/dialects/postgres-type-identity.ts
5754
- import { unsupportedCapability as unsupportedCapability29 } from "@vibeorm/schema";
6440
+ import { unsupportedCapability as unsupportedCapability30 } from "@vibeorm/schema";
5755
6441
  var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
5756
6442
  var MODIFIER_VALUE = `(?:[+-]?\\d+|${IDENTIFIER}|'(?:[^'\\x00]|'')*')`;
5757
6443
  var TYPE_NAME = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*(\\(\\s*${MODIFIER_VALUE}(?:\\s*,\\s*${MODIFIER_VALUE})*\\s*\\))?\\s*((?:\\[\\s*\\]\\s*)*)$`);
@@ -5778,13 +6464,13 @@ function parsePostgresTypeName(params) {
5778
6464
  }
5779
6465
  function renderPostgresTypeName(params) {
5780
6466
  if (params.dialect.name !== "postgres")
5781
- throw unsupportedCapability29({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
6467
+ throw unsupportedCapability30({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
5782
6468
  const { dialect, type } = params;
5783
6469
  return `${type.namespace === undefined ? "" : `${dialect.quoteIdent({ name: type.namespace })}.`}${dialect.quoteIdent({ name: type.name })}${type.modifier}${type.array ? "[]" : ""}`;
5784
6470
  }
5785
6471
  function renderPostgresTypeIdentity(params) {
5786
6472
  if (params.dialect.name !== "postgres")
5787
- throw unsupportedCapability29({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
6473
+ throw unsupportedCapability30({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
5788
6474
  const collector = new ParamCollector(params.dialect);
5789
6475
  const { source } = params;
5790
6476
  let text;
@@ -5797,11 +6483,11 @@ function renderPostgresTypeIdentity(params) {
5797
6483
  return { text, values: collector.values, returning: "none" };
5798
6484
  }
5799
6485
  // packages/sql/src/dialects/postgres-default-identity.ts
5800
- import { unsupportedCapability as unsupportedCapability30 } from "@vibeorm/schema";
6486
+ import { unsupportedCapability as unsupportedCapability31 } from "@vibeorm/schema";
5801
6487
  function renderPostgresDefaultFunctionIdentity(params) {
5802
6488
  const { dialect, table, column } = params;
5803
6489
  if (dialect.name !== "postgres")
5804
- throw unsupportedCapability30({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
6490
+ throw unsupportedCapability31({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
5805
6491
  const collector = new ParamCollector(dialect);
5806
6492
  const namespace = collector.add(table.namespace);
5807
6493
  const name = collector.add(table.name);
@@ -5825,28 +6511,28 @@ WHERE n.nspname = ${namespace} AND c.relname = ${name} AND a.attname = ${field}
5825
6511
  };
5826
6512
  }
5827
6513
  // packages/sql/src/dialects/constraint-owned-index.ts
5828
- import { unsupportedCapability as unsupportedCapability31 } from "@vibeorm/schema";
6514
+ import { unsupportedCapability as unsupportedCapability32 } from "@vibeorm/schema";
5829
6515
  function renderConstraintOwnedIndexDrop(params) {
5830
6516
  if (params.dialect.name !== "postgres")
5831
- throw unsupportedCapability31({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
6517
+ throw unsupportedCapability32({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
5832
6518
  const quote = (name) => params.dialect.quoteIdent({ name });
5833
6519
  return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
5834
6520
  }
5835
6521
  // packages/sql/src/dialects/mysql-check-probe.ts
5836
- import { unsupportedCapability as unsupportedCapability32 } from "@vibeorm/schema";
6522
+ import { unsupportedCapability as unsupportedCapability33 } from "@vibeorm/schema";
5837
6523
  function renderMysqlCheckProbe(params) {
5838
6524
  if (params.dialect.name !== "mysql")
5839
- throw unsupportedCapability32({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
6525
+ throw unsupportedCapability33({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
5840
6526
  const q = (name) => params.dialect.quoteIdent({ name });
5841
6527
  const probe = q(params.probe);
5842
6528
  switch (params.operation) {
5843
6529
  case "clone":
5844
6530
  if (params.table === undefined)
5845
- 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" });
5846
6532
  return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
5847
6533
  case "add": {
5848
6534
  if (params.expression === undefined || params.definition === undefined)
5849
- 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" });
5850
6536
  const parts = tableDefinitionParts({ sql: params.definition, backslashEscapes: true });
5851
6537
  return { text: `CREATE TEMPORARY TABLE ${probe} (
5852
6538
  ${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
@@ -5892,33 +6578,33 @@ function sqliteTableConstraints(params) {
5892
6578
  return result;
5893
6579
  }
5894
6580
  // packages/sql/src/dialects/migration-visibility.ts
5895
- import { unsupportedCapability as unsupportedCapability34 } from "@vibeorm/schema";
6581
+ import { unsupportedCapability as unsupportedCapability35 } from "@vibeorm/schema";
5896
6582
 
5897
6583
  // packages/sql/src/dialects/migration-inventory.ts
5898
- import { unsupportedCapability as unsupportedCapability33 } from "@vibeorm/schema";
6584
+ import { unsupportedCapability as unsupportedCapability34 } from "@vibeorm/schema";
5899
6585
  var MYSQL_NO_ROW_LIMIT = " LIMIT 18446744073709551615";
5900
6586
  function renderSqliteColumnInventory(params) {
5901
6587
  if (params.dialect.name !== "sqlite")
5902
- throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
6588
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
5903
6589
  const collector = new ParamCollector(params.dialect);
5904
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" };
5905
6591
  }
5906
6592
  function renderSqliteForeignKeyInventory(params) {
5907
6593
  if (params.dialect.name !== "sqlite")
5908
- throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
6594
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
5909
6595
  const collector = new ParamCollector(params.dialect);
5910
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" };
5911
6597
  }
5912
6598
  function renderSqliteColumnInventoryBatch(params) {
5913
6599
  if (params.dialect.name !== "sqlite")
5914
- throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
6600
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
5915
6601
  const collector = new ParamCollector(params.dialect);
5916
6602
  const request = params.dialect.quoteIdent({ name: "vibe_request" });
5917
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" };
5918
6604
  }
5919
6605
  function renderSqliteForeignKeyInventoryBatch(params) {
5920
6606
  if (params.dialect.name !== "sqlite")
5921
- throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
6607
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
5922
6608
  const collector = new ParamCollector(params.dialect);
5923
6609
  const request = params.dialect.quoteIdent({ name: "vibe_request" });
5924
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" };
@@ -5931,15 +6617,15 @@ var SQLITE_INTROSPECTION_PRAGMA_ORDER = Object.freeze({
5931
6617
  });
5932
6618
  function renderSqliteIntrospectionPragmaBatch(params) {
5933
6619
  if (params.dialect.name !== "sqlite")
5934
- throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteIntrospectionPragmaBatch" });
6620
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteIntrospectionPragmaBatch" });
5935
6621
  const order = SQLITE_INTROSPECTION_PRAGMA_ORDER[params.pragma];
5936
6622
  if (order === undefined)
5937
- throw unsupportedCapability33({ dialect: "sqlite", feature: `introspectionPragma:${String(params.pragma)}` });
6623
+ throw unsupportedCapability34({ dialect: "sqlite", feature: `introspectionPragma:${String(params.pragma)}` });
5938
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" };
5939
6625
  }
5940
6626
  function renderSqliteRebuildObjects(params) {
5941
6627
  if (params.dialect.name !== "sqlite")
5942
- throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
6628
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
5943
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" };
5944
6630
  }
5945
6631
  function renderMigrationInventory(params) {
@@ -5981,13 +6667,13 @@ ORDER BY kind, parent, name${MYSQL_NO_ROW_LIMIT}`, values: collector.values, ret
5981
6667
  // packages/sql/src/dialects/migration-visibility.ts
5982
6668
  function renderMigrationVisibility(params) {
5983
6669
  if (params.dialect.name !== "mysql")
5984
- throw unsupportedCapability34({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
6670
+ throw unsupportedCapability35({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
5985
6671
  const collector = new ParamCollector(params.dialect);
5986
6672
  const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
5987
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" };
5988
6674
  }
5989
6675
  // packages/sql/src/dialects/migration-objects.ts
5990
- import { internalError as internalError5, unsupportedCapability as unsupportedCapability35 } from "@vibeorm/schema";
6676
+ import { internalError as internalError6, unsupportedCapability as unsupportedCapability36 } from "@vibeorm/schema";
5991
6677
  function renderMigrationObjectQuery(params) {
5992
6678
  return objectQuery({ ...params, collector: new ParamCollector(params.dialect) });
5993
6679
  }
@@ -5995,7 +6681,7 @@ function objectQuery(params) {
5995
6681
  const { dialect, identity: object, collector } = params;
5996
6682
  if (object.kind === "namespace") {
5997
6683
  if (dialect.name !== "postgres")
5998
- throw unsupportedCapability35({ dialect: dialect.name, feature: "fixedNamespaces" });
6684
+ throw unsupportedCapability36({ dialect: dialect.name, feature: "fixedNamespaces" });
5999
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" };
6000
6686
  }
6001
6687
  const physical = "table" in object ? object.table : object.name;
@@ -6043,18 +6729,18 @@ function objectQuery(params) {
6043
6729
  }
6044
6730
  } else if (dialect.name === "mysql") {
6045
6731
  if (object.kind === "constraint" && object.constraintType === "exclusion")
6046
- throw unsupportedCapability35({ dialect: "mysql", feature: "exclusionConstraints" });
6732
+ throw unsupportedCapability36({ dialect: "mysql", feature: "exclusionConstraints" });
6047
6733
  const mysql = mysqlFactsText({ dialect, identity: object, namespace, name, collector });
6048
6734
  if (mysql === undefined)
6049
- throw unsupportedCapability35({ dialect: "mysql", feature: `inspect:${object.kind}` });
6735
+ throw unsupportedCapability36({ dialect: "mysql", feature: `inspect:${object.kind}` });
6050
6736
  text = mysql;
6051
6737
  } else {
6052
6738
  if (physical.namespace !== "main" && physical.namespace !== "temp")
6053
- throw unsupportedCapability35({ dialect: "sqlite", feature: "attachedSchemaInspection" });
6739
+ throw unsupportedCapability36({ dialect: "sqlite", feature: "attachedSchemaInspection" });
6054
6740
  const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
6055
6741
  const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
6056
6742
  if (["function", "sequence", "postgresExtension", "enum"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
6057
- throw unsupportedCapability35({ dialect: "sqlite", feature: `inspect:${object.kind}` });
6743
+ throw unsupportedCapability36({ dialect: "sqlite", feature: `inspect:${object.kind}` });
6058
6744
  const sqliteParams = new ParamCollector(dialect);
6059
6745
  if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
6060
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)}`;
@@ -6129,7 +6815,7 @@ function mysqlBatches(params) {
6129
6815
  function renderMysqlObjectBatch(params) {
6130
6816
  const { dialect } = params;
6131
6817
  if (dialect.name !== "mysql")
6132
- throw unsupportedCapability35({ dialect: dialect.name, feature: "batchedMigrationInspection" });
6818
+ throw unsupportedCapability36({ dialect: dialect.name, feature: "batchedMigrationInspection" });
6133
6819
  const position = dialect.quoteIdent({ name: "vibe_position" });
6134
6820
  const groups = new Map;
6135
6821
  for (const [index, identity] of params.identities.entries()) {
@@ -6149,7 +6835,7 @@ function renderMysqlObjectBatch(params) {
6149
6835
  function renderMysqlObjectMembersBatch(params) {
6150
6836
  const { dialect } = params;
6151
6837
  if (dialect.name !== "mysql")
6152
- throw unsupportedCapability35({ dialect: dialect.name, feature: "batchedMigrationInspection" });
6838
+ throw unsupportedCapability36({ dialect: dialect.name, feature: "batchedMigrationInspection" });
6153
6839
  const position = dialect.quoteIdent({ name: "vibe_position" });
6154
6840
  const ordinal = dialect.quoteIdent({ name: "vibe_ordinal" });
6155
6841
  const groups = new Map;
@@ -6169,7 +6855,7 @@ function renderMigrationDependencies(params) {
6169
6855
  }
6170
6856
  function dependencyQuery(params) {
6171
6857
  if (params.dialect.name !== "postgres")
6172
- throw unsupportedCapability35({ dialect: params.dialect.name, feature: "catalogDependencies" });
6858
+ throw unsupportedCapability36({ dialect: params.dialect.name, feature: "catalogDependencies" });
6173
6859
  const object = params.identity;
6174
6860
  const collector = params.collector;
6175
6861
  if (object.kind === "namespace") {
@@ -6226,7 +6912,7 @@ function inspectionReferences(params) {
6226
6912
  function renderMigrationObjectBatch(params) {
6227
6913
  const { dialect } = params;
6228
6914
  if (dialect.name !== "postgres")
6229
- throw unsupportedCapability35({ dialect: dialect.name, feature: "batchedMigrationInspection" });
6915
+ throw unsupportedCapability36({ dialect: dialect.name, feature: "batchedMigrationInspection" });
6230
6916
  const groups = new Map;
6231
6917
  for (const [position, identity] of params.identities.entries()) {
6232
6918
  const kind = identity.kind === "constraint" ? `${identity.kind}:${identity.constraintType}` : identity.kind === "generatedColumn" ? "columnDefault" : identity.kind;
@@ -6239,7 +6925,7 @@ function renderMigrationObjectBatch(params) {
6239
6925
  const facts = objectQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
6240
6926
  const dependencies = dependencyQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
6241
6927
  if (JSON.stringify(dependencies.values) !== JSON.stringify(facts.values))
6242
- 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 } });
6243
6929
  const columns = facts.values.map((value, index) => `${dialect.quoteIdent({ name: `p${index + 1}` })} ${Array.isArray(value) ? "text[]" : "text"}`);
6244
6930
  const requests = group.map(({ identity, position }) => ({ position, ...Object.fromEntries(renderMigrationObjectQuery({ dialect, identity }).values.map((value, index) => [`p${index + 1}`, value])) }));
6245
6931
  const collector = new ParamCollector(dialect);
@@ -6259,16 +6945,16 @@ function renderMigrationNamespace(params) {
6259
6945
  }
6260
6946
  function renderCheckProbe(params) {
6261
6947
  if (params.dialect.name !== "postgres")
6262
- throw unsupportedCapability35({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
6948
+ throw unsupportedCapability36({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
6263
6949
  const q = (name) => params.dialect.quoteIdent({ name });
6264
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" };
6265
6951
  }
6266
6952
  // packages/sql/src/dialects/postgres-migration.ts
6267
- import { VibeError as VibeError24, unsupportedCapability as unsupportedCapability36 } from "@vibeorm/schema";
6953
+ import { VibeError as VibeError25, unsupportedCapability as unsupportedCapability37 } from "@vibeorm/schema";
6268
6954
  var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
6269
6955
  function requirePostgres2(params) {
6270
6956
  if (params.dialect.name !== "postgres") {
6271
- throw unsupportedCapability36({ dialect: params.dialect.name, feature: params.feature });
6957
+ throw unsupportedCapability37({ dialect: params.dialect.name, feature: params.feature });
6272
6958
  }
6273
6959
  }
6274
6960
  function renderMigrationSessionRead(params) {
@@ -6283,7 +6969,7 @@ function renderMigrationSessionSet(params) {
6283
6969
  requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
6284
6970
  const { milliseconds, setting } = params;
6285
6971
  if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
6286
- throw new VibeError24({
6972
+ throw new VibeError25({
6287
6973
  code: "VIBE_MIGRATION",
6288
6974
  message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
6289
6975
  meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
@@ -6378,34 +7064,45 @@ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${table} AND a.att
6378
7064
  export {
6379
7065
  ADVISORY_KEY_BOUNDS,
6380
7066
  ADVISORY_LOCK_RESULT_COLUMN,
7067
+ AUTOMATIC_SHADOW_NAME_PATTERN,
6381
7068
  CHAIN_VETO_PREFIX,
6382
7069
  CHAIN_VETO_TOKEN_PATTERN,
7070
+ COALESCE_REF_NAME,
6383
7071
  DIALECT_CAPABILITIES,
6384
7072
  DIALECT_CODECS,
6385
7073
  JSON_TRANSPORT,
6386
7074
  MODULE_JOURNAL_COLUMN,
6387
7075
  MYSQL_CAPABILITIES,
6388
7076
  MYSQL_CODECS,
7077
+ MYSQL_SCALAR_COLUMN_TYPES,
6389
7078
  NATIVE_RLS,
6390
7079
  NATIVE_RLS_PROBE_POLICY,
6391
7080
  POSTGRES_CAPABILITIES,
6392
7081
  POSTGRES_CODECS,
6393
7082
  POSTGRES_DIAGNOSTIC_SQL,
7083
+ POSTGRES_EXACT_EQUIVALENTS,
7084
+ POSTGRES_NATIVE_ALIASES,
7085
+ POSTGRES_SCALAR_COLUMN_TYPES,
6394
7086
  POSTGRES_WORKLOAD_LIMITS,
6395
7087
  ParamCollector,
6396
7088
  RLS_READINESS_SQL,
6397
7089
  SHADOW_CAPABILITIES,
6398
7090
  SHADOW_DATABASE,
7091
+ SHADOW_MARKER_CAPABILITIES,
6399
7092
  SQLITE_CAPABILITIES,
6400
7093
  SQLITE_CODECS,
7094
+ SQLITE_SCALAR_STORAGE_CLASSES,
6401
7095
  SQLITE_SESSION_SQL,
6402
7096
  SQL_DEFAULT_CELL,
6403
7097
  SQL_DIALECTS,
6404
7098
  SQL_KEEP_CELL,
6405
7099
  assertIndexExpressionVersion,
6406
7100
  assertSqlParamBuilding,
7101
+ automaticShadowMarkerText,
7102
+ canonicalColumnType,
6407
7103
  chainVetoToken,
6408
7104
  cloneTimestampDate,
7105
+ coalesceRef,
6409
7106
  compareTimestampDates,
6410
7107
  compileExclusion,
6411
7108
  compileReusableSqlCommand,
@@ -6442,10 +7139,13 @@ export {
6442
7139
  isFixedDiagnostic,
6443
7140
  isJsonListParameter,
6444
7141
  isJsonParamCell,
7142
+ isJsonPatchOperation,
6445
7143
  isListSetOperation,
6446
7144
  isNativeEnumArrayParameter,
7145
+ isOtherRowReturningColumn,
6447
7146
  isReusableSqlCommand,
6448
7147
  isReusableSqlQuery,
7148
+ isRollupChain,
6449
7149
  isSetOperation,
6450
7150
  isSourceColumnRef,
6451
7151
  isSqlBindingCollector,
@@ -6459,17 +7159,22 @@ export {
6459
7159
  isTimeZoneName,
6460
7160
  jsonListParameter,
6461
7161
  jsonParamCell,
7162
+ jsonPatchVetoOf,
6462
7163
  keyListBindsUntypedRows,
6463
7164
  localDateCodec,
6464
7165
  mapConflictColumns,
6465
7166
  mysqlDialect,
6466
7167
  nativeEnumArrayParameter,
7168
+ normalizeMysqlNativeType,
7169
+ normalizePostgresNativeType,
7170
+ parseAutomaticShadowMarker,
6467
7171
  parseDateTimeText,
6468
7172
  parseExtendedDateTimeText,
6469
7173
  parseJsonText,
6470
7174
  parsePgArrayLiteral,
6471
7175
  parsePgArrayLiteralNested,
6472
7176
  parsePostgresTypeName,
7177
+ planJsonPatch,
6473
7178
  postgresArrayElementFromText,
6474
7179
  postgresBulkInsertArrayCastOf,
6475
7180
  postgresDialect,
@@ -6480,6 +7185,7 @@ export {
6480
7185
  registerSqlHandleCheck,
6481
7186
  renderAdvisoryLock,
6482
7187
  renderAdvisoryLocks,
7188
+ renderAutomaticShadowOperation,
6483
7189
  renderCaptureReadiness,
6484
7190
  renderCheckProbe,
6485
7191
  renderColumnStateQuery,
@@ -6587,4 +7293,4 @@ export {
6587
7293
  toPgArrayLiteral
6588
7294
  };
6589
7295
 
6590
- //# debugId=0EA38AD633C52F8C64756E2164756E21
7296
+ //# debugId=B21A2ED0EE62B5B964756E2164756E21