@vibeorm/sql 4.1.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,16 +1,27 @@
1
1
  // @bun
2
2
  // packages/sql/src/timestamp-identity.ts
3
3
  var identities = new WeakMap;
4
+ function canonicalKey(params) {
5
+ const fraction = /\.(\d+)/.exec(params.text)?.[1] ?? "";
6
+ const remainder = fraction.slice(3).replace(/0+$/, "");
7
+ const canonical = Date.prototype.toISOString.call(new Date(params.milliseconds)).replace(/(\.\d{3})Z$/, `$1${remainder}Z`);
8
+ return `date:${canonical}`;
9
+ }
10
+ function identityKey(identity) {
11
+ if (identity.key === undefined)
12
+ identity.key = canonicalKey({ milliseconds: identity.milliseconds, text: identity.text });
13
+ return identity.key;
14
+ }
4
15
  function rememberTimestampIdentity(params) {
5
16
  const { date, text } = params;
6
- const fraction = /\.(\d+)/.exec(text)?.[1] ?? "";
7
- const remainder = fraction.slice(3).replace(/0+$/, "");
8
- const canonical = Date.prototype.toISOString.call(date).replace(/(\.\d{3})Z$/, `$1${remainder}Z`);
17
+ const milliseconds = Date.prototype.getTime.call(date);
18
+ if (!Number.isFinite(milliseconds))
19
+ Date.prototype.toISOString.call(date);
9
20
  identities.set(date, {
10
- milliseconds: Date.prototype.getTime.call(date),
21
+ milliseconds,
11
22
  text,
12
- ...params.numeric === undefined ? {} : { numeric: params.numeric },
13
- key: params.key ?? `date:${canonical}`,
23
+ numeric: params.numeric,
24
+ key: params.key,
14
25
  kind: params.kind ?? "timestamp"
15
26
  });
16
27
  return date;
@@ -24,7 +35,8 @@ function identityOf(params) {
24
35
  return stored;
25
36
  }
26
37
  function timestampIdentityKey(params) {
27
- return identityOf(params)?.key ?? `date:${Number.isFinite(Date.prototype.getTime.call(params.date)) ? Date.prototype.toISOString.call(params.date) : "invalid"}`;
38
+ const identity = identityOf(params);
39
+ return identity !== undefined ? identityKey(identity) : `date:${Number.isFinite(Date.prototype.getTime.call(params.date)) ? Date.prototype.toISOString.call(params.date) : "invalid"}`;
28
40
  }
29
41
  function timestampIdentityText(params) {
30
42
  const identity = identityOf(params);
@@ -44,7 +56,7 @@ function compareTimestampDates(params) {
44
56
  return left?.numeric !== undefined && right?.numeric !== undefined ? rank(left.numeric, right.numeric) : null;
45
57
  }
46
58
  const clock = (identity) => {
47
- const [, hours, minutes, seconds, offset] = identity.key.split(":");
59
+ const [, hours, minutes, seconds, offset] = identityKey(identity).split(":");
48
60
  const [whole, fraction = ""] = seconds.split(".");
49
61
  const local = BigInt(Number(hours) * 3600 + Number(minutes) * 60 + Number(whole)) * 1000000000n + BigInt(fraction.padEnd(9, "0"));
50
62
  const zone = Number(offset);
@@ -142,39 +154,43 @@ function toPgArrayLiteral(params) {
142
154
  }
143
155
  function parsePgArrayLiteral(params) {
144
156
  const literal = params.literal.startsWith("[") ? params.literal.replace(/^\[-?\d+:-?\d+\]=/, "") : params.literal;
145
- if (literal.length < 2 || !literal.startsWith("{") || !literal.endsWith("}"))
157
+ if (literal.length < 2 || literal.charCodeAt(0) !== 123 || literal.charCodeAt(literal.length - 1) !== 125)
146
158
  return null;
147
- const inner = literal.slice(1, -1);
148
- if (inner.length === 0)
159
+ const end = literal.length - 1;
160
+ if (end === 1)
149
161
  return [];
150
162
  const out = [];
151
- let index = 0;
152
- while (index < inner.length) {
153
- if (inner[index] === '"') {
163
+ let index = 1;
164
+ while (index < end) {
165
+ if (literal.charCodeAt(index) === 34) {
154
166
  let text = "";
155
167
  index++;
156
- while (index < inner.length) {
157
- const char = inner[index];
158
- if (char === "\\") {
159
- text += inner[index + 1] ?? "";
168
+ let run = index;
169
+ while (index < end) {
170
+ const code = literal.charCodeAt(index);
171
+ if (code === 92) {
172
+ text += literal.slice(run, index) + (index + 1 < end ? literal[index + 1] : "");
160
173
  index += 2;
174
+ run = index;
161
175
  continue;
162
176
  }
163
- index++;
164
- if (char === '"')
177
+ if (code === 34)
165
178
  break;
166
- text += char;
179
+ index++;
167
180
  }
181
+ text += literal.slice(run, Math.min(index, end));
182
+ if (index < end)
183
+ index++;
168
184
  out.push(text);
169
185
  } else {
170
- let text = "";
171
- while (index < inner.length && inner[index] !== ",") {
172
- text += inner[index];
173
- index++;
174
- }
186
+ let stop = literal.indexOf(",", index);
187
+ if (stop === -1 || stop > end)
188
+ stop = end;
189
+ const text = literal.slice(index, stop);
175
190
  out.push(text === "NULL" ? null : text);
191
+ index = stop;
176
192
  }
177
- if (inner[index] === ",")
193
+ if (literal.charCodeAt(index) === 44)
178
194
  index++;
179
195
  }
180
196
  return out;
@@ -235,7 +251,7 @@ function parseDimension(params) {
235
251
  }
236
252
 
237
253
  // packages/sql/src/codecs.ts
238
- import { VibeError, invalidStoredData, timeOfDayType } from "@vibeorm/schema";
254
+ import { VibeError, invalidStoredData, isCanonicalLocalDate, timeOfDayType } from "@vibeorm/schema";
239
255
  function enumMembershipCodec(params) {
240
256
  const { model, field, enum: enumName } = params;
241
257
  const members = new Set(params.members);
@@ -303,20 +319,33 @@ var ZONE_LESS_DATETIME = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9}
303
319
  var PG_ZONED_DATETIME = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?)(\.\d{1,9})?([+-]\d{2})(?::?(\d{2}))?$/;
304
320
  function parseDateTimeText(params) {
305
321
  const trimmed = params.text.trim();
322
+ const date = parseDateTimeInstant({ trimmed });
323
+ return date === undefined ? undefined : rememberTimestampIdentity({ date, text: trimmed });
324
+ }
325
+ function parseDateTimeInstant(params) {
326
+ const { trimmed } = params;
327
+ const length = trimmed.length;
328
+ if (length > 0 && trimmed.charCodeAt(length - 1) === 90 || (length === 25 || length === 29 && trimmed.charCodeAt(19) === 46) && trimmed.charCodeAt(10) === 84 && trimmed.charCodeAt(length - 3) === 58 && (trimmed.charCodeAt(length - 6) === 43 || trimmed.charCodeAt(length - 6) === 45)) {
329
+ const parsed = new Date(trimmed);
330
+ return Number.isNaN(parsed.getTime()) ? undefined : parsed;
331
+ }
306
332
  if (ZONE_LESS_DATETIME.test(trimmed)) {
307
333
  const asUtc = new Date(`${trimmed.replace(" ", "T").replace(/(\.\d{3})\d+$/, "$1")}Z`);
308
334
  if (!Number.isNaN(asUtc.getTime()))
309
- return rememberTimestampIdentity({ date: asUtc, text: trimmed });
335
+ return asUtc;
310
336
  }
311
337
  const zoned = PG_ZONED_DATETIME.exec(trimmed);
312
338
  if (zoned !== null) {
313
339
  const [, date, time, fraction, hours, minutes] = zoned;
314
340
  const iso = new Date(`${date}T${time}${(fraction ?? "").slice(0, 4)}${hours}:${minutes ?? "00"}`);
315
341
  if (!Number.isNaN(iso.getTime()))
316
- return rememberTimestampIdentity({ date: iso, text: trimmed });
342
+ return iso;
317
343
  }
318
344
  const parsed = new Date(trimmed);
319
- return Number.isNaN(parsed.getTime()) ? undefined : rememberTimestampIdentity({ date: parsed, text: trimmed });
345
+ return Number.isNaN(parsed.getTime()) ? undefined : parsed;
346
+ }
347
+ function invalidDecodedDateTime() {
348
+ throw new VibeError({ code: "VIBE_DATA_INVALID", message: "DateTime requires a finite timestamp; use an explicit extended timestamp decoder for infinity" });
320
349
  }
321
350
  var dateNativeCodec = {
322
351
  encode: (value) => {
@@ -329,7 +358,7 @@ var dateNativeCodec = {
329
358
  const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
330
359
  if (date !== undefined && Number.isFinite(date.getTime()))
331
360
  return date;
332
- throw new VibeError({ code: "VIBE_DATA_INVALID", message: "DateTime requires a finite timestamp; use an explicit extended timestamp decoder for infinity" });
361
+ return invalidDecodedDateTime();
333
362
  }
334
363
  };
335
364
  var dateTextCodec = {
@@ -341,10 +370,16 @@ var dateTextCodec = {
341
370
  throw new VibeError({ code: "VIBE_VALIDATION", message: "DateTime parameters require a valid finite timestamp" });
342
371
  },
343
372
  decode: (value) => {
373
+ if (typeof value === "string") {
374
+ const date = parseDateTimeInstant({ trimmed: value.trim() });
375
+ if (date === undefined || !Number.isFinite(date.getTime()))
376
+ return invalidDecodedDateTime();
377
+ return rememberTimestampIdentity({ date, text: value, key: `date:${value}`, kind: "sqlite" });
378
+ }
344
379
  const date = dateNativeCodec.decode(value);
345
380
  if (typeof value === "number")
346
381
  return rememberTimestampIdentity({ date, text: date.toISOString(), numeric: value, key: `epoch:${value}`, kind: "sqlite" });
347
- return typeof value === "string" ? rememberTimestampIdentity({ date, text: value, key: `date:${value}`, kind: "sqlite" }) : date;
382
+ return date;
348
383
  }
349
384
  };
350
385
  var mysqlDateCodec = {
@@ -416,6 +451,28 @@ function timeOfDayCodec(params) {
416
451
  const type = timeOfDayType({ nativeType: params.nativeType });
417
452
  return type === "timetz" ? timetzCodec : type === "time" ? timeCodec : undefined;
418
453
  }
454
+ var localDateCodec = Object.freeze({
455
+ encode: (value) => {
456
+ const candidate = { value };
457
+ if (isCanonicalLocalDate(candidate))
458
+ return candidate.value;
459
+ throw new VibeError({
460
+ code: "VIBE_VALIDATION",
461
+ message: "A local date requires canonical YYYY-MM-DD text for a real date in years 0001\u20139999",
462
+ meta: { value }
463
+ });
464
+ },
465
+ decode: (value) => {
466
+ const candidate = { value };
467
+ if (isCanonicalLocalDate(candidate))
468
+ return candidate.value;
469
+ throw new VibeError({
470
+ code: "VIBE_DATA_INVALID",
471
+ message: "A local-date column returned noncanonical or out-of-domain calendar-day text",
472
+ meta: { value }
473
+ });
474
+ }
475
+ });
419
476
  var booleanIntCodec = {
420
477
  encode: (value) => typeof value === "boolean" ? value ? 1 : 0 : value,
421
478
  decode: (value) => typeof value === "number" ? value !== 0 : value
@@ -1096,7 +1153,7 @@ function renderPostgresPhysicalCatalog(params) {
1096
1153
  text = `SELECT table_name FROM information_schema.views WHERE table_schema = ${namespace} AND ${extensionOwnedFilter} ORDER BY table_name`;
1097
1154
  break;
1098
1155
  case "columns":
1099
- text = `SELECT table_name, column_name, data_type, udt_name, udt_schema, is_nullable, is_identity, is_generated, collation_name, column_default, datetime_precision, character_maximum_length, numeric_precision, numeric_scale, ordinal_position, (SELECT format_type(a.atttypid, a.atttypmod) FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND c.relname = columns.table_name AND a.attname = columns.column_name) AS full_type FROM information_schema.columns AS columns WHERE table_schema = ${namespace} ORDER BY table_name, ordinal_position`;
1156
+ text = `SELECT table_name, column_name, data_type, udt_name, udt_schema, is_nullable, is_identity, is_generated, collation_name, column_default, datetime_precision, character_maximum_length, numeric_precision, numeric_scale, ordinal_position, catalog.full_type, catalog.generated_kind, catalog.generation_expression FROM information_schema.columns AS columns LEFT JOIN LATERAL (SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) AS full_type, a.attgenerated::text AS generated_kind, CASE WHEN a.attgenerated <> '' THEN pg_catalog.pg_get_expr(d.adbin, d.adrelid, false) ELSE NULL END AS generation_expression FROM pg_catalog.pg_attribute a JOIN pg_catalog.pg_class c ON c.oid = a.attrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace LEFT JOIN pg_catalog.pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum WHERE n.nspname = ${namespace} AND c.relname = columns.table_name AND a.attname = columns.column_name AND a.attnum > 0 AND NOT a.attisdropped) catalog ON true WHERE table_schema = ${namespace} ORDER BY table_name, ordinal_position`;
1100
1157
  break;
1101
1158
  case "constraints":
1102
1159
  text = `SELECT c.conname AS constraint_name, CASE c.contype WHEN 'p' THEN 'PRIMARY KEY' ELSE 'UNIQUE' END AS constraint_type, cl.relname AS table_name, att.attname AS column_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace JOIN unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord) ON true JOIN pg_catalog.pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = k.attnum WHERE c.contype IN ('p', 'u') AND n.nspname = ${namespace} ORDER BY cl.relname, c.conname, k.ord`;
@@ -1118,15 +1175,28 @@ function renderPostgresPhysicalCatalog(params) {
1118
1175
  }
1119
1176
  // packages/sql/src/dialects/postgres-index-keys.ts
1120
1177
  import { unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
1178
+ function renderPostgresJitSuspend(params) {
1179
+ if (params.dialect.name !== "postgres")
1180
+ throw unsupportedCapability6({ dialect: params.dialect.name, feature: "indexOperatorClasses" });
1181
+ return { text: "SELECT setting.previous, pg_catalog.set_config('jit', 'off', true) AS current FROM (SELECT pg_catalog.current_setting('jit') AS previous OFFSET 0) setting", values: [], returning: "none" };
1182
+ }
1183
+ function renderPostgresJitRestore(params) {
1184
+ if (params.dialect.name !== "postgres")
1185
+ throw unsupportedCapability6({ dialect: params.dialect.name, feature: "indexOperatorClasses" });
1186
+ const collector = new ParamCollector(params.dialect);
1187
+ return { text: `SELECT pg_catalog.set_config('jit', ${collector.add(params.previous)}, true) AS current`, values: collector.values, returning: "none" };
1188
+ }
1121
1189
  function renderPostgresIndexKeys(params) {
1122
1190
  const { dialect } = params;
1123
1191
  if (dialect.name !== "postgres")
1124
1192
  throw unsupportedCapability6({ dialect: dialect.name, feature: "indexOperatorClasses" });
1125
1193
  const collector = new ParamCollector(dialect);
1126
- const scope = params.table !== undefined ? `n.nspname = ${collector.add(params.table.namespace)} AND c.relname = ${collector.add(params.table.name)}` : params.namespaces === undefined ? "n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'" : params.namespaces.length === 0 ? "false" : `n.nspname IN (${[...new Set(params.namespaces)].map((namespace) => collector.add(namespace)).join(", ")})`;
1127
- const defaultResolution = `LEFT JOIN LATERAL (WITH RECURSIVE input_types AS (SELECT t.oid, t.typbasetype, t.typtype, t.typelem, t.typcategory FROM pg_catalog.pg_type t WHERE t.oid = a.atttypid UNION ALL SELECT t.oid, t.typbasetype, t.typtype, t.typelem, t.typcategory FROM pg_catalog.pg_type t JOIN input_types previous ON t.oid = previous.typbasetype WHERE previous.typtype = 'd'), candidates AS (SELECT candidate.oid, CASE WHEN candidate.opcintype = input.oid THEN 0 WHEN class_input.typispreferred AND class_input.typcategory = input.typcategory THEN 1 ELSE 2 END AS priority FROM pg_catalog.pg_opclass candidate JOIN pg_catalog.pg_type class_input ON class_input.oid = candidate.opcintype CROSS JOIN input_types input WHERE input.typtype <> 'd' AND candidate.opcdefault AND candidate.opcmethod = idx.relam AND (candidate.opcintype = input.oid OR EXISTS (SELECT 1 FROM pg_catalog.pg_cast cast_entry WHERE cast_entry.castsource = input.oid AND cast_entry.casttarget = candidate.opcintype AND cast_entry.castmethod = 'b' AND cast_entry.castcontext = 'i') OR input.typtype = 'e' AND candidate.opcintype = 'pg_catalog.anyenum'::regtype OR input.typelem <> 0 AND candidate.opcintype = 'pg_catalog.anyarray'::regtype)) SELECT (array_agg(candidate.oid))[1] AS oid FROM candidates candidate WHERE candidate.priority = (SELECT min(priority) FROM candidates) HAVING count(*) = 1) resolved_default ON true`;
1194
+ const scope = params.table !== undefined ? `${params.expressionMetadata && params.table.namespace === "pg_temp" ? "n.oid = pg_catalog.pg_my_temp_schema()" : `n.nspname = ${collector.add(params.table.namespace)}`} AND c.relname = ${collector.add(params.table.name)}` : params.namespaces === undefined ? "n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'" : params.namespaces.length === 0 ? "false" : `n.nspname IN (${[...new Set(params.namespaces)].map((namespace) => collector.add(namespace)).join(", ")})`;
1195
+ const defaultResolution = `LEFT JOIN LATERAL (WITH RECURSIVE input_types AS (SELECT t.oid, t.typbasetype, t.typtype, t.typelem, t.typcategory FROM pg_catalog.pg_type t WHERE t.oid = ${params.expressionMetadata ? "key_attribute.atttypid" : "a.atttypid"} UNION ALL SELECT t.oid, t.typbasetype, t.typtype, t.typelem, t.typcategory FROM pg_catalog.pg_type t JOIN input_types previous ON t.oid = previous.typbasetype WHERE previous.typtype = 'd'), candidates AS (SELECT candidate.oid, CASE WHEN candidate.opcintype = input.oid THEN 0 WHEN class_input.typispreferred AND class_input.typcategory = input.typcategory THEN 1 ELSE 2 END AS priority FROM pg_catalog.pg_opclass candidate JOIN pg_catalog.pg_type class_input ON class_input.oid = candidate.opcintype CROSS JOIN input_types input WHERE input.typtype <> 'd' AND candidate.opcdefault AND candidate.opcmethod = idx.relam AND (candidate.opcintype = input.oid OR EXISTS (SELECT 1 FROM pg_catalog.pg_cast cast_entry WHERE cast_entry.castsource = input.oid AND cast_entry.casttarget = candidate.opcintype AND cast_entry.castmethod = 'b' AND cast_entry.castcontext = 'i') OR input.typtype = 'e' AND candidate.opcintype = 'pg_catalog.anyenum'::regtype OR input.typelem <> 0 AND candidate.opcintype = 'pg_catalog.anyarray'::regtype)) SELECT (array_agg(candidate.oid))[1] AS oid FROM candidates candidate WHERE candidate.priority = (SELECT min(priority) FROM candidates) HAVING count(*) = 1) resolved_default ON true`;
1196
+ const expressionColumns = params.expressionMetadata ? `, CASE WHEN k.attnum = 0 THEN pg_catalog.pg_get_indexdef(i.indexrelid, k.ordinality::integer, false) ELSE NULL END AS expression, pg_catalog.pg_get_indexdef(i.indexrelid, 0, false) AS index_definition, pg_catalog.format_type(key_attribute.atttypid, key_attribute.atttypmod) AS result_type, (i.indoption[k.ordinality - 1] & 2) <> 0 AS nulls_first, ((i.indoption[k.ordinality - 1] & 2) <> 0) = ((i.indoption[k.ordinality - 1] & 1) <> 0) AS default_null_order, i.indcollation[k.ordinality - 1] AS collation_oid, CASE WHEN k.attnum = 0 THEN NULL ELSE i.indcollation[k.ordinality - 1] = a.attcollation END AS default_collation, i.indisvalid AS valid, i.indisready AS ready, i.indimmediate AS immediate, i.indnkeyatts AS key_count, i.indnatts AS attribute_count` : "";
1197
+ const expressionJoins = params.expressionMetadata ? ` JOIN pg_catalog.pg_attribute key_attribute ON key_attribute.attrelid = idx.oid AND key_attribute.attnum = k.ordinality` : "";
1128
1198
  return {
1129
- text: `SELECT n.nspname AS table_namespace, c.relname AS table_name, idx.relname AS index_name, am.amname AS method, i.indisunique AS unique, pg_catalog.pg_get_expr(i.indpred, i.indrelid, false) AS predicate, i.indnullsnotdistinct AS nulls_not_distinct, k.ordinality AS ordinal, a.attname AS column_name, (i.indoption[k.ordinality - 1] & 1) <> 0 AS descending, opn.nspname AS operator_namespace, op.opcname AS operator_name, COALESCE(op.oid = resolved_default.oid, false) AS exact_default FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON c.oid = i.indrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN pg_catalog.pg_class idx ON idx.oid = i.indexrelid JOIN pg_catalog.pg_am am ON am.oid = idx.relam CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY k(attnum, ordinality) LEFT JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND a.attnum = k.attnum JOIN pg_catalog.pg_opclass op ON op.oid = i.indclass[k.ordinality - 1] JOIN pg_catalog.pg_namespace opn ON opn.oid = op.opcnamespace ${defaultResolution} WHERE ${scope} AND k.ordinality <= i.indnkeyatts ORDER BY n.nspname, c.relname, idx.relname, k.ordinality`,
1199
+ text: `SELECT n.nspname AS table_namespace, c.relname AS table_name, idx.relname AS index_name, am.amname AS method, i.indisunique AS unique, pg_catalog.pg_get_expr(i.indpred, i.indrelid, false) AS predicate, i.indnullsnotdistinct AS nulls_not_distinct, k.ordinality AS ordinal, a.attname AS column_name, (i.indoption[k.ordinality - 1] & 1) <> 0 AS descending, opn.nspname AS operator_namespace, op.opcname AS operator_name, COALESCE(op.oid = resolved_default.oid, false) AS exact_default${expressionColumns} FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON c.oid = i.indrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN pg_catalog.pg_class idx ON idx.oid = i.indexrelid JOIN pg_catalog.pg_am am ON am.oid = idx.relam CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY k(attnum, ordinality) LEFT JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND a.attnum = k.attnum${expressionJoins} JOIN pg_catalog.pg_opclass op ON op.oid = i.indclass[k.ordinality - 1] JOIN pg_catalog.pg_namespace opn ON opn.oid = op.opcnamespace ${defaultResolution} WHERE ${scope} AND k.ordinality <= i.indnkeyatts ORDER BY n.nspname, c.relname, idx.relname, k.ordinality`,
1130
1200
  values: collector.values,
1131
1201
  returning: "none"
1132
1202
  };
@@ -1149,1004 +1219,1524 @@ function renderIndexColumns(params) {
1149
1219
  return `${dialect.quoteIdent({ name: column.name })}${ops}${column.desc ? " DESC" : ""}`;
1150
1220
  });
1151
1221
  }
1152
- // packages/sql/src/dialects/module-dependency.ts
1153
- function renderModuleDependencyQuery(params) {
1154
- const { dialect, operation } = params;
1155
- if (operation.kind === "visibility")
1156
- return dialect.name === "mysql" ? { text: "SHOW GRANTS FOR CURRENT_USER", values: [], returning: "none" } : undefined;
1157
- if (dialect.name === "sqlite" || operation.tables.length === 0)
1158
- return;
1159
- const collector = new ParamCollector(dialect);
1160
- const owned = operation.tables.map((table, index) => `SELECT ${collector.add(operation.namespace)}${index === 0 ? " AS namespace" : ""}, ${collector.add(table)}${index === 0 ? " AS table_name" : ""}`).join(" UNION ALL ");
1161
- const pg = dialect.name === "postgres";
1162
- const nameKey = ({ column }) => pg ? column : `CAST(IF(@@lower_case_table_names = 0, ${column}, LOWER(${column})) AS BINARY)`;
1163
- const isOwned = ({ namespace, table }) => `EXISTS (SELECT 1 FROM owned_tables o WHERE ${nameKey({ column: namespace })} = ${nameKey({ column: "o.namespace" })} AND ${nameKey({ column: table })} = ${nameKey({ column: "o.table_name" })})`;
1164
- const sourceNamespace = pg ? "sn.nspname" : "fk.CONSTRAINT_SCHEMA";
1165
- const sourceTable = pg ? "s.relname" : "fk.TABLE_NAME";
1166
- const targetNamespace = pg ? "tn.nspname" : "fk.UNIQUE_CONSTRAINT_SCHEMA";
1167
- const targetTable = pg ? "t.relname" : "fk.REFERENCED_TABLE_NAME";
1168
- const from = pg ? "pg_catalog.pg_constraint fk JOIN pg_catalog.pg_class s ON s.oid = fk.conrelid JOIN pg_catalog.pg_namespace sn ON sn.oid = s.relnamespace JOIN pg_catalog.pg_class t ON t.oid = fk.confrelid JOIN pg_catalog.pg_namespace tn ON tn.oid = t.relnamespace WHERE fk.contype = 'f'" : "information_schema.REFERENTIAL_CONSTRAINTS fk";
1169
- return {
1170
- text: `WITH owned_tables AS (${owned})
1171
- SELECT * FROM (SELECT ${pg ? "fk.conname" : "fk.CONSTRAINT_NAME"} AS constraint_name, ${sourceNamespace} AS source_namespace, ${sourceTable} AS source_table, ${targetNamespace} AS target_namespace, ${targetTable} AS target_table, ${isOwned({ namespace: sourceNamespace, table: sourceTable })} AS source_owned, ${isOwned({ namespace: targetNamespace, table: targetTable })} AS target_owned FROM ${from}) dependencies
1172
- WHERE source_owned OR target_owned
1173
- ORDER BY source_namespace, source_table, constraint_name`,
1174
- values: collector.values,
1175
- returning: "none"
1222
+ // packages/sql/src/index-expressions.ts
1223
+ import { VibeError as VibeError11, indexKeyDefinitionKey, indexKeyTerms, isExpressionIndex, unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
1224
+
1225
+ // packages/sql/src/predicate.ts
1226
+ import { VibeError as VibeError7, assertPredicateCapabilities, fieldTypeIdentity, isCanonicalLocalDate as isCanonicalLocalDate2, isEnumType, isLocalDateField, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
1227
+ function renderPredicate(params) {
1228
+ assertPredicateCapabilities({ predicate: params.predicate, dialect: params.dialect.name, usage: params.usage, model: params.model });
1229
+ if (params.usage === "exclusionPredicate" && !params.dialect.capabilities.exclusionConstraints)
1230
+ throw unsupportedCapability8({ dialect: params.dialect.name, feature: "exclusionConstraints" });
1231
+ if (params.usage === "indexPredicate" && !params.dialect.capabilities.partialIndexes) {
1232
+ throw unsupportedCapability8({ dialect: params.dialect.name, feature: "partialIndexes" });
1233
+ }
1234
+ const context = {
1235
+ model: params.model,
1236
+ dialect: params.dialect,
1237
+ enums: params.enums ?? [],
1238
+ usage: params.usage,
1239
+ site: params.site
1176
1240
  };
1241
+ return renderNode({ node: params.predicate, context });
1177
1242
  }
1178
- // packages/sql/src/dialects/migration-replay-coverage.ts
1179
- var extensionMember = (classid, objid) => `EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = '${classid}'::regclass AND d.objid = ${objid} AND d.deptype = 'e')`;
1180
- var extensionOwnedNamespace = `(EXISTS (SELECT 1 FROM pg_catalog.pg_extension e WHERE e.extnamespace = n.oid) OR ${extensionMember("pg_namespace", "n.oid")}) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_class c WHERE c.relnamespace = n.oid AND c.relkind IN ('r', 'p', 'v', 'm', 'f', 'S') AND NOT ${extensionMember("pg_class", "c.oid")}) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_proc p WHERE p.pronamespace = n.oid AND NOT ${extensionMember("pg_proc", "p.oid")}) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_type t WHERE t.typnamespace = n.oid AND (t.typrelid = 0 AND t.typelem = 0 OR t.typtype = 'c' AND EXISTS (SELECT 1 FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid AND c.relkind = 'c')) AND NOT ${extensionMember("pg_type", "t.oid")})`;
1181
- function renderReplayCoverageGaps(params) {
1182
- const { dialect } = params;
1183
- const collector = new ParamCollector(dialect);
1184
- const namespace = collector.add(params.namespace);
1185
- if (dialect.name === "postgres")
1186
- return { text: `SELECT 'type' AS kind, t.typname::text AS name, n.nspname::text AS namespace, NULL::text AS parent FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} AND (t.typtype IN ('b', 'd', 'r', 'm') AND t.typelem = 0 OR t.typtype = 'c' AND EXISTS (SELECT 1 FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid AND c.relkind = 'c')) 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')
1187
- UNION ALL SELECT 'routine', p.proname::text, n.nspname::text, NULL::text FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = ${namespace} AND p.prokind <> 'f' AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_proc'::regclass AND d.objid = p.oid AND d.deptype = 'e')
1188
- UNION ALL SELECT 'relation-layout', c.relname::text, n.nspname::text, NULL::text FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND (c.relkind IN ('p', 'f') OR c.relispartition OR EXISTS (SELECT 1 FROM pg_catalog.pg_inherits i WHERE i.inhrelid = c.oid)) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_class'::regclass AND d.objid = c.oid AND d.deptype = 'e')
1189
- UNION ALL SELECT 'rule', r.rulename::text, n.nspname::text, c.relname::text FROM pg_catalog.pg_rewrite r JOIN pg_catalog.pg_class c ON c.oid = r.ev_class JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND r.rulename <> '_RETURN' AND NOT ${extensionMember("pg_class", "c.oid")}
1190
- UNION ALL SELECT 'namespace', n.nspname::text, n.nspname::text, NULL::text FROM pg_catalog.pg_namespace n WHERE n.nspname <> ${namespace} AND n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND NOT (${extensionOwnedNamespace})${params.privateNamespace === undefined ? "" : ` AND n.nspname <> ${collector.add(params.privateNamespace)}`}`, values: collector.values, returning: "none" };
1191
- if (dialect.name === "mysql")
1192
- return { text: `SELECT 'procedure' AS kind, ROUTINE_NAME AS name, ROUTINE_SCHEMA AS namespace, NULL AS parent FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = ${namespace} AND ROUTINE_TYPE = 'PROCEDURE'
1193
- UNION ALL SELECT 'event', EVENT_NAME, EVENT_SCHEMA, NULL FROM information_schema.EVENTS WHERE EVENT_SCHEMA = ${collector.add(params.namespace)}
1194
- UNION ALL SELECT DISTINCT 'partition', TABLE_NAME, TABLE_SCHEMA, NULL FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = ${collector.add(params.namespace)} AND PARTITION_NAME IS NOT NULL`, values: collector.values, returning: "none" };
1195
- return { text: `SELECT 'table-options' AS kind, name, schema AS namespace, NULL AS parent FROM pragma_table_list WHERE schema = ${namespace} AND name NOT LIKE 'sqlite\\_%' ESCAPE '\\' AND (type = 'virtual' OR wr <> 0 OR strict <> 0)`, values: collector.values, returning: "none" };
1243
+ function renderSchemaExpression({ expression, model, dialect, enums = [], usage, site }) {
1244
+ assertPredicateCapabilities({ expression, dialect: dialect.name, usage, model });
1245
+ const context = { model, dialect, enums, usage, site };
1246
+ const { text, resultType } = renderExpression({ expression, context });
1247
+ return { text, resultType };
1196
1248
  }
1197
- // packages/sql/src/dialects/migration-shadow.ts
1198
- import { assertSqlNamespace, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
1199
- var SHADOW_DATABASE = Object.freeze({ schema: "_vibeorm_shadow", table: "_vibe_shadow", lock: 1447645267, timeoutSeconds: 60 });
1200
- var SHADOW_CAPABILITIES = Object.freeze({
1201
- postgres: Object.freeze({ serverProvisioning: true, dedicated: true }),
1202
- mysql: Object.freeze({ serverProvisioning: true, dedicated: true }),
1203
- sqlite: Object.freeze({ serverProvisioning: false, dedicated: false })
1204
- });
1205
- function renderShadowOperation(params) {
1206
- const { dialect, operation } = params;
1207
- if (!SHADOW_CAPABILITIES[dialect.name].serverProvisioning)
1208
- throw unsupportedCapability8({ dialect: dialect.name, feature: "shadowDatabaseProvisioning" });
1209
- const pg = dialect.name === "postgres";
1210
- const collector = new ParamCollector(dialect);
1211
- const marker = pg ? `${dialect.quoteIdent({ name: SHADOW_DATABASE.schema })}.${dialect.quoteIdent({ name: SHADOW_DATABASE.table })}` : dialect.quoteIdent({ name: SHADOW_DATABASE.table });
1212
- const result = (text) => [{ text, values: collector.values, returning: "none" }];
1213
- switch (operation.kind) {
1214
- case "createDatabase":
1215
- return result(`CREATE DATABASE ${dialect.quoteIdent({ name: operation.name })}${pg ? " TEMPLATE template0" : ""}`);
1216
- case "dropDatabase":
1217
- return result(`DROP DATABASE ${dialect.quoteIdent({ name: operation.name })}`);
1218
- case "identity":
1219
- return result(pg ? `SELECT current_database()::text AS database, (SELECT oid::text FROM pg_catalog.pg_database WHERE datname = current_database()) AS database_id, current_schema()::text AS namespace` : `SELECT DATABASE() AS \`database\`, @@lower_case_table_names AS name_folding, @@server_uuid AS server_id`);
1220
- case "lock":
1221
- return result(pg ? `SELECT pg_catalog.pg_advisory_lock(${collector.add(SHADOW_DATABASE.lock)})` : `SELECT GET_LOCK(${collector.add(`vibeorm_shadow:${operation.database}`)}, ${collector.add(SHADOW_DATABASE.timeoutSeconds)}) AS acquired`);
1222
- case "unlock":
1223
- return result(pg ? `SELECT pg_catalog.pg_advisory_unlock(${collector.add(SHADOW_DATABASE.lock)}) AS released` : `SELECT RELEASE_LOCK(${collector.add(`vibeorm_shadow:${operation.database}`)}) AS released`);
1224
- case "markerExists":
1225
- return result(pg ? `SELECT c.relname AS name FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${collector.add(SHADOW_DATABASE.schema)} AND c.relname = ${collector.add(SHADOW_DATABASE.table)}` : `SELECT TABLE_NAME AS name FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ${collector.add(SHADOW_DATABASE.table)}`);
1226
- case "markerRead":
1227
- return result(`SELECT payload FROM ${marker}`);
1228
- case "markerColumns":
1229
- return result(`SELECT column_name AS name, data_type AS type, is_nullable AS nullable FROM information_schema.columns AS marker_column WHERE table_schema = ${pg ? collector.add(SHADOW_DATABASE.schema) : "DATABASE()"} AND table_name = ${collector.add(SHADOW_DATABASE.table)} AND ${pg ? `EXISTS (SELECT 1 FROM pg_catalog.pg_class r JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace WHERE n.nspname = marker_column.table_schema AND r.relname = marker_column.table_name AND r.relkind = 'r' AND r.relpersistence = 'p' AND NOT r.relrowsecurity AND NOT r.relforcerowsecurity AND NOT r.relhasrules AND NOT r.relhastriggers AND NOT r.relispartition AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_attrdef a WHERE a.adrelid = r.oid) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_constraint c WHERE c.conrelid = r.oid AND c.contype <> 'n') AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_index i WHERE i.indrelid = r.oid) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_inherits i WHERE i.inhrelid = r.oid OR i.inhparent = r.oid) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_attribute a WHERE a.attrelid = r.oid AND (a.attisdropped OR a.attidentity <> '' OR a.attgenerated <> '')))` : `EXISTS (SELECT 1 FROM information_schema.TABLES t WHERE t.TABLE_SCHEMA = marker_column.table_schema AND t.TABLE_NAME = marker_column.table_name AND t.TABLE_TYPE = 'BASE TABLE' AND t.ENGINE = 'InnoDB') AND NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS c WHERE c.TABLE_SCHEMA = marker_column.table_schema AND c.TABLE_NAME = marker_column.table_name AND (c.COLUMN_DEFAULT IS NOT NULL OR c.EXTRA <> '')) AND NOT EXISTS (SELECT 1 FROM information_schema.TRIGGERS t WHERE t.EVENT_OBJECT_SCHEMA = DATABASE() AND t.EVENT_OBJECT_TABLE = ${collector.add(SHADOW_DATABASE.table)}) AND NOT EXISTS (SELECT 1 FROM information_schema.TABLE_CONSTRAINTS c WHERE c.TABLE_SCHEMA = DATABASE() AND c.TABLE_NAME = ${collector.add(SHADOW_DATABASE.table)})`}
1230
- ORDER BY ordinal_position`);
1231
- case "grants": {
1232
- if (pg)
1233
- throw unsupportedCapability8({ dialect: dialect.name, feature: "mysqlShadowPrivileges" });
1234
- return result("SHOW GRANTS FOR CURRENT_USER");
1249
+ var PRECEDENCE = {
1250
+ or: 1,
1251
+ and: 2,
1252
+ not: 3,
1253
+ compare: 4,
1254
+ inList: 4,
1255
+ isNull: 4,
1256
+ isTrue: 4
1257
+ };
1258
+ var COMPARISON_SQL = {
1259
+ eq: "=",
1260
+ ne: "<>",
1261
+ gt: ">",
1262
+ gte: ">=",
1263
+ lt: "<",
1264
+ lte: "<="
1265
+ };
1266
+ var EXACT_INTEGER = /^-?(0|[1-9]\d*)$/;
1267
+ var EXACT_DECIMAL = /^-?(0|[1-9]\d*)(\.\d+)?$/;
1268
+ var INT32_MIN = -2147483648n;
1269
+ var INT32_MAX = 2147483647n;
1270
+ var INT64_MIN = -(2n ** 63n);
1271
+ var INT64_MAX = 2n ** 63n - 1n;
1272
+ function refuse2(params) {
1273
+ const { context, rule, message } = params;
1274
+ throw new VibeError7({
1275
+ code: "VIBE_SCHEMA_INVALID",
1276
+ message: `${context.site} on "${context.model.name}": ${message}`,
1277
+ meta: { rule, model: context.model.name, site: context.site, ...params.meta ?? {} }
1278
+ });
1279
+ }
1280
+ function renderChild(params) {
1281
+ const { node, parent, context } = params;
1282
+ if ((node.kind === "and" || node.kind === "or") && node.operands.length === 1) {
1283
+ return renderChild({ node: node.operands[0], parent, context });
1284
+ }
1285
+ const text = renderNode({ node, context });
1286
+ return PRECEDENCE[node.kind] < PRECEDENCE[parent] ? `(${text})` : text;
1287
+ }
1288
+ function renderNode(params) {
1289
+ const { node, context } = params;
1290
+ switch (node.kind) {
1291
+ case "and":
1292
+ case "or": {
1293
+ if (node.operands.length === 0) {
1294
+ refuse2({
1295
+ context,
1296
+ rule: "predicate-empty",
1297
+ message: `${node.kind === "and" ? "and()" : "or()"} has no operands \u2014 an empty boolean group has no honest SQL spelling`
1298
+ });
1299
+ }
1300
+ if (node.operands.length === 1)
1301
+ return renderNode({ node: node.operands[0], context });
1302
+ const separator = node.kind === "and" ? " AND " : " OR ";
1303
+ return node.operands.map((operand) => renderChild({ node: operand, parent: node.kind, context })).join(separator);
1235
1304
  }
1236
- case "markerCreate":
1237
- return pg ? [{ text: `CREATE SCHEMA ${dialect.quoteIdent({ name: SHADOW_DATABASE.schema })}`, values: [], returning: "none" }, { text: `CREATE TABLE ${marker} (payload text NOT NULL)`, values: [], returning: "none" }] : result(`CREATE TABLE ${marker} (payload LONGTEXT NOT NULL) ENGINE=InnoDB`);
1238
- case "markerWrite":
1239
- return result(operation.insert ? `INSERT INTO ${marker} (payload) VALUES (${collector.add(operation.value)})` : `UPDATE ${marker} SET payload = ${collector.add(operation.value)}`);
1240
- case "journal":
1241
- return result(`SELECT name, checksum, statement_index, error FROM ${dialect.quoteIdent({ name: "_vibe_migrations" })} ORDER BY name`);
1242
- case "inventory":
1243
- return result(pg ? `SELECT 'namespace' AS kind, nspname::text AS name, nspname::text AS namespace FROM pg_catalog.pg_namespace WHERE nspname NOT IN ('public', 'information_schema') AND nspname NOT LIKE 'pg\\_%' ESCAPE '\\'
1244
- UNION ALL SELECT 'relation', c.relname::text, n.nspname::text FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'
1245
- UNION ALL SELECT 'routine', p.proname::text, n.nspname::text FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'
1246
- UNION ALL SELECT 'type', t.typname::text, n.nspname::text FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND t.typrelid = 0 AND t.typelem = 0
1247
- UNION ALL SELECT 'extension', e.extname::text, n.nspname::text FROM pg_catalog.pg_extension e JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace WHERE e.extname <> 'plpgsql'
1248
- UNION ALL SELECT 'marker-dependency', d.objid::text, '${SHADOW_DATABASE.schema}' FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_class c ON d.classid = 'pg_class'::regclass AND d.objid = c.oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '${SHADOW_DATABASE.schema}' AND d.deptype NOT IN ('i', 'a') AND d.refclassid <> 'pg_namespace'::regclass
1249
- UNION ALL SELECT 'marker-trigger', t.tgname::text, n.nspname::text FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '${SHADOW_DATABASE.schema}'
1250
- UNION ALL SELECT 'namespace-object', o.name::text, n.nspname::text FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_namespace n ON d.refclassid = 'pg_namespace'::regclass AND d.refobjid = n.oid CROSS JOIN LATERAL pg_catalog.pg_identify_object(d.classid, d.objid, d.objsubid) o WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND d.classid NOT IN ('pg_class'::regclass, 'pg_proc'::regclass, 'pg_type'::regclass, 'pg_extension'::regclass)
1251
- UNION ALL SELECT 'database-object', evtname::text, '' FROM pg_catalog.pg_event_trigger
1252
- UNION ALL SELECT 'database-object', pubname::text, '' FROM pg_catalog.pg_publication
1253
- UNION ALL SELECT 'database-object', subname::text, '' FROM pg_catalog.pg_subscription WHERE subdbid = (SELECT oid FROM pg_catalog.pg_database WHERE datname = current_database())
1254
- UNION ALL SELECT 'database-object', srvname::text, '' FROM pg_catalog.pg_foreign_server
1255
- UNION ALL SELECT 'database-object', fdwname::text, '' FROM pg_catalog.pg_foreign_data_wrapper
1256
- UNION ALL SELECT 'database-object', c.oid::text, '' FROM pg_catalog.pg_cast c JOIN pg_catalog.pg_type s ON s.oid = c.castsource JOIN pg_catalog.pg_type t ON t.oid = c.casttarget LEFT JOIN pg_catalog.pg_proc p ON p.oid = c.castfunc WHERE s.typnamespace <> 'pg_catalog'::regnamespace OR t.typnamespace <> 'pg_catalog'::regnamespace OR p.pronamespace <> 'pg_catalog'::regnamespace` : `SELECT TABLE_TYPE AS kind, TABLE_NAME AS name, TABLE_SCHEMA AS namespace FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE()
1257
- UNION ALL SELECT ROUTINE_TYPE, ROUTINE_NAME, ROUTINE_SCHEMA FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE()
1258
- UNION ALL SELECT 'EVENT', EVENT_NAME, EVENT_SCHEMA FROM information_schema.EVENTS WHERE EVENT_SCHEMA = DATABASE()
1259
- UNION ALL SELECT 'TRIGGER', TRIGGER_NAME, TRIGGER_SCHEMA FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = DATABASE()
1260
- UNION ALL SELECT 'EXTERNAL FOREIGN KEY', CONCAT(TABLE_NAME, '.', CONSTRAINT_NAME), CONSTRAINT_SCHEMA FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE UNIQUE_CONSTRAINT_SCHEMA = DATABASE() AND CAST(CONSTRAINT_SCHEMA AS BINARY) <> CAST(DATABASE() AS BINARY)`);
1261
- case "resetPostgres":
1262
- case "resetDedicatedPostgres": {
1263
- if (!pg)
1264
- throw unsupportedCapability8({ dialect: dialect.name, feature: "postgresShadowReset" });
1265
- const owned = operation.kind === "resetDedicatedPostgres" ? operation.ownedNamespaces : [];
1266
- for (const namespace of owned)
1267
- assertSqlNamespace({ namespace, dialect: "postgres", allowPublic: false });
1268
- return [
1269
- ...owned.map((namespace) => ({ text: `DROP SCHEMA IF EXISTS ${dialect.quoteIdent({ name: namespace })} CASCADE`, values: [], returning: "none" })),
1270
- { text: 'DROP SCHEMA "public" CASCADE', values: [], returning: "none" },
1271
- { text: 'CREATE SCHEMA "public"', values: [], returning: "none" }
1272
- ];
1305
+ case "not":
1306
+ return `NOT ${renderChild({ node: node.operand, parent: "not", context })}`;
1307
+ case "isTrue":
1308
+ return `COALESCE(${renderNode({ node: node.operand, context })}, FALSE)`;
1309
+ case "isNull": {
1310
+ const field = resolveField({ name: node.field.name, context, nullTest: true });
1311
+ return `${quoteColumn({ field, context })} IS ${node.negate === true ? "NOT NULL" : "NULL"}`;
1273
1312
  }
1274
- case "mysqlForeignKeys": {
1275
- if (pg)
1276
- throw unsupportedCapability8({ dialect: dialect.name, feature: "mysqlShadowReset" });
1277
- return result(`SET FOREIGN_KEY_CHECKS = ${operation.enabled ? "1" : "0"}`);
1313
+ case "compare": {
1314
+ const left = renderExpression({ expression: node.left, context });
1315
+ const right = renderOperand({ operand: node.right, left, context });
1316
+ return `${left.text} ${COMPARISON_SQL[node.op]} ${right}`;
1278
1317
  }
1279
- case "dropMysqlObject": {
1280
- if (pg)
1281
- throw unsupportedCapability8({ dialect: dialect.name, feature: "mysqlShadowReset" });
1282
- return result(`DROP ${operation.object} ${dialect.quoteIdent({ name: operation.name })}`);
1318
+ case "inList": {
1319
+ if (node.values.length === 0) {
1320
+ refuse2({
1321
+ context,
1322
+ rule: "predicate-empty",
1323
+ message: `inList("${node.field.name}") has no values \u2014 an empty IN list is UNKNOWN on every row and never what an author means`,
1324
+ meta: { field: node.field.name }
1325
+ });
1326
+ }
1327
+ const field = resolveField({ name: node.field.name, context });
1328
+ const values = node.values.map((value) => renderLiteral({ value, resultType: fieldResultType({ field, context }), fieldName: field.name, context })).join(", ");
1329
+ return `${compareColumn({ field, context })} ${node.negate === true ? "NOT IN" : "IN"} (${values})`;
1283
1330
  }
1284
1331
  }
1285
1332
  }
1286
- // packages/sql/src/module-journal.ts
1287
- var MODULE_JOURNAL_COLUMN = "module_origin";
1288
- function renderModuleJournal(params) {
1289
- const { dialect, operation } = params;
1290
- const q = (name) => dialect.quoteIdent({ name });
1291
- const table = q(params.table);
1292
- const column = q(MODULE_JOURNAL_COLUMN);
1293
- const collector = new ParamCollector(dialect);
1294
- let text;
1295
- switch (operation.kind) {
1296
- case "columns":
1297
- text = dialect.name === "sqlite" ? `PRAGMA table_info(${table})` : `SELECT column_name AS name FROM information_schema.columns WHERE table_schema = ${dialect.name === "postgres" ? "current_schema()" : "DATABASE()"} AND table_name = ${collector.add(params.table)}`;
1298
- break;
1299
- case "addColumn":
1300
- text = `ALTER TABLE ${table} ADD COLUMN ${column} ${dialect.name === "mysql" ? "LONGTEXT" : "TEXT"}`;
1301
- break;
1302
- case "read":
1303
- text = `SELECT * FROM ${table} ORDER BY ${q("id")}`;
1304
- break;
1305
- case "insert":
1306
- text = `INSERT INTO ${table} (${["name", "checksum", "statement_index", MODULE_JOURNAL_COLUMN].map(q).join(", ")}) VALUES (${collector.addMany([operation.name, operation.checksum, operation.index, operation.entry]).join(", ")})`;
1307
- break;
1308
- case "complete":
1309
- text = `UPDATE ${table} SET ${column} = ${collector.add(operation.entry)} WHERE ${q("name")} = ${collector.add(operation.name)}`;
1310
- break;
1333
+ function resolveField(params) {
1334
+ const { name, context } = params;
1335
+ const field = context.model.fields.find((candidate) => candidate.name === name);
1336
+ if (field === undefined) {
1337
+ refuse2({
1338
+ context,
1339
+ rule: "predicate-unknown-field",
1340
+ message: `references field "${name}", which the model does not declare`,
1341
+ meta: { field: name }
1342
+ });
1311
1343
  }
1312
- return { text, values: collector.values };
1313
- }
1314
- // packages/sql/src/capabilities.ts
1315
- import {
1316
- DIALECT_CAPABILITIES,
1317
- POSTGRES_CAPABILITIES,
1318
- SQLITE_CAPABILITIES,
1319
- MYSQL_CAPABILITIES
1320
- } from "@vibeorm/schema";
1321
-
1322
- // packages/sql/src/identifiers.ts
1323
- import { VibeError as VibeError7 } from "@vibeorm/schema";
1324
- function assertNoNul(params) {
1325
- if (params.name.includes("\x00")) {
1326
- throw new VibeError7({
1327
- code: "VIBE_VALIDATION",
1328
- message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
1329
- meta: { name: params.name.replaceAll("\x00", "\\0") }
1344
+ if (field.isList) {
1345
+ refuse2({
1346
+ context,
1347
+ rule: "predicate-list-field",
1348
+ message: `compares scalar list "${name}" \u2014 array operators differ per dialect; use a raw expression for them`,
1349
+ meta: { field: name }
1330
1350
  });
1331
1351
  }
1332
- return params.name;
1333
- }
1334
-
1335
- // packages/sql/src/dialects/sqlite.ts
1336
- var sqliteDialect = {
1337
- name: "sqlite",
1338
- capabilities: SQLITE_CAPABILITIES,
1339
- inArrayStrategy: "expand",
1340
- tupleInStrategy: "rowValue",
1341
- timeOfDayInStrategy: "in",
1342
- upsertForm: "onConflict",
1343
- conflictKeyEquality: {
1344
- foldingScalars: [],
1345
- textIsBinaryByDefault: true,
1346
- duplicatesInOneStatement: "lastWriteWins"
1347
- },
1348
- excludedRowRef: { form: "keyword", name: "excluded" },
1349
- doNothingForm: "onConflictDoNothing",
1350
- insensitiveLike: "lower",
1351
- strictMatchStrategy: "glob",
1352
- greatestForm: "frame",
1353
- orderByNullsMode: "native",
1354
- offsetRequiresLimit: { limitText: "-1" },
1355
- likeEscapeClause: " ESCAPE '\\'",
1356
- jsonPathMode: "sqliteExtract",
1357
- supportedJsonOps: [
1358
- "equals",
1359
- "string_contains",
1360
- "string_starts_with",
1361
- "string_ends_with",
1362
- "array_contains",
1363
- "array_starts_with",
1364
- "array_ends_with"
1365
- ],
1366
- jsonParamCast: "",
1367
- valueCastTypes: Object.freeze({ String: "TEXT", Boolean: "INTEGER", Int: "INTEGER", BigInt: "INTEGER", Float: "REAL", Decimal: "TEXT", DateTime: "TEXT", Json: "TEXT", Bytes: "BLOB" }),
1368
- lastInsertIdSql: null,
1369
- rowLockSyntax: null,
1370
- advisoryLockFunctions: null,
1371
- rowDefaultKeyword: false,
1372
- statementChain: null,
1373
- paramBudget: 900,
1374
- booleanLiteral: { true: "1", false: "0" },
1375
- quoteIdent({ name }) {
1376
- return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
1377
- },
1378
- quoteStringLiteral({ value }) {
1379
- return `'${value.replaceAll("'", "''")}'`;
1380
- },
1381
- placeholder() {
1382
- return "?";
1383
- },
1384
- jsonTextProjection({ expression }) {
1385
- return expression;
1386
- },
1387
- renderDbNow({ offsetSeconds, collector }) {
1388
- if (offsetSeconds === 0)
1389
- return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
1390
- return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
1352
+ if (field.isUnsupported === true) {
1353
+ refuse2({
1354
+ context,
1355
+ rule: "predicate-unsupported-type",
1356
+ message: `references "${name}", an Unsupported("${field.nativeType ?? "?"}") column whose operators VibeORM does not model \u2014 use a raw expression`,
1357
+ meta: { field: name }
1358
+ });
1359
+ }
1360
+ if (params.nullTest !== true && (field.type === "Json" || field.type === "Bytes")) {
1361
+ refuse2({
1362
+ context,
1363
+ rule: "predicate-unsupported-type",
1364
+ message: `references ${field.type} column "${name}" \u2014 ${field.type} comparison operators differ per dialect; use a raw expression`,
1365
+ meta: { field: name, type: field.type }
1366
+ });
1391
1367
  }
1368
+ if (context.usage === "check" && context.dialect.name === "mysql" && field.default?.kind === "autoincrement") {
1369
+ throw unsupportedCapability8({
1370
+ dialect: "mysql",
1371
+ feature: `CHECK constraint referencing auto-increment column "${context.model.name}.${name}"`,
1372
+ hint: "mysql refuses this with error 3818 \u2014 enforce the rule in application code or on a non-generated column"
1373
+ });
1374
+ }
1375
+ return field;
1376
+ }
1377
+ function quoteColumn(params) {
1378
+ const { field, context } = params;
1379
+ return context.dialect.quoteIdent({ name: field.dbName ?? field.name });
1380
+ }
1381
+ function compareColumn(params) {
1382
+ const { field, context } = params;
1383
+ const quoted = quoteColumn({ field, context });
1384
+ return context.dialect.name === "sqlite" && field.type === "Decimal" ? `CAST(${quoted} AS REAL)` : quoted;
1385
+ }
1386
+ function fieldResultType({ field, context }) {
1387
+ if (isLocalDateField({ field }))
1388
+ return { type: field.type, refinement: "localDate" };
1389
+ if (context.dialect.name === "postgres" && field.type === "String" && field.nativeType?.trim().toLowerCase() === "uuid")
1390
+ return { type: "String", refinement: "nativeUuid" };
1391
+ return { type: field.type };
1392
+ }
1393
+ function resultIdentity({ resultType }) {
1394
+ return resultType.refinement === "localDate" ? "localDate" : fieldTypeIdentity({ type: resultType.type });
1395
+ }
1396
+ var ARITHMETIC_SQL = {
1397
+ add: "+",
1398
+ sub: "-",
1399
+ mul: "*"
1392
1400
  };
1393
-
1394
- // packages/sql/src/dialects/sqlite-table-definition.ts
1395
- import { VibeError as VibeError8 } from "@vibeorm/schema";
1396
- function tableDefinitionParts(params) {
1397
- const sql = params.sql;
1398
- if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql))
1399
- throw unavailable();
1400
- const parts = [];
1401
- let start = -1;
1402
- let depth = 0;
1403
- let quote;
1404
- for (let index = 0;index < sql.length; index++) {
1405
- const char = sql[index];
1406
- if (quote !== undefined) {
1407
- if (char === quote) {
1408
- if (quote !== "]" && sql[index + 1] === quote)
1409
- index++;
1410
- else
1411
- quote = undefined;
1401
+ function renderSqliteArithmetic({ left, right, kind }) {
1402
+ return `(${left} ${ARITHMETIC_SQL[kind]} ${right})`;
1403
+ }
1404
+ function renderExpression({ expression, context }) {
1405
+ switch (expression.kind) {
1406
+ case "field": {
1407
+ const field = resolveField({ name: expression.name, context });
1408
+ return { text: compareColumn({ field, context }), resultType: fieldResultType({ field, context }), fieldName: field.name };
1409
+ }
1410
+ case "jsonText": {
1411
+ const field = resolveField({ name: expression.field.name, context, nullTest: true });
1412
+ if (field.type !== "Json")
1413
+ refuse2({ context, rule: "predicate-type-mismatch", message: `jsonText requires a scalar Json field, not "${field.name}"` });
1414
+ if (!Array.isArray(expression.path) || Array.from(expression.path).some((segment) => typeof segment !== "string" || segment.includes("\x00"))) {
1415
+ refuse2({ context, rule: "predicate-json-path", message: "jsonText requires a string-only path without NUL bytes" });
1412
1416
  }
1413
- continue;
1417
+ const path = expression.path.map((value) => context.dialect.quoteStringLiteral({ value })).join(", ");
1418
+ return { text: `(${quoteColumn({ field, context })} #>> ARRAY[${path}]::text[])`, resultType: { type: "String" }, fieldName: field.name };
1414
1419
  }
1415
- if (char === "'" || char === '"' || char === "`") {
1416
- quote = char;
1417
- continue;
1420
+ case "length":
1421
+ case "lower":
1422
+ case "upper": {
1423
+ const inner = renderExpression({ expression: expression.expression, context });
1424
+ if (inner.resultType.type !== "String" || inner.resultType.refinement === "localDate") {
1425
+ refuse2({ context, rule: "predicate-type-mismatch", message: `${expression.kind} requires a text expression, not a local date or another scalar`, meta: { field: inner.fieldName } });
1426
+ }
1427
+ return { text: `${expression.kind}(${inner.text})`, resultType: { type: expression.kind === "length" ? "Int" : "String" }, fieldName: inner.fieldName };
1418
1428
  }
1419
- if (char === "[") {
1420
- quote = "]";
1421
- continue;
1429
+ case "add":
1430
+ case "sub":
1431
+ case "mul": {
1432
+ const left = renderExpression({ expression: expression.left, context });
1433
+ const type = left.resultType.type;
1434
+ if (type !== "Int" && type !== "BigInt" && type !== "Float" && type !== "Decimal") {
1435
+ refuse2({ context, rule: "predicate-type-mismatch", message: `${expression.kind} requires Int, BigInt, Float or Decimal operands`, meta: { field: left.fieldName } });
1436
+ }
1437
+ const right = renderOperand({ operand: expression.right, left, context });
1438
+ const text = context.dialect.name === "sqlite" ? renderSqliteArithmetic({ left: left.text, right, kind: expression.kind, type }) : `(${left.text} ${ARITHMETIC_SQL[expression.kind]} ${right})`;
1439
+ return { text, resultType: { type }, fieldName: left.fieldName };
1422
1440
  }
1423
- if (char === "-" && sql[index + 1] === "-") {
1424
- const end = sql.indexOf(`
1425
- `, index + 2);
1426
- if (end < 0)
1427
- break;
1428
- index = end;
1429
- continue;
1441
+ }
1442
+ }
1443
+ function renderOperand({ operand, left, context }) {
1444
+ if (operand.kind === "value")
1445
+ return renderLiteral({ value: operand, resultType: left.resultType, fieldName: left.fieldName, context });
1446
+ const other = renderExpression({ expression: operand, context });
1447
+ const leftType = resultIdentity({ resultType: left.resultType });
1448
+ const rightType = resultIdentity({ resultType: other.resultType });
1449
+ if (leftType !== rightType) {
1450
+ refuse2({
1451
+ context,
1452
+ rule: "predicate-type-mismatch",
1453
+ message: `compares "${left.fieldName}" (${leftType}) with "${other.fieldName}" (${rightType}): both operands need the same scalar type`,
1454
+ meta: { field: left.fieldName, other: other.fieldName, fieldType: leftType, otherType: rightType }
1455
+ });
1456
+ }
1457
+ return other.text;
1458
+ }
1459
+ function renderLiteral(params) {
1460
+ const { resultType, fieldName, context } = params;
1461
+ const { value } = params.value;
1462
+ const type = resultType.type;
1463
+ const wrongCarrier = (expected) => refuse2({
1464
+ context,
1465
+ rule: "predicate-type-mismatch",
1466
+ message: `compares ${fieldTypeIdentity({ type })} field "${fieldName}" with ${describe({ value })}: ${expected}`,
1467
+ meta: { field: fieldName, fieldType: fieldTypeIdentity({ type }), value }
1468
+ });
1469
+ if (isEnumType(type)) {
1470
+ if (typeof value !== "string")
1471
+ return wrongCarrier(`enum ${type.enum} takes one of its member names as a string`);
1472
+ const declared = context.enums.find((entry) => entry.name === type.enum);
1473
+ if (declared !== undefined && !declared.values.includes(value)) {
1474
+ refuse2({
1475
+ context,
1476
+ rule: "predicate-enum-member",
1477
+ message: `compares "${fieldName}" with "${value}", which is not a member of enum ${type.enum} (${declared.values.join(", ")})`,
1478
+ meta: { field: fieldName, enum: type.enum, value, members: [...declared.values] }
1479
+ });
1430
1480
  }
1431
- if (char === "/" && sql[index + 1] === "*") {
1432
- const end = sql.indexOf("*/", index + 2);
1433
- if (end < 0)
1434
- break;
1435
- index = end + 1;
1436
- continue;
1481
+ return stringLiteral({ value, context, fieldName });
1482
+ }
1483
+ switch (type) {
1484
+ case "String":
1485
+ if (typeof value !== "string")
1486
+ return wrongCarrier("a String column takes a string literal");
1487
+ if (resultType.refinement === "localDate" && !isCanonicalLocalDate2({ value })) {
1488
+ refuse2({
1489
+ context,
1490
+ rule: "predicate-literal-invalid",
1491
+ message: `compares local-date field "${fieldName}" with a noncanonical or invalid calendar date`,
1492
+ meta: { field: fieldName, value }
1493
+ });
1494
+ }
1495
+ return stringLiteral({ value, context, fieldName });
1496
+ case "Boolean":
1497
+ if (typeof value !== "boolean")
1498
+ return wrongCarrier("a Boolean column takes `true` or `false`");
1499
+ return value ? context.dialect.booleanLiteral.true : context.dialect.booleanLiteral.false;
1500
+ case "Int":
1501
+ return integerLiteral({ value, fieldName, context, min: INT32_MIN, max: INT32_MAX, label: "Int" });
1502
+ case "BigInt":
1503
+ return integerLiteral({ value, fieldName, context, min: INT64_MIN, max: INT64_MAX, label: "BigInt" });
1504
+ case "Float": {
1505
+ if (typeof value !== "number")
1506
+ return wrongCarrier("a Float column takes a number");
1507
+ if (!Number.isFinite(value)) {
1508
+ refuse2({
1509
+ context,
1510
+ rule: "predicate-literal-invalid",
1511
+ message: `compares "${fieldName}" with ${String(value)}: a Float literal must be finite`,
1512
+ meta: { field: fieldName, value: String(value) }
1513
+ });
1514
+ }
1515
+ return String(value);
1437
1516
  }
1438
- if (char === "(") {
1439
- if (depth === 0)
1440
- start = index + 1;
1441
- depth++;
1442
- } else if (char === ")") {
1443
- depth--;
1444
- if (depth === 0 && start >= 0) {
1445
- parts.push(sql.slice(start, index).trim());
1446
- return { clauses: parts, suffix: sql.slice(index + 1) };
1517
+ case "Decimal": {
1518
+ if (typeof value !== "string") {
1519
+ return wrongCarrier('a Decimal column takes exact decimal TEXT ("0", "19.99") \u2014 a JS number cannot carry an exact decimal');
1520
+ }
1521
+ if (!EXACT_DECIMAL.test(value)) {
1522
+ refuse2({
1523
+ context,
1524
+ rule: "predicate-literal-invalid",
1525
+ message: `compares "${fieldName}" with "${value}", which is not an exact decimal literal`,
1526
+ meta: { field: fieldName, value }
1527
+ });
1447
1528
  }
1448
- } else if (char === "," && depth === 1) {
1449
- parts.push(sql.slice(start, index).trim());
1450
- start = index + 1;
1529
+ return value;
1451
1530
  }
1531
+ case "DateTime":
1532
+ refuse2({
1533
+ context,
1534
+ rule: "predicate-unsupported-literal",
1535
+ message: `compares DateTime field "${fieldName}" with a literal. DateTime literals are spelled differently on postgres, mysql and sqlite, so VibeORM does not guess one. Compare two DateTime columns, or write a raw expression for a fixed instant`,
1536
+ meta: { field: fieldName }
1537
+ });
1538
+ case "Json":
1539
+ case "Bytes":
1540
+ return wrongCarrier(`${type} columns take no predicate literal`);
1452
1541
  }
1453
- throw unavailable();
1454
- }
1455
- function unavailable() {
1456
- return new VibeError8({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
1457
1542
  }
1458
- function sqliteColumnClauses(params) {
1459
- const columns = [];
1460
- for (const part of tableDefinitionParts(params).clauses) {
1461
- const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
1462
- if (token === null)
1463
- throw unavailable();
1464
- if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
1465
- continue;
1466
- const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
1467
- columns.push({ name, sql: part });
1543
+ function integerLiteral(params) {
1544
+ const { value, fieldName, context, min, max, label } = params;
1545
+ let text;
1546
+ if (typeof value === "number") {
1547
+ if (!Number.isSafeInteger(value)) {
1548
+ refuse2({
1549
+ context,
1550
+ rule: "predicate-literal-invalid",
1551
+ message: `compares "${fieldName}" with ${String(value)}: an ${label} literal must be an exact integer JavaScript can represent; pass a bigint or a decimal string for larger values`,
1552
+ meta: { field: fieldName, value: String(value) }
1553
+ });
1554
+ }
1555
+ text = String(value);
1556
+ } else if (typeof value === "string") {
1557
+ if (!EXACT_INTEGER.test(value)) {
1558
+ refuse2({
1559
+ context,
1560
+ rule: "predicate-literal-invalid",
1561
+ message: `compares "${fieldName}" with "${value}", which is not an exact integer literal`,
1562
+ meta: { field: fieldName, value }
1563
+ });
1564
+ }
1565
+ text = value;
1566
+ } else {
1567
+ refuse2({
1568
+ context,
1569
+ rule: "predicate-type-mismatch",
1570
+ message: `compares ${label} field "${fieldName}" with ${describe({ value })}: an ${label} column takes an integer`,
1571
+ meta: { field: fieldName, value }
1572
+ });
1468
1573
  }
1469
- return columns;
1574
+ const parsed = BigInt(text);
1575
+ if (parsed < min || parsed > max) {
1576
+ refuse2({
1577
+ context,
1578
+ rule: "predicate-literal-invalid",
1579
+ message: `compares "${fieldName}" with ${text}, which is outside the range an ${label} column can hold (${min}\u2026${max})`,
1580
+ meta: { field: fieldName, value: text }
1581
+ });
1582
+ }
1583
+ return text;
1470
1584
  }
1471
-
1472
- // packages/sql/src/dialects/sqlite-fts.ts
1473
- function updateStatements(params) {
1474
- const quote = (name) => sqliteDialect.quoteIdent({ name });
1475
- const columns = params.columns.map((column) => quote(column)).join(", ");
1476
- const oldValues = params.columns.map((column) => `old.${quote(column)}`).join(", ");
1477
- const newValues = params.columns.map((column) => `new.${quote(column)}`).join(", ");
1478
- const changes = [
1479
- "old.rowid IS NOT new.rowid",
1480
- ...params.columns.map((column) => `old.${quote(column)} COLLATE BINARY IS NOT new.${quote(column)} COLLATE BINARY`)
1481
- ].join(" OR ");
1482
- const target = `${quote(`${params.ftsTable}_au`)} AFTER UPDATE ON ${quote(params.table)}`;
1483
- const body = `BEGIN
1484
- INSERT INTO ${quote(params.ftsTable)}(${quote(params.ftsTable)}, rowid, ${columns}) VALUES ('delete', old.rowid, ${oldValues});
1485
- INSERT INTO ${quote(params.ftsTable)}(rowid, ${columns}) VALUES (new.rowid, ${newValues});
1486
- END`;
1487
- const tail = `${target} WHEN (${changes}) ${body}`;
1488
- return {
1489
- create: `CREATE TRIGGER IF NOT EXISTS ${tail};`,
1490
- definition: `CREATE TRIGGER ${tail}`,
1491
- legacyDefinition: `CREATE TRIGGER ${target} ${body}`
1492
- };
1585
+ function stringLiteral(params) {
1586
+ const { value, context, fieldName } = params;
1587
+ if (value.includes("\x00")) {
1588
+ refuse2({
1589
+ context,
1590
+ rule: "predicate-literal-invalid",
1591
+ message: `compares "${fieldName}" with a string containing a NUL byte, which no dialect here can store`,
1592
+ meta: { field: fieldName }
1593
+ });
1594
+ }
1595
+ return context.dialect.quoteStringLiteral({ value });
1493
1596
  }
1494
- function renderSqliteFtsUpdateTrigger(params) {
1495
- const { create, definition } = updateStatements(params);
1496
- return { create, definition };
1597
+ function describe(params) {
1598
+ const { value } = params;
1599
+ return typeof value === "string" ? `the string ${JSON.stringify(value)}` : `the ${typeof value} ${String(value)}`;
1497
1600
  }
1498
- function sqliteFtsUpdateUpgrade(params) {
1499
- const quoted = '"(?:[^"]|"")+"';
1500
- const body = new RegExp(`BEGIN\\n INSERT INTO (${quoted})\\((${quoted}), rowid, (${quoted}(?:, ${quoted})*)\\) VALUES \\('delete', old\\.rowid, `, "g");
1501
- for (const match of params.create.matchAll(body)) {
1502
- const ftsTable = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[1]})` })[0].name;
1503
- const columns = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[3]})` }).map((column) => column.name);
1504
- if (params.name !== `${ftsTable}_au`)
1601
+
1602
+ // packages/sql/src/dialects/postgres-index-expressions.ts
1603
+ import { unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
1604
+
1605
+ // packages/sql/src/dialects/sqlite-table-definition.ts
1606
+ import { VibeError as VibeError8 } from "@vibeorm/schema";
1607
+ function tableDefinitionTokens({ sql, backslashEscapes = false }) {
1608
+ const tokens = [];
1609
+ let depth = 0;
1610
+ for (let index = 0;index < sql.length; ) {
1611
+ const char = sql[index];
1612
+ if (/\s/.test(char)) {
1613
+ index++;
1505
1614
  continue;
1506
- const statements = updateStatements({ table: params.table, ftsTable, columns });
1507
- if (params.create !== statements.create)
1615
+ }
1616
+ if (char === "-" && sql[index + 1] === "-" || char === "#") {
1617
+ const end = sql.indexOf(`
1618
+ `, index + 1);
1619
+ index = end < 0 ? sql.length : end + 1;
1508
1620
  continue;
1509
- const installed = params.installed === statements.legacyDefinition ? "legacy" : params.installed === statements.definition ? "current" : undefined;
1510
- if (installed === undefined)
1621
+ }
1622
+ if (char === "/" && sql[index + 1] === "*") {
1623
+ const end = sql.indexOf("*/", index + 2);
1624
+ if (end < 0)
1625
+ throw unavailable();
1626
+ index = end + 2;
1511
1627
  continue;
1512
- if (params.tableDefinition === undefined)
1513
- return { kind: "tableRequired", ftsTable };
1514
- const tableTail = `${sqliteDialect.quoteIdent({ name: ftsTable })} USING fts5(${columns.map((name) => sqliteDialect.quoteIdent({ name })).join(", ")}, content=${sqliteDialect.quoteStringLiteral({ value: params.table })})`;
1515
- if (params.tableDefinition !== `CREATE VIRTUAL TABLE ${tableTail}` && params.tableDefinition !== `CREATE VIRTUAL TABLE IF NOT EXISTS ${tableTail};`)
1628
+ }
1629
+ const start = index;
1630
+ if (char === "'" || char === '"' || char === "`" || char === "[") {
1631
+ const close = char === "[" ? "]" : char;
1632
+ let value = "";
1633
+ let closed = false;
1634
+ index++;
1635
+ while (index < sql.length) {
1636
+ const current = sql[index++];
1637
+ if (backslashEscapes && current === "\\" && (char === "'" || char === '"')) {
1638
+ if (index >= sql.length)
1639
+ throw unavailable();
1640
+ value += sql[index++];
1641
+ } else if (current === close) {
1642
+ if (close !== "]" && sql[index] === close) {
1643
+ value += close;
1644
+ index++;
1645
+ } else {
1646
+ closed = true;
1647
+ break;
1648
+ }
1649
+ } else
1650
+ value += current;
1651
+ }
1652
+ if (!closed)
1653
+ throw unavailable();
1654
+ tokens.push({ value, kind: char === "'" ? "literal" : "quoted", start, end: index, depth });
1516
1655
  continue;
1517
- return { kind: "recognized", ftsTable, legacyDefinition: statements.legacyDefinition, currentDefinition: statements.definition, drop: `DROP TRIGGER IF EXISTS ${sqliteDialect.quoteIdent({ name: `${ftsTable}_au` })};`, installed };
1656
+ }
1657
+ if (/[\p{L}\p{N}_$]/u.test(char)) {
1658
+ index++;
1659
+ while (index < sql.length && /[\p{L}\p{N}_$]/u.test(sql[index]))
1660
+ index++;
1661
+ tokens.push({ value: sql.slice(start, index), kind: "word", start, end: index, depth });
1662
+ continue;
1663
+ }
1664
+ if (char === ")") {
1665
+ depth--;
1666
+ if (depth < 0)
1667
+ throw unavailable();
1668
+ }
1669
+ tokens.push({ value: char, kind: "symbol", start, end: ++index, depth });
1670
+ if (char === "(")
1671
+ depth++;
1518
1672
  }
1519
- return;
1520
- }
1521
- // packages/sql/src/dialects/sqlite-session.ts
1522
- var SQLITE_SESSION_SQL = Object.freeze({
1523
- lockingMode: "PRAGMA main.locking_mode",
1524
- exclusive: "PRAGMA main.locking_mode = EXCLUSIVE",
1525
- normal: "PRAGMA main.locking_mode = NORMAL",
1526
- acquire: "BEGIN EXCLUSIVE",
1527
- commit: "COMMIT",
1528
- rollback: "ROLLBACK",
1529
- touch: "SELECT name FROM main.sqlite_schema LIMIT 1",
1530
- foreignKeysOn: "PRAGMA foreign_keys = ON"
1531
- });
1532
- // packages/sql/src/composable.ts
1533
- import { VibeError as VibeError9 } from "@vibeorm/schema";
1534
- var fragments = new WeakMap;
1535
- registerSqlHandleCheck({ check: (value) => fragments.has(value) });
1536
- function invalid3(message) {
1537
- throw new VibeError9({ code: "VIBE_VALIDATION", message });
1538
- }
1539
- function construct(parts) {
1540
- const snapshot = Object.freeze([...parts]);
1541
- const fragment = Object.freeze(Object.defineProperty(Object.create(null), "toJSON", {
1542
- value: () => ({ parts: snapshot })
1543
- }));
1544
- fragments.set(fragment, snapshot);
1545
- return fragment;
1546
- }
1547
- function isSqlFragment(value) {
1548
- return typeof value === "object" && value !== null && fragments.has(value);
1673
+ if (depth !== 0)
1674
+ throw unavailable();
1675
+ return tokens;
1549
1676
  }
1550
- function partsOf(value) {
1551
- return fragments.get(value) ?? invalid3("Expected a constructed SQL fragment");
1677
+ function tableDefinitionParts({ sql, backslashEscapes }) {
1678
+ const tokens = tableDefinitionTokens({ sql, backslashEscapes });
1679
+ const words = tokens.filter((token) => token.depth === 0);
1680
+ if (words[0]?.kind !== "word" || words[0].value.toUpperCase() !== "CREATE")
1681
+ throw unavailable();
1682
+ const tablePosition = /^(TEMP|TEMPORARY)$/i.test(words[1]?.value ?? "") ? 2 : 1;
1683
+ if (words[tablePosition]?.kind !== "word" || words[tablePosition].value.toUpperCase() !== "TABLE")
1684
+ throw unavailable();
1685
+ const open = words.find((token) => token.kind === "symbol" && token.value === "(");
1686
+ if (open === undefined)
1687
+ throw unavailable();
1688
+ const close = words.find((token) => token.start > open.start && token.kind === "symbol" && token.value === ")");
1689
+ if (close === undefined)
1690
+ throw unavailable();
1691
+ const delimiters = tokens.filter((token) => token.start > open.start && token.start < close.start && token.depth === 1 && token.kind === "symbol" && token.value === ",");
1692
+ const clauses = [];
1693
+ let start = open.end;
1694
+ for (const delimiter of [...delimiters, close]) {
1695
+ const clause = sql.slice(start, delimiter.start).trim();
1696
+ if (clause.length === 0)
1697
+ throw unavailable();
1698
+ clauses.push(clause);
1699
+ start = delimiter.end;
1700
+ }
1701
+ return { clauses, suffix: sql.slice(close.end) };
1552
1702
  }
1553
- function containsGreatest(fragment) {
1554
- return partsOf(fragment).some((part) => typeof part !== "string" && ("greatest" in part));
1703
+ function unavailable() {
1704
+ return new VibeError8({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
1555
1705
  }
1556
- function identifier(parts) {
1557
- if (!Array.isArray(parts) || parts.length === 0 || parts.some((part) => typeof part !== "string" || part.length === 0 || part.includes("\x00"))) {
1558
- invalid3("SQL identifiers require non-empty, NUL-free name components");
1706
+ function tableColumnClauses({ sql, backslashEscapes, mysql = false }) {
1707
+ const columns = [];
1708
+ for (const part of tableDefinitionParts({ sql, backslashEscapes }).clauses) {
1709
+ const first = tableDefinitionTokens({ sql: part, backslashEscapes })[0];
1710
+ if (first === undefined || first.kind === "symbol")
1711
+ throw unavailable();
1712
+ if (first.kind === "word" && /^(CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(first.value))
1713
+ continue;
1714
+ if (mysql && first.kind === "word" && /^(KEY|INDEX|FULLTEXT|SPATIAL)$/i.test(first.value))
1715
+ continue;
1716
+ columns.push({ name: first.value, sql: part });
1559
1717
  }
1718
+ return columns;
1560
1719
  }
1561
- function template(strings, ...values) {
1562
- if (!Array.isArray(strings) || !Array.isArray(strings.raw) || strings.length !== values.length + 1 || strings.some((text) => typeof text !== "string")) {
1563
- invalid3("Use sql as a template tag");
1564
- }
1565
- const parts = [];
1566
- for (let index = 0;index < strings.length; index++) {
1567
- parts.push(strings[index]);
1568
- if (index < values.length) {
1569
- const value = values[index];
1570
- if (isSqlFragment(value))
1571
- parts.push(...partsOf(value));
1572
- else
1573
- parts.push(Object.freeze({ value: snapshotSqlValue(value) }));
1574
- }
1575
- }
1576
- return construct(parts);
1720
+ function sqliteColumnClauses({ sql }) {
1721
+ return tableColumnClauses({ sql });
1577
1722
  }
1578
- var sql = Object.freeze(Object.assign(template, {
1579
- text: ({ text }) => {
1580
- if (typeof text !== "string" || text.includes("\x00"))
1581
- invalid3("SQL text must be a NUL-free string");
1582
- return construct([text]);
1583
- },
1584
- ident: ({ parts }) => {
1585
- identifier(parts);
1586
- return construct([Object.freeze({ ident: Object.freeze([...parts]) })]);
1587
- },
1588
- value: ({ value, decoder }) => construct([Object.freeze({ value: snapshotSqlValue(value), ...decoder === undefined ? {} : { decoder } })]),
1589
- join: ({ items, separator }) => {
1590
- if (!Array.isArray(items))
1591
- invalid3("sql.join requires an array");
1592
- const between = separator === undefined ? [", "] : partsOf(separator);
1593
- const parts = [];
1594
- items.forEach((value, index) => {
1595
- if (index > 0)
1596
- parts.push(...between);
1597
- parts.push(...isSqlFragment(value) ? partsOf(value) : [Object.freeze({ value: snapshotSqlValue(value) })]);
1598
- });
1599
- return construct(parts);
1600
- },
1601
- existing: ({ column }) => {
1602
- identifier([column]);
1603
- return construct([Object.freeze({ row: "existing", column })]);
1604
- },
1605
- incoming: ({ column }) => {
1606
- identifier([column]);
1607
- return construct([Object.freeze({ row: "incoming", column })]);
1608
- },
1609
- greatest: ({ values }) => {
1610
- if (!Array.isArray(values) || values.length !== 2 || values.some((value) => !isSqlFragment(value)))
1611
- invalid3("sql.greatest requires two SQL fragments");
1612
- return construct([Object.freeze({ greatest: Object.freeze([...values]) })]);
1613
- }
1614
- }));
1615
- function renderSqlFragment(params) {
1616
- const { dialect, collector, conflict } = params;
1617
- const render = (fragment) => renderSqlFragment({ ...params, fragment });
1618
- const encode = (part) => {
1619
- if (part.value === undefined)
1620
- invalid3("SQL parameters cannot be undefined; use null explicitly");
1621
- return snapshotSqlValue(part.decoder === undefined ? part.value : part.decoder.encode({ value: snapshotSqlValue(part.value), dialect: dialect.name }));
1622
- };
1623
- const recording = isSqlBindingCollector({ collector });
1624
- const recordValue = (part) => recordSqlBinding({
1625
- collector,
1626
- source: { kind: "literal", value: part.value },
1627
- transport: part.decoder === undefined ? sqlParameterTransport({ value: part.value }) : { kind: "plain" },
1628
- encode: ({ value, dialect: target }) => part.decoder === undefined ? value : part.decoder.encode({ value, dialect: target.name })
1629
- });
1630
- const frameOperand = (fragment) => {
1631
- const parts = partsOf(fragment).filter((part) => part !== "");
1632
- const only = parts.length === 1 ? parts[0] : undefined;
1633
- if (recording && only !== undefined && typeof only !== "string" && "value" in only) {
1634
- if (only.decoder !== undefined && resultScalarContract({ decoder: only.decoder }) === undefined)
1635
- throw new VibeError9({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL cannot compile an opaque encoder whose output controls a greatest NULL frame" });
1636
- if (only.value === null) {
1637
- if (only.decoder !== undefined)
1638
- recordSqlBindingCheck({ collector, check: () => {
1639
- if (encode({ ...only, value: snapshotSqlValue(only.value) }) !== null)
1640
- throw new VibeError9({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL NULL frame encoding changed its declared structure" });
1641
- } });
1642
- return "NULL";
1723
+
1724
+ // packages/sql/src/dialects/sqlite-index-expressions.ts
1725
+ function extractIndexKeyList({ sql, open, close, tokens, wrapped = false, removeWrapper = wrapped }) {
1726
+ const commas = tokens.filter((token) => token.start > open.start && token.start < close.start && token.depth === open.depth + 1 && token.kind === "symbol" && token.value === ",");
1727
+ const keys = [];
1728
+ let start = open.end;
1729
+ for (const delimiter of [...commas, close]) {
1730
+ const termTokens = tokens.filter((token) => token.start >= start && token.end <= delimiter.start);
1731
+ if (termTokens.length === 0)
1732
+ return;
1733
+ const last = termTokens.at(-1);
1734
+ const sorted = last.depth === open.depth + 1 && last.kind === "word" && /^(ASC|DESC)$/i.test(last.value);
1735
+ const desc = sorted && /^DESC$/i.test(last.value);
1736
+ const bodyTokens = sorted ? termTokens.slice(0, -1) : termTokens;
1737
+ const first = bodyTokens[0];
1738
+ const end = bodyTokens.at(-1);
1739
+ if (first === undefined || end === undefined)
1740
+ return;
1741
+ if (bodyTokens.length === 1 && (first.kind === "word" || first.kind === "quoted")) {
1742
+ keys.push({ kind: "field", name: first.value, desc });
1743
+ } else {
1744
+ const top = bodyTokens.filter((token) => token.depth === open.depth + 1);
1745
+ if (top.some((token) => token.kind === "word" && /^(COLLATE|NULLS)$/i.test(token.value)))
1746
+ return;
1747
+ if (first.kind === "symbol" && first.value === "(" && end.kind === "symbol" && end.value === ")" && end.depth === first.depth) {
1748
+ if (top.length !== 2)
1749
+ return;
1750
+ const text = sql.slice(removeWrapper ? first.end : start, removeWrapper ? end.start : sorted ? last.start : delimiter.start).trim();
1751
+ if (!text)
1752
+ return;
1753
+ keys.push({ kind: "expression", text, desc });
1754
+ } else {
1755
+ if (wrapped)
1756
+ return;
1757
+ const text = sql.slice(start, sorted ? last.start : delimiter.start).trim();
1758
+ if (!text || bodyTokens.some((token) => token.depth === open.depth + 1 && token.kind === "word" && /^(ASC|DESC)$/i.test(token.value)))
1759
+ return;
1760
+ keys.push({ kind: "expression", text, desc });
1643
1761
  }
1644
- const text = recordValue(only);
1645
- return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
1646
1762
  }
1647
- const value = only !== undefined && typeof only !== "string" && "value" in only ? { encoded: encode(only) } : undefined;
1648
- if (value !== undefined && value.encoded === null)
1649
- return "NULL";
1650
- const text = value === undefined ? render(fragment) : collector.add(value.encoded);
1651
- return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
1763
+ start = delimiter.end;
1764
+ }
1765
+ return keys.length === 0 ? undefined : keys;
1766
+ }
1767
+ function extractStandaloneIndexKeys({ definition, index, postgres = false }) {
1768
+ const tokens = tableDefinitionTokens({ sql: definition });
1769
+ const top = tokens.filter((token) => token.depth === 0);
1770
+ let position = 0;
1771
+ const word = ({ value }) => {
1772
+ const token = top[position];
1773
+ if (token?.kind !== "word" || token.value.toUpperCase() !== value)
1774
+ return false;
1775
+ position++;
1776
+ return true;
1652
1777
  };
1653
- return partsOf(params.fragment).map((part) => {
1654
- if (typeof part === "string")
1655
- return part;
1656
- if ("value" in part)
1657
- return recording ? recordValue(part) : collector.add(encode(part));
1658
- if ("ident" in part)
1659
- return part.ident.map((name) => dialect.quoteIdent({ name })).join(".");
1660
- if ("row" in part) {
1661
- if (conflict === undefined)
1662
- invalid3("Existing/incoming row references require a conflict update");
1663
- const name = conflict.columns === undefined ? part.column : conflict.columns[part.column];
1664
- if (name === undefined)
1665
- invalid3(`Unknown conflict-expression field "${part.column}"`);
1666
- const ref = dialect.excludedRowRef;
1667
- const qualifier = part.row === "existing" ? dialect.quoteIdent({ name: conflict.table }) : ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
1668
- return `${qualifier}.${dialect.quoteIdent({ name })}`;
1669
- }
1670
- if (dialect.greatestForm === "native")
1671
- return `GREATEST(${part.greatest.map(render).join(", ")})`;
1672
- if (!part.greatest.some(containsGreatest)) {
1673
- return render(part.greatest.reduce((left, right) => sql`(CASE WHEN ${left} IS NULL THEN ${right} WHEN ${right} IS NULL THEN ${left} WHEN ${left} >= ${right} THEN ${left} ELSE ${right} END)`));
1778
+ if (!word({ value: "CREATE" }))
1779
+ return;
1780
+ word({ value: "UNIQUE" });
1781
+ if (!word({ value: "INDEX" }))
1782
+ return;
1783
+ if (word({ value: "IF" }) && (!word({ value: "NOT" }) || !word({ value: "EXISTS" })))
1784
+ return;
1785
+ let name = top[position++];
1786
+ if (top[position]?.value === ".") {
1787
+ position++;
1788
+ name = top[position++];
1789
+ }
1790
+ if (name === undefined || !["quoted", "word"].includes(name.kind) || name.value !== index || !word({ value: "ON" }))
1791
+ return;
1792
+ const table = top[position++];
1793
+ if (table === undefined || !["quoted", "word"].includes(table.kind))
1794
+ return;
1795
+ if (top[position]?.value === ".") {
1796
+ position++;
1797
+ if (top[position++] === undefined)
1798
+ return;
1799
+ }
1800
+ if (word({ value: "USING" })) {
1801
+ if (!postgres || !word({ value: "BTREE" }))
1802
+ return;
1803
+ }
1804
+ const open = top[position++];
1805
+ const close = top[position++];
1806
+ if (open?.kind !== "symbol" || open.value !== "(" || close?.kind !== "symbol" || close.value !== ")")
1807
+ return;
1808
+ if (postgres && word({ value: "NULLS" }) && (!word({ value: "NOT" }) || !word({ value: "DISTINCT" })))
1809
+ return;
1810
+ const tail = top[position];
1811
+ if (tail !== undefined && !(tail.kind === "word" && tail.value.toUpperCase() === "WHERE") && !(tail.value === ";" && position === top.length - 1))
1812
+ return;
1813
+ const keys = extractIndexKeyList({ sql: definition, open, close, tokens, removeWrapper: postgres });
1814
+ if (postgres && keys !== undefined) {
1815
+ const delimiters = tokens.filter((token) => token.start > open.start && token.start < close.start && token.depth === 1 && token.value === ",");
1816
+ let start = open.end;
1817
+ for (const delimiter of [...delimiters, close]) {
1818
+ const keyTop = tokens.filter((token) => token.start >= start && token.end <= delimiter.start && token.depth === 1);
1819
+ if (keyTop.length > 1 && (keyTop[0]?.kind === "word" || keyTop[0]?.kind === "quoted") && (keyTop[1]?.kind === "quoted" || keyTop[1]?.kind === "word" && !/^(ASC|DESC)$/i.test(keyTop[1].value)))
1820
+ return;
1821
+ start = delimiter.end;
1674
1822
  }
1675
- const [left, right] = part.greatest.map(frameOperand);
1676
- const [a, b, frame] = ["a", "b", "g"].map((name) => dialect.quoteIdent({ name }));
1677
- return `(SELECT CASE WHEN ${a} IS NULL THEN ${b} WHEN ${b} IS NULL THEN ${a} WHEN ${a} >= ${b} THEN ${a} ELSE ${b} END FROM (SELECT ${left} AS ${a}, ${right} AS ${b}) AS ${frame})`;
1678
- }).join("");
1823
+ const slices = tokens.filter((token) => token.start >= open.end && token.end <= close.start && token.depth === 1);
1824
+ if (slices.some((token, ordinal) => slices[ordinal - 1]?.value === ")" && (token.kind === "quoted" || token.kind === "word" && !/^(ASC|DESC)$/i.test(token.value))))
1825
+ return;
1826
+ }
1827
+ return keys;
1679
1828
  }
1680
- function mapConflictColumns(params) {
1681
- return construct(partsOf(params.fragment).map((part) => {
1682
- if (typeof part === "string")
1683
- return part;
1684
- if ("row" in part) {
1685
- const column = Object.hasOwn(params.columns, part.column) ? params.columns[part.column] : undefined;
1686
- if (column === undefined)
1687
- invalid3(`Unknown conflict-expression field "${part.column}"`);
1688
- return Object.freeze({ ...part, column });
1689
- }
1690
- if ("greatest" in part)
1691
- return Object.freeze({ greatest: Object.freeze(part.greatest.map((fragment) => mapConflictColumns({ ...params, fragment }))) });
1692
- return part;
1693
- }));
1829
+ function extractSqliteIndexKeys({ definition, index }) {
1830
+ return extractStandaloneIndexKeys({ definition, index });
1694
1831
  }
1695
- function compileSql(params) {
1696
- const collector = new ParamCollector(params.dialect);
1697
- const text = renderSqlFragment({ dialect: params.dialect, fragment: params.query, collector });
1698
- return { text, values: collector.values };
1832
+
1833
+ // packages/sql/src/dialects/postgres-index-expressions.ts
1834
+ function extractPostgresIndexKeys({ definition, index }) {
1835
+ return extractStandaloneIndexKeys({ definition, index, postgres: true });
1836
+ }
1837
+ function renderPostgresIndexExpressionProbe({ ...params }) {
1838
+ const { dialect, operation, probe } = params;
1839
+ if (operation === "context")
1840
+ return [{ text: "SELECT current_setting('server_version') AS version, current_setting('server_version_num') AS version_number, current_setting('search_path') AS search_path", values: [], returning: "none" }];
1841
+ const table = dialect.quoteIdent({ name: probe.table });
1842
+ if (operation === "read")
1843
+ return [renderPostgresIndexKeys({ dialect, table: { namespace: "pg_temp", name: probe.table }, expressionMetadata: true })];
1844
+ if (operation === "drop")
1845
+ return [{ text: `DROP TABLE pg_temp.${table}`, values: [], returning: "none" }];
1846
+ if (operation === "clone") {
1847
+ if (params.table === undefined)
1848
+ throw unsupportedCapability9({ dialect: "postgres", feature: "indexExpressionProbeSource" });
1849
+ return [{ text: `CREATE TEMP TABLE ${table} (LIKE ${dialect.quoteIdent({ name: params.table.namespace })}.${dialect.quoteIdent({ name: params.table.name })} INCLUDING DEFAULTS INCLUDING GENERATED INCLUDING IDENTITY INCLUDING STORAGE INCLUDING CONSTRAINTS)`, values: [], returning: "none" }];
1850
+ }
1851
+ if (params.keys === undefined)
1852
+ throw unsupportedCapability9({ dialect: "postgres", feature: "indexExpressionProbeKeys" });
1853
+ if (params.nullsNotDistinct === true && params.unique !== true)
1854
+ throw unsupportedCapability9({ dialect: "postgres", feature: "nonUniqueNullsNotDistinct" });
1855
+ const keys = renderIndexKeys({ dialect, keys: params.keys, unique: params.unique === true });
1856
+ return [{ text: `CREATE ${params.unique ? "UNIQUE " : ""}INDEX ${dialect.quoteIdent({ name: probe.index })} ON pg_temp.${table} (${keys.join(", ")})${params.nullsNotDistinct ? " NULLS NOT DISTINCT" : ""}${params.where === undefined ? "" : ` WHERE ${params.where}`}`, values: [], returning: "none" }];
1699
1857
  }
1700
- function sqlUsesIncoming(params) {
1701
- return partsOf(params.fragment).some((part) => typeof part !== "string" && (("row" in part) && part.row === "incoming" || ("greatest" in part) && part.greatest.some((fragment) => sqlUsesIncoming({ fragment }))));
1858
+
1859
+ // packages/sql/src/dialects/mysql-index-expressions.ts
1860
+ import { MYSQL_FUNCTIONAL_KEY_LIMITS, mysqlIndexKeyWidth, unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
1861
+
1862
+ // packages/sql/src/dialects/mysql-generated-columns.ts
1863
+ import { VibeError as VibeError10, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
1864
+
1865
+ // packages/sql/src/dialects/sqlite-generated-columns.ts
1866
+ import { VibeError as VibeError9 } from "@vibeorm/schema";
1867
+ function renderSqliteGeneratedColumnClause({ generated }) {
1868
+ return `GENERATED ALWAYS AS (${generated.expression}) STORED`;
1869
+ }
1870
+ function unavailable2({ column }) {
1871
+ return new VibeError9({ code: "VIBE_MIGRATION", message: `Cannot inspect generated expression for column "${column}"`, meta: { reason: "generated-expression-unavailable", column } });
1872
+ }
1873
+ function extractGenerationFromDefinition({ definition, column, backslashEscapes = false, mysql = false }) {
1874
+ const matches = tableColumnClauses({ sql: definition, backslashEscapes, mysql }).filter((clause) => clause.name === column);
1875
+ if (matches.length !== 1)
1876
+ throw unavailable2({ column });
1877
+ const columnSql = matches[0].sql;
1878
+ const tokens = tableDefinitionTokens({ sql: columnSql, backslashEscapes }).filter((token) => token.depth === 0);
1879
+ const name = tokens[0];
1880
+ let result;
1881
+ for (let index = 1;index < tokens.length; index++) {
1882
+ const token = tokens[index];
1883
+ if (token.kind !== "word" || token.value.toUpperCase() !== "AS")
1884
+ continue;
1885
+ const open = tokens[index + 1];
1886
+ const close = tokens[index + 2];
1887
+ if (open?.kind !== "symbol" || open.value !== "(" || close?.kind !== "symbol" || close.value !== ")")
1888
+ throw unavailable2({ column });
1889
+ if (result !== undefined)
1890
+ throw unavailable2({ column });
1891
+ const expression = columnSql.slice(open.end, close.start).trim();
1892
+ if (expression.length === 0 || expression.includes("\x00"))
1893
+ throw unavailable2({ column });
1894
+ const preceding = tokens[index - 1];
1895
+ const generated = preceding?.kind === "word" && preceding.value.toUpperCase() === "ALWAYS" ? tokens[index - 2] : preceding;
1896
+ const explicit = generated?.kind === "word" && generated.value.toUpperCase() === "GENERATED";
1897
+ if (preceding?.kind === "word" && preceding.value.toUpperCase() === "ALWAYS" && !explicit)
1898
+ throw unavailable2({ column });
1899
+ const next = tokens[index + 3];
1900
+ const storage = next?.kind === "word" && next.value.toUpperCase() === "STORED" ? "stored" : "virtual";
1901
+ result = { expression, storage, columnSql, typeSql: columnSql.slice(name.end, explicit ? generated.start : token.start).trim() };
1902
+ index += 2;
1903
+ }
1904
+ if (result === undefined && tokens.some((token, index) => token.kind === "word" && token.value.toUpperCase() === "GENERATED" && tokens[index + 1]?.kind === "word" && tokens[index + 1]?.value.toUpperCase() === "ALWAYS"))
1905
+ throw unavailable2({ column });
1906
+ return result;
1702
1907
  }
1703
- // packages/sql/src/query-plan.ts
1704
- import { VibeError as VibeError10 } from "@vibeorm/schema";
1705
- var queries = new WeakMap;
1706
- var commands = new WeakMap;
1707
- var reusableQueries = new WeakMap;
1708
- var reusableCommands = new WeakMap;
1709
- registerSqlHandleCheck({ check: (value) => queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) });
1710
- function invalid4(message) {
1711
- throw new VibeError10({ code: "VIBE_VALIDATION", message });
1908
+ function extractSqliteGeneratedColumn({ definition, column }) {
1909
+ return extractGenerationFromDefinition({ definition, column });
1712
1910
  }
1713
- function createSqlQuery(params) {
1714
- if (typeof params.render !== "function" || typeof params.decoder?.decode !== "function")
1715
- invalid4("SQL queries require a renderer and result decoder");
1716
- const query = Object.freeze(Object.create(null));
1717
- queries.set(query, Object.freeze({ render: params.render, decoder: params.decoder }));
1718
- return query;
1911
+
1912
+ // packages/sql/src/dialects/mysql-generated-columns.ts
1913
+ function renderMysqlGeneratedColumnClause({ generated }) {
1914
+ return `GENERATED ALWAYS AS (${generated.expression}) STORED`;
1915
+ }
1916
+ function renderMysqlGeneratedColumnProbe({ dialect, probe }) {
1917
+ if (dialect.name !== "mysql")
1918
+ throw unsupportedCapability10({ dialect: dialect.name, feature: "temporaryGeneratedColumnProbe" });
1919
+ if (probe.operation === "context")
1920
+ return { text: "SELECT @@SESSION.sql_mode AS sqlMode", values: [], returning: "none" };
1921
+ const q = ({ name }) => dialect.quoteIdent({ name });
1922
+ const name = q({ name: probe.probe });
1923
+ let text;
1924
+ switch (probe.operation) {
1925
+ case "clone":
1926
+ text = `CREATE TEMPORARY TABLE ${name} LIKE ${q({ name: probe.table.namespace })}.${q({ name: probe.table.name })}`;
1927
+ break;
1928
+ case "read":
1929
+ text = `SHOW CREATE TABLE ${probe.table === undefined ? name : `${q({ name: probe.table.namespace })}.${q({ name: probe.table.name })}`}`;
1930
+ break;
1931
+ case "drop":
1932
+ text = `DROP TEMPORARY TABLE ${name}`;
1933
+ break;
1934
+ case "create": {
1935
+ const parts = tableDefinitionParts({ sql: probe.definition, backslashEscapes: probe.backslashEscapes });
1936
+ const columns = tableColumnClauses({ sql: probe.definition, backslashEscapes: probe.backslashEscapes, mysql: true });
1937
+ const targets = columns.filter((column) => column.name === probe.column);
1938
+ if (targets.length > 1)
1939
+ throw unavailable3({ column: probe.column });
1940
+ const targetDefinition = extractMysqlGeneratedColumn({ definition: `CREATE TABLE ${name} (${probe.columnDefinition})`, column: probe.column, backslashEscapes: probe.backslashEscapes });
1941
+ if (targetDefinition?.storage !== "stored")
1942
+ throw unavailable3({ column: probe.column });
1943
+ const clauses = [...parts.clauses];
1944
+ if (targets.length === 1) {
1945
+ clauses[clauses.indexOf(targets[0].sql)] = probe.columnDefinition;
1946
+ } else if (probe.after !== undefined) {
1947
+ const after = columns.filter((column) => column.name === probe.after);
1948
+ if (after.length !== 1)
1949
+ throw unavailable3({ column: probe.after });
1950
+ clauses.splice(clauses.indexOf(after[0].sql) + 1, 0, probe.columnDefinition);
1951
+ } else {
1952
+ const last = columns.at(-1);
1953
+ if (last === undefined)
1954
+ throw unavailable3({ column: probe.column });
1955
+ clauses.splice(clauses.indexOf(last.sql) + 1, 0, probe.columnDefinition);
1956
+ }
1957
+ text = `CREATE TEMPORARY TABLE ${name} (
1958
+ ${clauses.join(`,
1959
+ `)}
1960
+ )${parts.suffix}`;
1961
+ break;
1962
+ }
1963
+ }
1964
+ return { text, values: [], returning: "none" };
1719
1965
  }
1720
- function createSqlCommand(params) {
1721
- if (typeof params.render !== "function")
1722
- invalid4("SQL commands require a renderer");
1723
- const command = Object.freeze(Object.create(null));
1724
- commands.set(command, params.render);
1725
- return command;
1966
+ function unavailable3({ column }) {
1967
+ return new VibeError10({ code: "VIBE_MIGRATION", message: `Cannot build temporary generated-column definition for "${column}"`, meta: { reason: "generated-expression-unavailable", column } });
1726
1968
  }
1727
- function isSqlQuery(value) {
1728
- return typeof value === "object" && value !== null && queries.has(value);
1969
+ function extractMysqlGeneratedColumn({ definition, column, backslashEscapes }) {
1970
+ return extractGenerationFromDefinition({ definition, column, backslashEscapes, mysql: true });
1729
1971
  }
1730
- function isSqlCommand(value) {
1731
- return typeof value === "object" && value !== null && commands.has(value);
1972
+
1973
+ // packages/sql/src/dialects/mysql-index-expressions.ts
1974
+ function assertMysqlIndexKeyStorage({ model, terms }) {
1975
+ const width = mysqlIndexKeyWidth({ terms, model });
1976
+ if (width.kind === "refused")
1977
+ throw unsupportedCapability11({ dialect: "mysql", feature: width.feature, hint: `${model.name}.${width.field}: use bounded VARCHAR/CHAR, an explicit raw cast with intentional width/collation, or a generated column; no automatic cast is inserted` });
1978
+ if (width.kind === "bytes" && width.bytes > MYSQL_FUNCTIONAL_KEY_LIMITS.bytes)
1979
+ throw unsupportedCapability11({ dialect: "mysql", feature: "mysqlFunctionalKeyWidth", hint: `The complete key needs up to ${width.bytes} bytes; modern InnoDB admits at most ${MYSQL_FUNCTIONAL_KEY_LIMITS.bytes}. Use narrower declared storage or an explicit reviewed raw key` });
1980
+ }
1981
+ function extractMysqlIndexKeys({ definition, index, backslashEscapes }) {
1982
+ const matching = tableDefinitionParts({ sql: definition, backslashEscapes }).clauses.flatMap((sql) => {
1983
+ const tokens = tableDefinitionTokens({ sql, backslashEscapes });
1984
+ const top = tokens.filter((token) => token.depth === 0);
1985
+ let position = top[0]?.kind === "word" && top[0].value.toUpperCase() === "UNIQUE" ? 1 : 0;
1986
+ const key = top[position++];
1987
+ if (key?.kind !== "word" || !/^(KEY|INDEX)$/i.test(key.value))
1988
+ return [];
1989
+ const name = top[position++];
1990
+ if (name === undefined || !["word", "quoted"].includes(name.kind) || name.value !== index)
1991
+ return [];
1992
+ if (top[position]?.kind === "word" && top[position]?.value.toUpperCase() === "USING") {
1993
+ position++;
1994
+ if (top[position++]?.value.toUpperCase() !== "BTREE")
1995
+ return [{ sql, tokens, open: undefined, close: undefined }];
1996
+ }
1997
+ const open = top[position++];
1998
+ const close = top[position++];
1999
+ const tail = top.slice(position);
2000
+ const supported = tail.length === 0 || tail.length === 1 && tail[0]?.value.toUpperCase() === "VISIBLE" || tail.length === 2 && tail[0]?.value.toUpperCase() === "USING" && tail[1]?.value.toUpperCase() === "BTREE";
2001
+ return [{ sql, tokens, open: supported && open?.value === "(" ? open : undefined, close: close?.value === ")" ? close : undefined }];
2002
+ });
2003
+ if (matching.length !== 1)
2004
+ return;
2005
+ const clause = matching[0];
2006
+ if (clause.open === undefined || clause.close === undefined)
2007
+ return;
2008
+ return extractIndexKeyList({ ...clause, open: clause.open, close: clause.close, wrapped: true });
2009
+ }
2010
+ function renderMysqlIndexExpressionProbe({ ...params }) {
2011
+ const { dialect, probe, operation } = params;
2012
+ if (operation === "context")
2013
+ return [{ text: "SELECT VERSION() AS version, @@SESSION.sql_mode AS sqlMode, @@SESSION.character_set_connection AS characterSet, @@SESSION.collation_connection AS collation", values: [], returning: "none" }];
2014
+ if (operation === "clone") {
2015
+ if (params.table === undefined)
2016
+ throw unsupportedCapability11({ dialect: "mysql", feature: "indexExpressionProbeSource" });
2017
+ return [renderMysqlGeneratedColumnProbe({ dialect, probe: { operation: "clone", probe: probe.table, table: params.table } })];
2018
+ }
2019
+ if (operation === "read" || operation === "drop")
2020
+ return [renderMysqlGeneratedColumnProbe({ dialect, probe: { operation, probe: probe.table } })];
2021
+ if (params.definition === undefined || params.keys === undefined)
2022
+ throw unsupportedCapability11({ dialect: "mysql", feature: "indexExpressionProbeDefinition" });
2023
+ if (params.where !== undefined || params.nullsNotDistinct === true)
2024
+ throw unsupportedCapability11({ dialect: "mysql", feature: "partialOrNullsNotDistinctExpressionIndex" });
2025
+ if (params.backslashEscapes === undefined)
2026
+ throw unsupportedCapability11({ dialect: "mysql", feature: "indexExpressionProbeSqlMode", hint: "Read sqlMode on the pinned session and supply backslashEscapes explicitly" });
2027
+ const parts = tableDefinitionParts({ sql: params.definition, backslashEscapes: params.backslashEscapes });
2028
+ const clauses = parts.clauses.filter((sql) => {
2029
+ const top = tableDefinitionTokens({ sql, backslashEscapes: params.backslashEscapes }).filter((token) => token.depth === 0);
2030
+ const position = top[0]?.kind === "word" && /^UNIQUE$/i.test(top[0].value) ? 1 : 0;
2031
+ return !(top[position]?.kind === "word" && /^(KEY|INDEX)$/i.test(top[position].value) && top[position + 1]?.value === probe.index);
2032
+ });
2033
+ const keys = renderIndexKeys({ dialect, keys: params.keys, unique: params.unique === true });
2034
+ clauses.push(`${params.unique ? "UNIQUE " : ""}KEY ${dialect.quoteIdent({ name: probe.index })} (${keys.join(", ")})`);
2035
+ return [{ text: `CREATE TEMPORARY TABLE ${dialect.quoteIdent({ name: probe.table })} (
2036
+ ${clauses.join(`,
2037
+ `)}
2038
+ )${parts.suffix}`, values: [], returning: "none" }];
1732
2039
  }
1733
- function isReusableSqlQuery(value) {
1734
- return typeof value === "object" && value !== null && reusableQueries.has(value);
2040
+
2041
+ // packages/sql/src/index-expressions.ts
2042
+ function resolveIndexKeys({ index, model, dialect, enums = [], site }) {
2043
+ const computed = isExpressionIndex({ index });
2044
+ if (computed)
2045
+ indexKeyDefinitionKey({ index, model });
2046
+ const terms = indexKeyTerms({ index });
2047
+ if (computed && index.where !== undefined && !dialect.capabilities.partialIndexes)
2048
+ throw unsupportedCapability12({ dialect: dialect.name, feature: "partialIndexes" });
2049
+ if (computed && index.nullsNotDistinct === true && dialect.name !== "postgres")
2050
+ throw unsupportedCapability12({ dialect: dialect.name, feature: "nullsNotDistinct" });
2051
+ if (computed && !dialect.capabilities.expressionIndexes)
2052
+ throw unsupportedCapability12({ dialect: dialect.name, feature: "expressionIndexes" });
2053
+ if (computed && (index.fields.length !== 0 || !terms.some((term) => term.kind !== "field")))
2054
+ throw invalid3({ site, reason: "index-expression-terms" });
2055
+ if (terms.length === 0)
2056
+ throw invalid3({ site, reason: "index-empty-keys" });
2057
+ if (computed && (index.clientName !== undefined || index.constraintName !== undefined))
2058
+ throw invalid3({ site, reason: "index-expression-selector" });
2059
+ if (computed && (index.kind !== "btree" && index.kind !== "unique" || terms.some((term) => term.kind === "field" && term.field.operatorClass !== undefined)))
2060
+ throw unsupportedCapability12({ dialect: dialect.name, feature: "expressionIndexOperatorClasses" });
2061
+ const keys = terms.map((term) => {
2062
+ if (term.kind === "field") {
2063
+ const field = model.fields.find((candidate) => candidate.name === term.field.name);
2064
+ if (field === undefined)
2065
+ throw invalid3({ site, reason: "index-unknown-field" });
2066
+ return { kind: "field", name: field.dbName ?? field.name, desc: term.field.sort === "desc", ...dialect.name === "sqlite" && field.type === "Decimal" ? { decimal: true } : {}, ...term.field.operatorClass === undefined ? {} : { operatorClass: term.field.operatorClass } };
2067
+ }
2068
+ if (term.kind === "raw") {
2069
+ if (index.name === undefined || typeof term.sql !== "string" || !term.sql.trim() || term.sql.includes("\x00"))
2070
+ throw invalid3({ site, reason: "index-raw-expression" });
2071
+ return { kind: "expression", text: term.sql, desc: term.sort === "desc" };
2072
+ }
2073
+ const rendered = renderSchemaExpression({ expression: term.expression, model, dialect, enums, usage: "indexExpression", site });
2074
+ if (!["lower", "upper", "add", "sub", "mul"].includes(term.expression.kind))
2075
+ throw invalid3({ site, reason: "index-expression-root" });
2076
+ if (rendered.resultType.refinement !== undefined || typeof rendered.resultType.type !== "string" || !["String", "Int", "BigInt", "Float", "Decimal"].includes(rendered.resultType.type))
2077
+ throw invalid3({ site, reason: "index-expression-result" });
2078
+ return { kind: "expression", text: rendered.text, desc: term.sort === "desc" };
2079
+ });
2080
+ if (computed && dialect.name === "mysql")
2081
+ assertMysqlIndexKeyStorage({ dialect, model, terms });
2082
+ return keys;
2083
+ }
2084
+ function renderIndexKeys({ dialect, keys, unique }) {
2085
+ if (keys.length === 0)
2086
+ throw invalid3({ site: "index", reason: "index-empty-keys" });
2087
+ const computed = keys.some((key) => key.kind === "expression");
2088
+ if (computed && !dialect.capabilities.expressionIndexes)
2089
+ throw unsupportedCapability12({ dialect: dialect.name, feature: "expressionIndexes" });
2090
+ if (computed && keys.some((key) => key.kind === "field" && key.operatorClass !== undefined))
2091
+ throw unsupportedCapability12({ dialect: dialect.name, feature: "expressionIndexOperatorClasses" });
2092
+ return keys.map((key) => {
2093
+ if (key.kind === "field") {
2094
+ if (dialect.name === "sqlite" && key.decimal === true)
2095
+ return `CAST(${dialect.quoteIdent({ name: key.name })} AS REAL)${key.desc ? " DESC" : ""}`;
2096
+ return renderIndexColumns({ dialect, columns: [key], unique })[0];
2097
+ }
2098
+ if (!key.text.trim() || key.text.includes("\x00"))
2099
+ throw invalid3({ site: "index", reason: "index-raw-expression" });
2100
+ const expression = dialect.name === "sqlite" ? key.text : `(${key.text})`;
2101
+ return `${expression}${key.desc ? " DESC" : ""}`;
2102
+ });
1735
2103
  }
1736
- function isReusableSqlCommand(value) {
1737
- return typeof value === "object" && value !== null && reusableCommands.has(value);
2104
+ function extractIndexKeys({ dialect, definition, index, backslashEscapes = true }) {
2105
+ switch (dialect) {
2106
+ case "postgres":
2107
+ return extractPostgresIndexKeys({ definition, index });
2108
+ case "sqlite":
2109
+ return extractSqliteIndexKeys({ definition, index });
2110
+ case "mysql":
2111
+ return extractMysqlIndexKeys({ definition, index, backslashEscapes });
2112
+ }
2113
+ }
2114
+ function renderIndexExpressionProbe({ ...params }) {
2115
+ if (params.operation === "context" && params.dialect.name === "sqlite")
2116
+ return [{ text: "SELECT sqlite_version() AS version", values: [], returning: "none" }];
2117
+ if (!params.dialect.capabilities.expressionIndexes)
2118
+ throw unsupportedCapability12({ dialect: params.dialect.name, feature: "expressionIndexes" });
2119
+ switch (params.dialect.name) {
2120
+ case "postgres":
2121
+ return renderPostgresIndexExpressionProbe(params);
2122
+ case "mysql":
2123
+ return renderMysqlIndexExpressionProbe(params);
2124
+ case "sqlite":
2125
+ throw unsupportedCapability12({ dialect: "sqlite", feature: "indexExpressionNormalizationProbe", hint: "SQLite expression indexes use quote-aware source extraction, not a normalization probe" });
2126
+ }
2127
+ }
2128
+ function assertIndexExpressionVersion({ dialect, version }) {
2129
+ if (dialect === "postgres")
2130
+ return;
2131
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:[-\s].*)?$/);
2132
+ const parts = match?.slice(1, 4).map(Number);
2133
+ const minimum = dialect === "mysql" ? [8, 0, 13] : [3, 9, 0];
2134
+ const admitted = parts !== undefined && !/mariadb/i.test(version) && parts.some((part, ordinal) => part > minimum[ordinal] && parts.slice(0, ordinal).every((prefix, position) => prefix === minimum[position])) || parts !== undefined && parts.every((part, ordinal) => part === minimum[ordinal]);
2135
+ if (!admitted)
2136
+ throw unsupportedCapability12({ dialect, feature: "expressionIndexServerVersion", hint: `Expression indexes require a known ${dialect} version at least ${minimum.join(".")}; received ${JSON.stringify(version)}` });
1738
2137
  }
1739
- function createReusableSqlQuery(params) {
1740
- const state = queries.get(params.query) ?? invalid4("Expected an authentic SQL row query");
1741
- sqlInputScopeKeys({ scope: params.scope });
1742
- const query = Object.freeze(Object.create(null));
1743
- reusableQueries.set(query, { ...state, scope: params.scope, cache: new WeakMap });
1744
- return query;
2138
+ function invalid3({ site, reason }) {
2139
+ return new VibeError11({ code: "VIBE_SCHEMA_INVALID", message: `Invalid index keys at ${site}: ${reason}`, meta: { rule: reason, site } });
1745
2140
  }
1746
- function createReusableSqlCommand(params) {
1747
- const render = commands.get(params.query) ?? invalid4("Expected an authentic SQL command");
1748
- sqlInputScopeKeys({ scope: params.scope });
1749
- const query = Object.freeze(Object.create(null));
1750
- reusableCommands.set(query, { render, scope: params.scope, cache: new WeakMap });
1751
- return query;
2141
+ // packages/sql/src/dialects/module-dependency.ts
2142
+ function renderModuleDependencyQuery(params) {
2143
+ const { dialect, operation } = params;
2144
+ if (operation.kind === "visibility")
2145
+ return dialect.name === "mysql" ? { text: "SHOW GRANTS FOR CURRENT_USER", values: [], returning: "none" } : undefined;
2146
+ if (dialect.name === "sqlite" || operation.tables.length === 0)
2147
+ return;
2148
+ const collector = new ParamCollector(dialect);
2149
+ const owned = operation.tables.map((table, index) => `SELECT ${collector.add(operation.namespace)}${index === 0 ? " AS namespace" : ""}, ${collector.add(table)}${index === 0 ? " AS table_name" : ""}`).join(" UNION ALL ");
2150
+ const pg = dialect.name === "postgres";
2151
+ const nameKey = ({ column }) => pg ? column : `CAST(IF(@@lower_case_table_names = 0, ${column}, LOWER(${column})) AS BINARY)`;
2152
+ const isOwned = ({ namespace, table }) => `EXISTS (SELECT 1 FROM owned_tables o WHERE ${nameKey({ column: namespace })} = ${nameKey({ column: "o.namespace" })} AND ${nameKey({ column: table })} = ${nameKey({ column: "o.table_name" })})`;
2153
+ const sourceNamespace = pg ? "sn.nspname" : "fk.CONSTRAINT_SCHEMA";
2154
+ const sourceTable = pg ? "s.relname" : "fk.TABLE_NAME";
2155
+ const targetNamespace = pg ? "tn.nspname" : "fk.UNIQUE_CONSTRAINT_SCHEMA";
2156
+ const targetTable = pg ? "t.relname" : "fk.REFERENCED_TABLE_NAME";
2157
+ const from = pg ? "pg_catalog.pg_constraint fk JOIN pg_catalog.pg_class s ON s.oid = fk.conrelid JOIN pg_catalog.pg_namespace sn ON sn.oid = s.relnamespace JOIN pg_catalog.pg_class t ON t.oid = fk.confrelid JOIN pg_catalog.pg_namespace tn ON tn.oid = t.relnamespace WHERE fk.contype = 'f'" : "information_schema.REFERENTIAL_CONSTRAINTS fk";
2158
+ return {
2159
+ text: `WITH owned_tables AS (${owned})
2160
+ SELECT * FROM (SELECT ${pg ? "fk.conname" : "fk.CONSTRAINT_NAME"} AS constraint_name, ${sourceNamespace} AS source_namespace, ${sourceTable} AS source_table, ${targetNamespace} AS target_namespace, ${targetTable} AS target_table, ${isOwned({ namespace: sourceNamespace, table: sourceTable })} AS source_owned, ${isOwned({ namespace: targetNamespace, table: targetTable })} AS target_owned FROM ${from}) dependencies
2161
+ WHERE source_owned OR target_owned
2162
+ ORDER BY source_namespace, source_table, constraint_name`,
2163
+ values: collector.values,
2164
+ returning: "none"
2165
+ };
1752
2166
  }
1753
- function compiledTemplate(params) {
1754
- const cached = params.state.cache.get(params.dialect);
1755
- if (cached !== undefined)
1756
- return cached;
1757
- const collector = new ParamCollector(params.dialect);
1758
- startSqlBindingCollection({ collector, dialect: params.dialect, scope: params.state.scope });
1759
- const text = params.state.render({ dialect: params.dialect, collector });
1760
- if (collector.count() > params.dialect.paramBudget)
1761
- invalid4("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
1762
- const program = finishSqlBindingCollection({ collector });
1763
- const compiled = Object.freeze({ text, program });
1764
- params.state.cache.set(params.dialect, compiled);
1765
- return compiled;
2167
+ // packages/sql/src/dialects/migration-replay-coverage.ts
2168
+ var extensionMember = (classid, objid) => `EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = '${classid}'::regclass AND d.objid = ${objid} AND d.deptype = 'e')`;
2169
+ var extensionOwnedNamespace = `(EXISTS (SELECT 1 FROM pg_catalog.pg_extension e WHERE e.extnamespace = n.oid) OR ${extensionMember("pg_namespace", "n.oid")}) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_class c WHERE c.relnamespace = n.oid AND c.relkind IN ('r', 'p', 'v', 'm', 'f', 'S') AND NOT ${extensionMember("pg_class", "c.oid")}) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_proc p WHERE p.pronamespace = n.oid AND NOT ${extensionMember("pg_proc", "p.oid")}) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_type t WHERE t.typnamespace = n.oid AND (t.typrelid = 0 AND t.typelem = 0 OR t.typtype = 'c' AND EXISTS (SELECT 1 FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid AND c.relkind = 'c')) AND NOT ${extensionMember("pg_type", "t.oid")})`;
2170
+ function renderReplayCoverageGaps(params) {
2171
+ const { dialect } = params;
2172
+ const collector = new ParamCollector(dialect);
2173
+ const namespace = collector.add(params.namespace);
2174
+ if (dialect.name === "postgres")
2175
+ return { text: `SELECT 'type' AS kind, t.typname::text AS name, n.nspname::text AS namespace, NULL::text AS parent FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} AND (t.typtype IN ('b', 'd', 'r', 'm') AND t.typelem = 0 OR t.typtype = 'c' AND EXISTS (SELECT 1 FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid AND c.relkind = 'c')) 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')
2176
+ UNION ALL SELECT 'routine', p.proname::text, n.nspname::text, NULL::text FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = ${namespace} AND p.prokind <> 'f' AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_proc'::regclass AND d.objid = p.oid AND d.deptype = 'e')
2177
+ UNION ALL SELECT 'relation-layout', c.relname::text, n.nspname::text, NULL::text FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND (c.relkind IN ('p', 'f') OR c.relispartition OR EXISTS (SELECT 1 FROM pg_catalog.pg_inherits i WHERE i.inhrelid = c.oid)) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_class'::regclass AND d.objid = c.oid AND d.deptype = 'e')
2178
+ UNION ALL SELECT 'rule', r.rulename::text, n.nspname::text, c.relname::text FROM pg_catalog.pg_rewrite r JOIN pg_catalog.pg_class c ON c.oid = r.ev_class JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND r.rulename <> '_RETURN' AND NOT ${extensionMember("pg_class", "c.oid")}
2179
+ UNION ALL SELECT 'namespace', n.nspname::text, n.nspname::text, NULL::text FROM pg_catalog.pg_namespace n WHERE n.nspname <> ${namespace} AND n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND NOT (${extensionOwnedNamespace})${params.privateNamespace === undefined ? "" : ` AND n.nspname <> ${collector.add(params.privateNamespace)}`}`, values: collector.values, returning: "none" };
2180
+ if (dialect.name === "mysql")
2181
+ return { text: `SELECT 'procedure' AS kind, ROUTINE_NAME AS name, ROUTINE_SCHEMA AS namespace, NULL AS parent FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = ${namespace} AND ROUTINE_TYPE = 'PROCEDURE'
2182
+ UNION ALL SELECT 'event', EVENT_NAME, EVENT_SCHEMA, NULL FROM information_schema.EVENTS WHERE EVENT_SCHEMA = ${collector.add(params.namespace)}
2183
+ UNION ALL SELECT DISTINCT 'partition', TABLE_NAME, TABLE_SCHEMA, NULL FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = ${collector.add(params.namespace)} AND PARTITION_NAME IS NOT NULL`, values: collector.values, returning: "none" };
2184
+ return { text: `SELECT 'table-options' AS kind, name, schema AS namespace, NULL AS parent FROM pragma_table_list WHERE schema = ${namespace} AND name NOT LIKE 'sqlite\\_%' ESCAPE '\\' AND (type = 'virtual' OR wr <> 0 OR strict <> 0)`, values: collector.values, returning: "none" };
1766
2185
  }
1767
- function executionHandle(params) {
1768
- const { value } = params;
1769
- return queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) || isSqlFragment(value) || isSqlInput(value) || sqlInputScopeHandle(params);
2186
+ // packages/sql/src/dialects/migration-shadow.ts
2187
+ import { assertSqlNamespace, unsupportedCapability as unsupportedCapability13 } from "@vibeorm/schema";
2188
+ var SHADOW_DATABASE = Object.freeze({ schema: "_vibeorm_shadow", table: "_vibe_shadow", lock: 1447645267, timeoutSeconds: 60 });
2189
+ var SHADOW_CAPABILITIES = Object.freeze({
2190
+ postgres: Object.freeze({ serverProvisioning: true, dedicated: true }),
2191
+ mysql: Object.freeze({ serverProvisioning: true, dedicated: true }),
2192
+ sqlite: Object.freeze({ serverProvisioning: false, dedicated: false })
2193
+ });
2194
+ function renderShadowOperation(params) {
2195
+ const { dialect, operation } = params;
2196
+ if (!SHADOW_CAPABILITIES[dialect.name].serverProvisioning)
2197
+ throw unsupportedCapability13({ dialect: dialect.name, feature: "shadowDatabaseProvisioning" });
2198
+ const pg = dialect.name === "postgres";
2199
+ const collector = new ParamCollector(dialect);
2200
+ const marker = pg ? `${dialect.quoteIdent({ name: SHADOW_DATABASE.schema })}.${dialect.quoteIdent({ name: SHADOW_DATABASE.table })}` : dialect.quoteIdent({ name: SHADOW_DATABASE.table });
2201
+ const result = (text) => [{ text, values: collector.values, returning: "none" }];
2202
+ switch (operation.kind) {
2203
+ case "createDatabase":
2204
+ return result(`CREATE DATABASE ${dialect.quoteIdent({ name: operation.name })}${pg ? " TEMPLATE template0" : ""}`);
2205
+ case "dropDatabase":
2206
+ return result(`DROP DATABASE ${dialect.quoteIdent({ name: operation.name })}`);
2207
+ case "identity":
2208
+ return result(pg ? `SELECT current_database()::text AS database, (SELECT oid::text FROM pg_catalog.pg_database WHERE datname = current_database()) AS database_id, current_schema()::text AS namespace` : `SELECT DATABASE() AS \`database\`, @@lower_case_table_names AS name_folding, @@server_uuid AS server_id`);
2209
+ case "lock":
2210
+ return result(pg ? `SELECT pg_catalog.pg_advisory_lock(${collector.add(SHADOW_DATABASE.lock)})` : `SELECT GET_LOCK(${collector.add(`vibeorm_shadow:${operation.database}`)}, ${collector.add(SHADOW_DATABASE.timeoutSeconds)}) AS acquired`);
2211
+ case "unlock":
2212
+ return result(pg ? `SELECT pg_catalog.pg_advisory_unlock(${collector.add(SHADOW_DATABASE.lock)}) AS released` : `SELECT RELEASE_LOCK(${collector.add(`vibeorm_shadow:${operation.database}`)}) AS released`);
2213
+ case "markerExists":
2214
+ return result(pg ? `SELECT c.relname AS name FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${collector.add(SHADOW_DATABASE.schema)} AND c.relname = ${collector.add(SHADOW_DATABASE.table)}` : `SELECT TABLE_NAME AS name FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ${collector.add(SHADOW_DATABASE.table)}`);
2215
+ case "markerRead":
2216
+ return result(`SELECT payload FROM ${marker}`);
2217
+ case "markerColumns":
2218
+ return result(`SELECT column_name AS name, data_type AS type, is_nullable AS nullable FROM information_schema.columns AS marker_column WHERE table_schema = ${pg ? collector.add(SHADOW_DATABASE.schema) : "DATABASE()"} AND table_name = ${collector.add(SHADOW_DATABASE.table)} AND ${pg ? `EXISTS (SELECT 1 FROM pg_catalog.pg_class r JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace WHERE n.nspname = marker_column.table_schema AND r.relname = marker_column.table_name AND r.relkind = 'r' AND r.relpersistence = 'p' AND NOT r.relrowsecurity AND NOT r.relforcerowsecurity AND NOT r.relhasrules AND NOT r.relhastriggers AND NOT r.relispartition AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_attrdef a WHERE a.adrelid = r.oid) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_constraint c WHERE c.conrelid = r.oid AND c.contype <> 'n') AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_index i WHERE i.indrelid = r.oid) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_inherits i WHERE i.inhrelid = r.oid OR i.inhparent = r.oid) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_attribute a WHERE a.attrelid = r.oid AND (a.attisdropped OR a.attidentity <> '' OR a.attgenerated <> '')))` : `EXISTS (SELECT 1 FROM information_schema.TABLES t WHERE t.TABLE_SCHEMA = marker_column.table_schema AND t.TABLE_NAME = marker_column.table_name AND t.TABLE_TYPE = 'BASE TABLE' AND t.ENGINE = 'InnoDB') AND NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS c WHERE c.TABLE_SCHEMA = marker_column.table_schema AND c.TABLE_NAME = marker_column.table_name AND (c.COLUMN_DEFAULT IS NOT NULL OR c.EXTRA <> '')) AND NOT EXISTS (SELECT 1 FROM information_schema.TRIGGERS t WHERE t.EVENT_OBJECT_SCHEMA = DATABASE() AND t.EVENT_OBJECT_TABLE = ${collector.add(SHADOW_DATABASE.table)}) AND NOT EXISTS (SELECT 1 FROM information_schema.TABLE_CONSTRAINTS c WHERE c.TABLE_SCHEMA = DATABASE() AND c.TABLE_NAME = ${collector.add(SHADOW_DATABASE.table)})`}
2219
+ ORDER BY ordinal_position`);
2220
+ case "grants": {
2221
+ if (pg)
2222
+ throw unsupportedCapability13({ dialect: dialect.name, feature: "mysqlShadowPrivileges" });
2223
+ return result("SHOW GRANTS FOR CURRENT_USER");
2224
+ }
2225
+ case "markerCreate":
2226
+ return pg ? [{ text: `CREATE SCHEMA ${dialect.quoteIdent({ name: SHADOW_DATABASE.schema })}`, values: [], returning: "none" }, { text: `CREATE TABLE ${marker} (payload text NOT NULL)`, values: [], returning: "none" }] : result(`CREATE TABLE ${marker} (payload LONGTEXT NOT NULL) ENGINE=InnoDB`);
2227
+ case "markerWrite":
2228
+ return result(operation.insert ? `INSERT INTO ${marker} (payload) VALUES (${collector.add(operation.value)})` : `UPDATE ${marker} SET payload = ${collector.add(operation.value)}`);
2229
+ case "journal":
2230
+ return result(`SELECT name, checksum, statement_index, error FROM ${dialect.quoteIdent({ name: "_vibe_migrations" })} ORDER BY name`);
2231
+ case "inventory":
2232
+ return result(pg ? `SELECT 'namespace' AS kind, nspname::text AS name, nspname::text AS namespace FROM pg_catalog.pg_namespace WHERE nspname NOT IN ('public', 'information_schema') AND nspname NOT LIKE 'pg\\_%' ESCAPE '\\'
2233
+ UNION ALL SELECT 'relation', c.relname::text, n.nspname::text FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'
2234
+ UNION ALL SELECT 'routine', p.proname::text, n.nspname::text FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'
2235
+ UNION ALL SELECT 'type', t.typname::text, n.nspname::text FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND t.typrelid = 0 AND t.typelem = 0
2236
+ UNION ALL SELECT 'extension', e.extname::text, n.nspname::text FROM pg_catalog.pg_extension e JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace WHERE e.extname <> 'plpgsql'
2237
+ UNION ALL SELECT 'marker-dependency', d.objid::text, '${SHADOW_DATABASE.schema}' FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_class c ON d.classid = 'pg_class'::regclass AND d.objid = c.oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '${SHADOW_DATABASE.schema}' AND d.deptype NOT IN ('i', 'a') AND d.refclassid <> 'pg_namespace'::regclass
2238
+ UNION ALL SELECT 'marker-trigger', t.tgname::text, n.nspname::text FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '${SHADOW_DATABASE.schema}'
2239
+ UNION ALL SELECT 'namespace-object', o.name::text, n.nspname::text FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_namespace n ON d.refclassid = 'pg_namespace'::regclass AND d.refobjid = n.oid CROSS JOIN LATERAL pg_catalog.pg_identify_object(d.classid, d.objid, d.objsubid) o WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND d.classid NOT IN ('pg_class'::regclass, 'pg_proc'::regclass, 'pg_type'::regclass, 'pg_extension'::regclass)
2240
+ UNION ALL SELECT 'database-object', evtname::text, '' FROM pg_catalog.pg_event_trigger
2241
+ UNION ALL SELECT 'database-object', pubname::text, '' FROM pg_catalog.pg_publication
2242
+ UNION ALL SELECT 'database-object', subname::text, '' FROM pg_catalog.pg_subscription WHERE subdbid = (SELECT oid FROM pg_catalog.pg_database WHERE datname = current_database())
2243
+ UNION ALL SELECT 'database-object', srvname::text, '' FROM pg_catalog.pg_foreign_server
2244
+ UNION ALL SELECT 'database-object', fdwname::text, '' FROM pg_catalog.pg_foreign_data_wrapper
2245
+ UNION ALL SELECT 'database-object', c.oid::text, '' FROM pg_catalog.pg_cast c JOIN pg_catalog.pg_type s ON s.oid = c.castsource JOIN pg_catalog.pg_type t ON t.oid = c.casttarget LEFT JOIN pg_catalog.pg_proc p ON p.oid = c.castfunc WHERE s.typnamespace <> 'pg_catalog'::regnamespace OR t.typnamespace <> 'pg_catalog'::regnamespace OR p.pronamespace <> 'pg_catalog'::regnamespace` : `SELECT TABLE_TYPE AS kind, TABLE_NAME AS name, TABLE_SCHEMA AS namespace FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE()
2246
+ UNION ALL SELECT ROUTINE_TYPE, ROUTINE_NAME, ROUTINE_SCHEMA FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE()
2247
+ UNION ALL SELECT 'EVENT', EVENT_NAME, EVENT_SCHEMA FROM information_schema.EVENTS WHERE EVENT_SCHEMA = DATABASE()
2248
+ UNION ALL SELECT 'TRIGGER', TRIGGER_NAME, TRIGGER_SCHEMA FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = DATABASE()
2249
+ UNION ALL SELECT 'EXTERNAL FOREIGN KEY', CONCAT(TABLE_NAME, '.', CONSTRAINT_NAME), CONSTRAINT_SCHEMA FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE UNIQUE_CONSTRAINT_SCHEMA = DATABASE() AND CAST(CONSTRAINT_SCHEMA AS BINARY) <> CAST(DATABASE() AS BINARY)`);
2250
+ case "resetPostgres":
2251
+ case "resetDedicatedPostgres": {
2252
+ if (!pg)
2253
+ throw unsupportedCapability13({ dialect: dialect.name, feature: "postgresShadowReset" });
2254
+ const owned = operation.kind === "resetDedicatedPostgres" ? operation.ownedNamespaces : [];
2255
+ for (const namespace of owned)
2256
+ assertSqlNamespace({ namespace, dialect: "postgres", allowPublic: false });
2257
+ return [
2258
+ ...owned.map((namespace) => ({ text: `DROP SCHEMA IF EXISTS ${dialect.quoteIdent({ name: namespace })} CASCADE`, values: [], returning: "none" })),
2259
+ { text: 'DROP SCHEMA "public" CASCADE', values: [], returning: "none" },
2260
+ { text: 'CREATE SCHEMA "public"', values: [], returning: "none" }
2261
+ ];
2262
+ }
2263
+ case "mysqlForeignKeys": {
2264
+ if (pg)
2265
+ throw unsupportedCapability13({ dialect: dialect.name, feature: "mysqlShadowReset" });
2266
+ return result(`SET FOREIGN_KEY_CHECKS = ${operation.enabled ? "1" : "0"}`);
2267
+ }
2268
+ case "dropMysqlObject": {
2269
+ if (pg)
2270
+ throw unsupportedCapability13({ dialect: dialect.name, feature: "mysqlShadowReset" });
2271
+ return result(`DROP ${operation.object} ${dialect.quoteIdent({ name: operation.name })}`);
2272
+ }
2273
+ }
1770
2274
  }
1771
- function bindTemplate(params) {
1772
- const { scope } = params.state;
1773
- return bindSqlBindingProgram({ program: params.compiled.program, params: params.inputs, isHandle: (value) => executionHandle({ scope, value }) });
2275
+ // packages/sql/src/module-journal.ts
2276
+ var MODULE_JOURNAL_COLUMN = "module_origin";
2277
+ function renderModuleJournal(params) {
2278
+ const { dialect, operation } = params;
2279
+ const q = (name) => dialect.quoteIdent({ name });
2280
+ const table = q(params.table);
2281
+ const column = q(MODULE_JOURNAL_COLUMN);
2282
+ const collector = new ParamCollector(dialect);
2283
+ let text;
2284
+ switch (operation.kind) {
2285
+ case "columns":
2286
+ text = dialect.name === "sqlite" ? `PRAGMA table_info(${table})` : `SELECT column_name AS name FROM information_schema.columns WHERE table_schema = ${dialect.name === "postgres" ? "current_schema()" : "DATABASE()"} AND table_name = ${collector.add(params.table)}`;
2287
+ break;
2288
+ case "addColumn":
2289
+ text = `ALTER TABLE ${table} ADD COLUMN ${column} ${dialect.name === "mysql" ? "LONGTEXT" : "TEXT"}`;
2290
+ break;
2291
+ case "read":
2292
+ text = `SELECT * FROM ${table} ORDER BY ${q("id")}`;
2293
+ break;
2294
+ case "insert":
2295
+ text = `INSERT INTO ${table} (${["name", "checksum", "statement_index", MODULE_JOURNAL_COLUMN].map(q).join(", ")}) VALUES (${collector.addMany([operation.name, operation.checksum, operation.index, operation.entry]).join(", ")})`;
2296
+ break;
2297
+ case "complete":
2298
+ text = `UPDATE ${table} SET ${column} = ${collector.add(operation.entry)} WHERE ${q("name")} = ${collector.add(operation.name)}`;
2299
+ break;
2300
+ }
2301
+ return { text, values: collector.values };
1774
2302
  }
1775
- function compileReusableSqlQuery(params) {
1776
- const state = reusableQueries.get(params.query) ?? invalid4("Expected an authentic reusable SQL row query");
1777
- const compiled = compiledTemplate({ dialect: params.dialect, state });
1778
- return { text: compiled.text, values: bindTemplate({ compiled, state, inputs: params.params }), decoder: state.decoder };
1779
- }
1780
- function compileReusableSqlCommand(params) {
1781
- const state = reusableCommands.get(params.query) ?? invalid4("Expected an authentic reusable SQL command");
1782
- const compiled = compiledTemplate({ dialect: params.dialect, state });
1783
- return { text: compiled.text, values: bindTemplate({ compiled, state, inputs: params.params }) };
1784
- }
1785
- function compileSqlQuery(params) {
1786
- const state = queries.get(params.query) ?? invalid4("Expected an authentic SQL row query");
1787
- const collector = new ParamCollector(params.dialect);
1788
- const text = state.render({ dialect: params.dialect, collector });
1789
- if (collector.values.length > params.dialect.paramBudget)
1790
- invalid4("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
1791
- return { text, values: collector.values, decoder: state.decoder };
1792
- }
1793
- function compileSqlCommand(params) {
1794
- const render = commands.get(params.query) ?? invalid4("Expected an authentic SQL command");
1795
- const collector = new ParamCollector(params.dialect);
1796
- const text = render({ dialect: params.dialect, collector });
1797
- if (collector.values.length > params.dialect.paramBudget)
1798
- invalid4("SQL command exceeds this dialect's parameter budget; statements are never split implicitly");
1799
- return { text, values: collector.values };
2303
+ // packages/sql/src/capabilities.ts
2304
+ import {
2305
+ DIALECT_CAPABILITIES,
2306
+ POSTGRES_CAPABILITIES,
2307
+ SQLITE_CAPABILITIES,
2308
+ MYSQL_CAPABILITIES
2309
+ } from "@vibeorm/schema";
2310
+
2311
+ // packages/sql/src/identifiers.ts
2312
+ import { VibeError as VibeError12 } from "@vibeorm/schema";
2313
+ function assertNoNul(params) {
2314
+ if (params.name.includes("\x00")) {
2315
+ throw new VibeError12({
2316
+ code: "VIBE_VALIDATION",
2317
+ message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
2318
+ meta: { name: params.name.replaceAll("\x00", "\\0") }
2319
+ });
2320
+ }
2321
+ return params.name;
1800
2322
  }
1801
- // packages/sql/src/exclusion.ts
1802
- import { VibeError as VibeError12, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
1803
2323
 
1804
- // packages/sql/src/predicate.ts
1805
- import { VibeError as VibeError11, assertPredicateCapabilities, fieldTypeIdentity, isEnumType, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
1806
- function renderPredicate(params) {
1807
- assertPredicateCapabilities({ predicate: params.predicate, dialect: params.dialect.name, usage: params.usage });
1808
- if (params.usage === "exclusionPredicate" && !params.dialect.capabilities.exclusionConstraints)
1809
- throw unsupportedCapability9({ dialect: params.dialect.name, feature: "exclusionConstraints" });
1810
- if (params.usage === "indexPredicate" && !params.dialect.capabilities.partialIndexes) {
1811
- throw unsupportedCapability9({ dialect: params.dialect.name, feature: "partialIndexes" });
2324
+ // packages/sql/src/dialects/sqlite.ts
2325
+ var sqliteDialect = {
2326
+ name: "sqlite",
2327
+ capabilities: SQLITE_CAPABILITIES,
2328
+ inArrayStrategy: "expand",
2329
+ tupleInStrategy: "rowValue",
2330
+ keyListStrategy: "values",
2331
+ keysetSeekStrategy: "rowValue",
2332
+ keyPageStrategy: "inLimit",
2333
+ relationFilterStrategy: "inSelect",
2334
+ deferredJoinPage: null,
2335
+ nativeNullsOrder: "low",
2336
+ nullsUnionPage: false,
2337
+ timeOfDayInStrategy: "in",
2338
+ upsertForm: "onConflict",
2339
+ conflictKeyEquality: {
2340
+ foldingScalars: [],
2341
+ textIsBinaryByDefault: true,
2342
+ duplicatesInOneStatement: "lastWriteWins"
2343
+ },
2344
+ excludedRowRef: { form: "keyword", name: "excluded" },
2345
+ doNothingForm: "onConflictDoNothing",
2346
+ insensitiveLike: "lower",
2347
+ strictMatchStrategy: "glob",
2348
+ greatestForm: "frame",
2349
+ orderByNullsMode: "native",
2350
+ offsetRequiresLimit: { limitText: "-1" },
2351
+ likeEscapeClause: " ESCAPE '\\'",
2352
+ jsonPathMode: "sqliteExtract",
2353
+ supportedJsonOps: [
2354
+ "equals",
2355
+ "string_contains",
2356
+ "string_starts_with",
2357
+ "string_ends_with",
2358
+ "array_contains",
2359
+ "array_starts_with",
2360
+ "array_ends_with"
2361
+ ],
2362
+ jsonParamCast: "",
2363
+ valueCastTypes: Object.freeze({ String: "TEXT", Boolean: "INTEGER", Int: "INTEGER", BigInt: "INTEGER", Float: "REAL", Decimal: "TEXT", DateTime: "TEXT", Json: "TEXT", Bytes: "BLOB" }),
2364
+ lastInsertIdSql: null,
2365
+ rowLockSyntax: null,
2366
+ advisoryLockFunctions: null,
2367
+ rowDefaultKeyword: false,
2368
+ bulkInsertForm: "values",
2369
+ emptyInsert: { form: "defaultValues", conflictClause: false },
2370
+ statementChain: null,
2371
+ paramBudget: 900,
2372
+ booleanLiteral: { true: "1", false: "0" },
2373
+ quoteIdent({ name }) {
2374
+ return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
2375
+ },
2376
+ quoteStringLiteral({ value }) {
2377
+ return `'${value.replaceAll("'", "''")}'`;
2378
+ },
2379
+ placeholder() {
2380
+ return "?";
2381
+ },
2382
+ jsonTextProjection({ expression }) {
2383
+ return expression;
2384
+ },
2385
+ renderDbNow({ offsetSeconds, collector }) {
2386
+ if (offsetSeconds === 0)
2387
+ return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
2388
+ return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
1812
2389
  }
1813
- const context = {
1814
- model: params.model,
1815
- dialect: params.dialect,
1816
- enums: params.enums ?? [],
1817
- usage: params.usage,
1818
- site: params.site
2390
+ };
2391
+
2392
+ // packages/sql/src/dialects/sqlite-fts.ts
2393
+ function updateStatements(params) {
2394
+ const quote = (name) => sqliteDialect.quoteIdent({ name });
2395
+ const columns = params.columns.map((column) => quote(column)).join(", ");
2396
+ const oldValues = params.columns.map((column) => `old.${quote(column)}`).join(", ");
2397
+ const newValues = params.columns.map((column) => `new.${quote(column)}`).join(", ");
2398
+ const changes = [
2399
+ "old.rowid IS NOT new.rowid",
2400
+ ...params.columns.map((column) => `old.${quote(column)} COLLATE BINARY IS NOT new.${quote(column)} COLLATE BINARY`)
2401
+ ].join(" OR ");
2402
+ const target = `${quote(`${params.ftsTable}_au`)} AFTER UPDATE ON ${quote(params.table)}`;
2403
+ const body = `BEGIN
2404
+ INSERT INTO ${quote(params.ftsTable)}(${quote(params.ftsTable)}, rowid, ${columns}) VALUES ('delete', old.rowid, ${oldValues});
2405
+ INSERT INTO ${quote(params.ftsTable)}(rowid, ${columns}) VALUES (new.rowid, ${newValues});
2406
+ END`;
2407
+ const tail = `${target} WHEN (${changes}) ${body}`;
2408
+ return {
2409
+ create: `CREATE TRIGGER IF NOT EXISTS ${tail};`,
2410
+ definition: `CREATE TRIGGER ${tail}`,
2411
+ legacyDefinition: `CREATE TRIGGER ${target} ${body}`
1819
2412
  };
1820
- return renderNode({ node: params.predicate, context });
1821
2413
  }
1822
- var PRECEDENCE = {
1823
- or: 1,
1824
- and: 2,
1825
- not: 3,
1826
- compare: 4,
1827
- inList: 4,
1828
- isNull: 4,
1829
- isTrue: 4
1830
- };
1831
- var COMPARISON_SQL = {
1832
- eq: "=",
1833
- ne: "<>",
1834
- gt: ">",
1835
- gte: ">=",
1836
- lt: "<",
1837
- lte: "<="
1838
- };
1839
- var EXACT_INTEGER = /^-?(0|[1-9]\d*)$/;
1840
- var EXACT_DECIMAL = /^-?(0|[1-9]\d*)(\.\d+)?$/;
1841
- var INT32_MIN = -2147483648n;
1842
- var INT32_MAX = 2147483647n;
1843
- var INT64_MIN = -(2n ** 63n);
1844
- var INT64_MAX = 2n ** 63n - 1n;
1845
- function refuse2(params) {
1846
- const { context, rule, message } = params;
1847
- throw new VibeError11({
1848
- code: "VIBE_SCHEMA_INVALID",
1849
- message: `${context.site} on "${context.model.name}": ${message}`,
1850
- meta: { rule, model: context.model.name, site: context.site, ...params.meta ?? {} }
1851
- });
2414
+ function renderSqliteFtsUpdateTrigger(params) {
2415
+ const { create, definition } = updateStatements(params);
2416
+ return { create, definition };
1852
2417
  }
1853
- function renderChild(params) {
1854
- const { node, parent, context } = params;
1855
- if ((node.kind === "and" || node.kind === "or") && node.operands.length === 1) {
1856
- return renderChild({ node: node.operands[0], parent, context });
2418
+ function sqliteFtsUpdateUpgrade(params) {
2419
+ const quoted = '"(?:[^"]|"")+"';
2420
+ const body = new RegExp(`BEGIN\\n INSERT INTO (${quoted})\\((${quoted}), rowid, (${quoted}(?:, ${quoted})*)\\) VALUES \\('delete', old\\.rowid, `, "g");
2421
+ for (const match of params.create.matchAll(body)) {
2422
+ const ftsTable = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[1]})` })[0].name;
2423
+ const columns = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[3]})` }).map((column) => column.name);
2424
+ if (params.name !== `${ftsTable}_au`)
2425
+ continue;
2426
+ const statements = updateStatements({ table: params.table, ftsTable, columns });
2427
+ if (params.create !== statements.create)
2428
+ continue;
2429
+ const installed = params.installed === statements.legacyDefinition ? "legacy" : params.installed === statements.definition ? "current" : undefined;
2430
+ if (installed === undefined)
2431
+ continue;
2432
+ if (params.tableDefinition === undefined)
2433
+ return { kind: "tableRequired", ftsTable };
2434
+ const tableTail = `${sqliteDialect.quoteIdent({ name: ftsTable })} USING fts5(${columns.map((name) => sqliteDialect.quoteIdent({ name })).join(", ")}, content=${sqliteDialect.quoteStringLiteral({ value: params.table })})`;
2435
+ if (params.tableDefinition !== `CREATE VIRTUAL TABLE ${tableTail}` && params.tableDefinition !== `CREATE VIRTUAL TABLE IF NOT EXISTS ${tableTail};`)
2436
+ continue;
2437
+ return { kind: "recognized", ftsTable, legacyDefinition: statements.legacyDefinition, currentDefinition: statements.definition, drop: `DROP TRIGGER IF EXISTS ${sqliteDialect.quoteIdent({ name: `${ftsTable}_au` })};`, installed };
1857
2438
  }
1858
- const text = renderNode({ node, context });
1859
- return PRECEDENCE[node.kind] < PRECEDENCE[parent] ? `(${text})` : text;
2439
+ return;
1860
2440
  }
1861
- function renderNode(params) {
1862
- const { node, context } = params;
1863
- switch (node.kind) {
1864
- case "and":
1865
- case "or": {
1866
- if (node.operands.length === 0) {
1867
- refuse2({
1868
- context,
1869
- rule: "predicate-empty",
1870
- message: `${node.kind === "and" ? "and()" : "or()"} has no operands \u2014 an empty boolean group has no honest SQL spelling`
1871
- });
1872
- }
1873
- if (node.operands.length === 1)
1874
- return renderNode({ node: node.operands[0], context });
1875
- const separator = node.kind === "and" ? " AND " : " OR ";
1876
- return node.operands.map((operand) => renderChild({ node: operand, parent: node.kind, context })).join(separator);
1877
- }
1878
- case "not":
1879
- return `NOT ${renderChild({ node: node.operand, parent: "not", context })}`;
1880
- case "isTrue":
1881
- return `COALESCE(${renderNode({ node: node.operand, context })}, FALSE)`;
1882
- case "isNull": {
1883
- const field = resolveField({ name: node.field.name, context, nullTest: true });
1884
- return `${quoteColumn({ field, context })} IS ${node.negate === true ? "NOT NULL" : "NULL"}`;
1885
- }
1886
- case "compare": {
1887
- const left = renderExpression({ expression: node.left, context });
1888
- const right = renderOperand({ operand: node.right, field: left.field, context });
1889
- return `${left.text} ${COMPARISON_SQL[node.op]} ${right}`;
1890
- }
1891
- case "inList": {
1892
- if (node.values.length === 0) {
1893
- refuse2({
1894
- context,
1895
- rule: "predicate-empty",
1896
- message: `inList("${node.field.name}") has no values \u2014 an empty IN list is UNKNOWN on every row and never what an author means`,
1897
- meta: { field: node.field.name }
1898
- });
1899
- }
1900
- const field = resolveField({ name: node.field.name, context });
1901
- const values = node.values.map((value) => renderLiteral({ value, field, context })).join(", ");
1902
- return `${compareColumn({ field, context })} ${node.negate === true ? "NOT IN" : "IN"} (${values})`;
1903
- }
1904
- }
2441
+ // packages/sql/src/dialects/sqlite-session.ts
2442
+ var SQLITE_SESSION_SQL = Object.freeze({
2443
+ lockingMode: "PRAGMA main.locking_mode",
2444
+ exclusive: "PRAGMA main.locking_mode = EXCLUSIVE",
2445
+ normal: "PRAGMA main.locking_mode = NORMAL",
2446
+ acquire: "BEGIN EXCLUSIVE",
2447
+ commit: "COMMIT",
2448
+ rollback: "ROLLBACK",
2449
+ touch: "SELECT name FROM main.sqlite_schema LIMIT 1",
2450
+ foreignKeysOn: "PRAGMA foreign_keys = ON"
2451
+ });
2452
+ // packages/sql/src/composable.ts
2453
+ import { VibeError as VibeError13 } from "@vibeorm/schema";
2454
+ var fragments = new WeakMap;
2455
+ registerSqlHandleCheck({ check: (value) => fragments.has(value) });
2456
+ function invalid4(message) {
2457
+ throw new VibeError13({ code: "VIBE_VALIDATION", message });
1905
2458
  }
1906
- function resolveField(params) {
1907
- const { name, context } = params;
1908
- const field = context.model.fields.find((candidate) => candidate.name === name);
1909
- if (field === undefined) {
1910
- refuse2({
1911
- context,
1912
- rule: "predicate-unknown-field",
1913
- message: `references field "${name}", which the model does not declare`,
1914
- meta: { field: name }
1915
- });
1916
- }
1917
- if (field.isList) {
1918
- refuse2({
1919
- context,
1920
- rule: "predicate-list-field",
1921
- message: `compares scalar list "${name}" \u2014 array operators differ per dialect; use a raw expression for them`,
1922
- meta: { field: name }
1923
- });
1924
- }
1925
- if (field.isUnsupported === true) {
1926
- refuse2({
1927
- context,
1928
- rule: "predicate-unsupported-type",
1929
- message: `references "${name}", an Unsupported("${field.nativeType ?? "?"}") column whose operators VibeORM does not model \u2014 use a raw expression`,
1930
- meta: { field: name }
1931
- });
1932
- }
1933
- if (params.nullTest !== true && (field.type === "Json" || field.type === "Bytes")) {
1934
- refuse2({
1935
- context,
1936
- rule: "predicate-unsupported-type",
1937
- message: `references ${field.type} column "${name}" \u2014 ${field.type} comparison operators differ per dialect; use a raw expression`,
1938
- meta: { field: name, type: field.type }
1939
- });
1940
- }
1941
- if (context.usage === "check" && context.dialect.name === "mysql" && field.default?.kind === "autoincrement") {
1942
- throw unsupportedCapability9({
1943
- dialect: "mysql",
1944
- feature: `CHECK constraint referencing auto-increment column "${context.model.name}.${name}"`,
1945
- hint: "mysql refuses this with error 3818 \u2014 enforce the rule in application code or on a non-generated column"
1946
- });
1947
- }
1948
- return field;
2459
+ function construct(parts) {
2460
+ const snapshot = Object.freeze([...parts]);
2461
+ const fragment = Object.freeze(Object.defineProperty(Object.create(null), "toJSON", {
2462
+ value: () => ({ parts: snapshot })
2463
+ }));
2464
+ fragments.set(fragment, snapshot);
2465
+ return fragment;
1949
2466
  }
1950
- function quoteColumn(params) {
1951
- const { field, context } = params;
1952
- return context.dialect.quoteIdent({ name: field.dbName ?? field.name });
2467
+ function isSqlFragment(value) {
2468
+ return typeof value === "object" && value !== null && fragments.has(value);
1953
2469
  }
1954
- function compareColumn(params) {
1955
- const { field, context } = params;
1956
- const quoted = quoteColumn({ field, context });
1957
- return context.dialect.name === "sqlite" && field.type === "Decimal" ? `CAST(${quoted} AS REAL)` : quoted;
2470
+ function partsOf(value) {
2471
+ return fragments.get(value) ?? invalid4("Expected a constructed SQL fragment");
2472
+ }
2473
+ function containsGreatest(fragment) {
2474
+ return partsOf(fragment).some((part) => typeof part !== "string" && ("greatest" in part));
2475
+ }
2476
+ function identifier(parts) {
2477
+ if (!Array.isArray(parts) || parts.length === 0 || parts.some((part) => typeof part !== "string" || part.length === 0 || part.includes("\x00"))) {
2478
+ invalid4("SQL identifiers require non-empty, NUL-free name components");
2479
+ }
1958
2480
  }
1959
- function renderExpression(params) {
1960
- const { expression, context } = params;
1961
- if (expression.kind === "field") {
1962
- const field = resolveField({ name: expression.name, context });
1963
- return { text: compareColumn({ field, context }), field };
1964
- }
1965
- if (expression.kind === "jsonText") {
1966
- const field = resolveField({ name: expression.field.name, context, nullTest: true });
1967
- if (field.type !== "Json")
1968
- refuse2({ context, rule: "predicate-type-mismatch", message: `jsonText requires a scalar Json field, not "${field.name}"` });
1969
- if (!Array.isArray(expression.path) || Array.from(expression.path).some((segment) => typeof segment !== "string" || segment.includes("\x00"))) {
1970
- refuse2({ context, rule: "predicate-json-path", message: "jsonText requires a string-only path without NUL bytes" });
2481
+ function template(strings, ...values) {
2482
+ if (!Array.isArray(strings) || !Array.isArray(strings.raw) || strings.length !== values.length + 1 || strings.some((text) => typeof text !== "string")) {
2483
+ invalid4("Use sql as a template tag");
2484
+ }
2485
+ const parts = [];
2486
+ for (let index = 0;index < strings.length; index++) {
2487
+ parts.push(strings[index]);
2488
+ if (index < values.length) {
2489
+ const value = values[index];
2490
+ if (isSqlFragment(value))
2491
+ parts.push(...partsOf(value));
2492
+ else
2493
+ parts.push(Object.freeze({ value: snapshotSqlValue(value) }));
1971
2494
  }
1972
- const path = expression.path.map((value) => context.dialect.quoteStringLiteral({ value })).join(", ");
1973
- return { text: `(${quoteColumn({ field, context })} #>> ARRAY[${path}]::text[])`, field: { ...field, type: "String" } };
1974
2495
  }
1975
- const inner = renderExpression({ expression: expression.expression, context });
1976
- if (inner.field.type !== "String")
1977
- refuse2({ context, rule: "predicate-type-mismatch", message: "length requires a text expression" });
1978
- return { text: `length(${inner.text})`, field: { ...inner.field, type: "Int" } };
2496
+ return construct(parts);
1979
2497
  }
1980
- function renderOperand(params) {
1981
- const { operand, field, context } = params;
1982
- if (operand.kind === "value")
1983
- return renderLiteral({ value: operand, field, context });
1984
- const other = renderExpression({ expression: operand, context });
1985
- const leftType = fieldTypeIdentity({ type: field.type });
1986
- const rightType = fieldTypeIdentity({ type: other.field.type });
1987
- if (leftType !== rightType) {
1988
- refuse2({
1989
- context,
1990
- rule: "predicate-type-mismatch",
1991
- message: `compares "${field.name}" (${leftType}) with "${other.field.name}" (${rightType}) \u2014 both sides of a comparison need the same scalar type`,
1992
- meta: { field: field.name, other: other.field.name, fieldType: leftType, otherType: rightType }
2498
+ var sql = Object.freeze(Object.assign(template, {
2499
+ text: ({ text }) => {
2500
+ if (typeof text !== "string" || text.includes("\x00"))
2501
+ invalid4("SQL text must be a NUL-free string");
2502
+ return construct([text]);
2503
+ },
2504
+ ident: ({ parts }) => {
2505
+ identifier(parts);
2506
+ return construct([Object.freeze({ ident: Object.freeze([...parts]) })]);
2507
+ },
2508
+ value: ({ value, decoder }) => construct([Object.freeze({ value: snapshotSqlValue(value), ...decoder === undefined ? {} : { decoder } })]),
2509
+ join: ({ items, separator }) => {
2510
+ if (!Array.isArray(items))
2511
+ invalid4("sql.join requires an array");
2512
+ const between = separator === undefined ? [", "] : partsOf(separator);
2513
+ const parts = [];
2514
+ items.forEach((value, index) => {
2515
+ if (index > 0)
2516
+ parts.push(...between);
2517
+ parts.push(...isSqlFragment(value) ? partsOf(value) : [Object.freeze({ value: snapshotSqlValue(value) })]);
1993
2518
  });
2519
+ return construct(parts);
2520
+ },
2521
+ existing: ({ column }) => {
2522
+ identifier([column]);
2523
+ return construct([Object.freeze({ row: "existing", column })]);
2524
+ },
2525
+ incoming: ({ column }) => {
2526
+ identifier([column]);
2527
+ return construct([Object.freeze({ row: "incoming", column })]);
2528
+ },
2529
+ greatest: ({ values }) => {
2530
+ if (!Array.isArray(values) || values.length !== 2 || values.some((value) => !isSqlFragment(value)))
2531
+ invalid4("sql.greatest requires two SQL fragments");
2532
+ return construct([Object.freeze({ greatest: Object.freeze([...values]) })]);
1994
2533
  }
1995
- return other.text;
1996
- }
1997
- function renderLiteral(params) {
1998
- const { field, context } = params;
1999
- const { value } = params.value;
2000
- const type = field.type;
2001
- const wrongCarrier = (expected) => refuse2({
2002
- context,
2003
- rule: "predicate-type-mismatch",
2004
- message: `compares ${fieldTypeIdentity({ type })} field "${field.name}" with ${describe({ value })} \u2014 ${expected}`,
2005
- meta: { field: field.name, fieldType: fieldTypeIdentity({ type }), value }
2534
+ }));
2535
+ function renderSqlFragment(params) {
2536
+ const { dialect, collector, conflict } = params;
2537
+ const render = (fragment) => renderSqlFragment({ ...params, fragment });
2538
+ const encode = (part) => {
2539
+ if (part.value === undefined)
2540
+ invalid4("SQL parameters cannot be undefined; use null explicitly");
2541
+ return snapshotSqlValue(part.decoder === undefined ? part.value : part.decoder.encode({ value: snapshotSqlValue(part.value), dialect: dialect.name }));
2542
+ };
2543
+ const recording = isSqlBindingCollector({ collector });
2544
+ const recordValue = (part) => recordSqlBinding({
2545
+ collector,
2546
+ source: { kind: "literal", value: part.value },
2547
+ transport: part.decoder === undefined ? sqlParameterTransport({ value: part.value }) : { kind: "plain" },
2548
+ encode: ({ value, dialect: target }) => part.decoder === undefined ? value : part.decoder.encode({ value, dialect: target.name })
2006
2549
  });
2007
- if (isEnumType(type)) {
2008
- if (typeof value !== "string")
2009
- return wrongCarrier(`enum ${type.enum} takes one of its member names as a string`);
2010
- const declared = context.enums.find((entry) => entry.name === type.enum);
2011
- if (declared !== undefined && !declared.values.includes(value)) {
2012
- refuse2({
2013
- context,
2014
- rule: "predicate-enum-member",
2015
- message: `compares "${field.name}" with "${value}", which is not a member of enum ${type.enum} (${declared.values.join(", ")})`,
2016
- meta: { field: field.name, enum: type.enum, value, members: [...declared.values] }
2017
- });
2018
- }
2019
- return stringLiteral({ value, context, field });
2020
- }
2021
- switch (type) {
2022
- case "String":
2023
- if (typeof value !== "string")
2024
- return wrongCarrier("a String column takes a string literal");
2025
- return stringLiteral({ value, context, field });
2026
- case "Boolean":
2027
- if (typeof value !== "boolean")
2028
- return wrongCarrier("a Boolean column takes `true` or `false`");
2029
- return value ? context.dialect.booleanLiteral.true : context.dialect.booleanLiteral.false;
2030
- case "Int":
2031
- return integerLiteral({ value, field, context, min: INT32_MIN, max: INT32_MAX, label: "Int" });
2032
- case "BigInt":
2033
- return integerLiteral({ value, field, context, min: INT64_MIN, max: INT64_MAX, label: "BigInt" });
2034
- case "Float": {
2035
- if (typeof value !== "number")
2036
- return wrongCarrier("a Float column takes a number");
2037
- if (!Number.isFinite(value)) {
2038
- refuse2({
2039
- context,
2040
- rule: "predicate-literal-invalid",
2041
- message: `compares "${field.name}" with ${String(value)} \u2014 a Float literal must be finite`,
2042
- meta: { field: field.name, value: String(value) }
2043
- });
2550
+ const frameOperand = (fragment) => {
2551
+ const parts = partsOf(fragment).filter((part) => part !== "");
2552
+ const only = parts.length === 1 ? parts[0] : undefined;
2553
+ if (recording && only !== undefined && typeof only !== "string" && "value" in only) {
2554
+ if (only.decoder !== undefined && resultScalarContract({ decoder: only.decoder }) === undefined)
2555
+ throw new VibeError13({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL cannot compile an opaque encoder whose output controls a greatest NULL frame" });
2556
+ if (only.value === null) {
2557
+ if (only.decoder !== undefined)
2558
+ recordSqlBindingCheck({ collector, check: () => {
2559
+ if (encode({ ...only, value: snapshotSqlValue(only.value) }) !== null)
2560
+ throw new VibeError13({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL NULL frame encoding changed its declared structure" });
2561
+ } });
2562
+ return "NULL";
2044
2563
  }
2045
- return String(value);
2564
+ const text = recordValue(only);
2565
+ return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
2046
2566
  }
2047
- case "Decimal": {
2048
- if (typeof value !== "string") {
2049
- return wrongCarrier('a Decimal column takes exact decimal TEXT ("0", "19.99") \u2014 a JS number cannot carry an exact decimal');
2050
- }
2051
- if (!EXACT_DECIMAL.test(value)) {
2052
- refuse2({
2053
- context,
2054
- rule: "predicate-literal-invalid",
2055
- message: `compares "${field.name}" with "${value}", which is not an exact decimal literal`,
2056
- meta: { field: field.name, value }
2057
- });
2058
- }
2059
- return value;
2567
+ const value = only !== undefined && typeof only !== "string" && "value" in only ? { encoded: encode(only) } : undefined;
2568
+ if (value !== undefined && value.encoded === null)
2569
+ return "NULL";
2570
+ const text = value === undefined ? render(fragment) : collector.add(value.encoded);
2571
+ return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
2572
+ };
2573
+ return partsOf(params.fragment).map((part) => {
2574
+ if (typeof part === "string")
2575
+ return part;
2576
+ if ("value" in part)
2577
+ return recording ? recordValue(part) : collector.add(encode(part));
2578
+ if ("ident" in part)
2579
+ return part.ident.map((name) => dialect.quoteIdent({ name })).join(".");
2580
+ if ("row" in part) {
2581
+ if (conflict === undefined)
2582
+ invalid4("Existing/incoming row references require a conflict update");
2583
+ const name = conflict.columns === undefined ? part.column : conflict.columns[part.column];
2584
+ if (name === undefined)
2585
+ invalid4(`Unknown conflict-expression field "${part.column}"`);
2586
+ const ref = dialect.excludedRowRef;
2587
+ const qualifier = part.row === "existing" ? dialect.quoteIdent({ name: conflict.table }) : ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
2588
+ return `${qualifier}.${dialect.quoteIdent({ name })}`;
2060
2589
  }
2061
- case "DateTime":
2062
- refuse2({
2063
- context,
2064
- rule: "predicate-unsupported-literal",
2065
- message: `compares DateTime field "${field.name}" with a literal \u2014 DateTime literals are spelled differently on postgres, mysql and sqlite, so VibeORM does not guess one. Compare two DateTime columns, or write a raw expression for a fixed instant`,
2066
- meta: { field: field.name }
2067
- });
2068
- case "Json":
2069
- case "Bytes":
2070
- return wrongCarrier(`${type} columns take no predicate literal`);
2071
- }
2072
- }
2073
- function integerLiteral(params) {
2074
- const { value, field, context, min, max, label } = params;
2075
- let text;
2076
- if (typeof value === "number") {
2077
- if (!Number.isSafeInteger(value)) {
2078
- refuse2({
2079
- context,
2080
- rule: "predicate-literal-invalid",
2081
- message: `compares "${field.name}" with ${String(value)} \u2014 an ${label} literal must be an exact integer JavaScript can represent; pass a bigint or a decimal string for larger values`,
2082
- meta: { field: field.name, value: String(value) }
2083
- });
2590
+ if (dialect.greatestForm === "native")
2591
+ return `GREATEST(${part.greatest.map(render).join(", ")})`;
2592
+ if (!part.greatest.some(containsGreatest)) {
2593
+ return render(part.greatest.reduce((left, right) => sql`(CASE WHEN ${left} IS NULL THEN ${right} WHEN ${right} IS NULL THEN ${left} WHEN ${left} >= ${right} THEN ${left} ELSE ${right} END)`));
2084
2594
  }
2085
- text = String(value);
2086
- } else if (typeof value === "string") {
2087
- if (!EXACT_INTEGER.test(value)) {
2088
- refuse2({
2089
- context,
2090
- rule: "predicate-literal-invalid",
2091
- message: `compares "${field.name}" with "${value}", which is not an exact integer literal`,
2092
- meta: { field: field.name, value }
2093
- });
2595
+ const [left, right] = part.greatest.map(frameOperand);
2596
+ const [a, b, frame] = ["a", "b", "g"].map((name) => dialect.quoteIdent({ name }));
2597
+ return `(SELECT CASE WHEN ${a} IS NULL THEN ${b} WHEN ${b} IS NULL THEN ${a} WHEN ${a} >= ${b} THEN ${a} ELSE ${b} END FROM (SELECT ${left} AS ${a}, ${right} AS ${b}) AS ${frame})`;
2598
+ }).join("");
2599
+ }
2600
+ function mapConflictColumns(params) {
2601
+ return construct(partsOf(params.fragment).map((part) => {
2602
+ if (typeof part === "string")
2603
+ return part;
2604
+ if ("row" in part) {
2605
+ const column = Object.hasOwn(params.columns, part.column) ? params.columns[part.column] : undefined;
2606
+ if (column === undefined)
2607
+ invalid4(`Unknown conflict-expression field "${part.column}"`);
2608
+ return Object.freeze({ ...part, column });
2094
2609
  }
2095
- text = value;
2096
- } else {
2097
- refuse2({
2098
- context,
2099
- rule: "predicate-type-mismatch",
2100
- message: `compares ${label} field "${field.name}" with ${describe({ value })} \u2014 an ${label} column takes an integer`,
2101
- meta: { field: field.name, value }
2102
- });
2103
- }
2104
- const parsed = BigInt(text);
2105
- if (parsed < min || parsed > max) {
2106
- refuse2({
2107
- context,
2108
- rule: "predicate-literal-invalid",
2109
- message: `compares "${field.name}" with ${text}, which is outside the range an ${label} column can hold (${min}\u2026${max})`,
2110
- meta: { field: field.name, value: text }
2111
- });
2112
- }
2113
- return text;
2610
+ if ("greatest" in part)
2611
+ return Object.freeze({ greatest: Object.freeze(part.greatest.map((fragment) => mapConflictColumns({ ...params, fragment }))) });
2612
+ return part;
2613
+ }));
2114
2614
  }
2115
- function stringLiteral(params) {
2116
- const { value, context, field } = params;
2117
- if (value.includes("\x00")) {
2118
- refuse2({
2119
- context,
2120
- rule: "predicate-literal-invalid",
2121
- message: `compares "${field.name}" with a string containing a NUL byte, which no dialect here can store`,
2122
- meta: { field: field.name }
2123
- });
2124
- }
2125
- return context.dialect.quoteStringLiteral({ value });
2615
+ function compileSql(params) {
2616
+ const collector = new ParamCollector(params.dialect);
2617
+ const text = renderSqlFragment({ dialect: params.dialect, fragment: params.query, collector });
2618
+ return { text, values: collector.values };
2126
2619
  }
2127
- function describe(params) {
2620
+ function sqlUsesIncoming(params) {
2621
+ return partsOf(params.fragment).some((part) => typeof part !== "string" && (("row" in part) && part.row === "incoming" || ("greatest" in part) && part.greatest.some((fragment) => sqlUsesIncoming({ fragment }))));
2622
+ }
2623
+ // packages/sql/src/query-plan.ts
2624
+ import { VibeError as VibeError14 } from "@vibeorm/schema";
2625
+ var queries = new WeakMap;
2626
+ var commands = new WeakMap;
2627
+ var reusableQueries = new WeakMap;
2628
+ var reusableCommands = new WeakMap;
2629
+ registerSqlHandleCheck({ check: (value) => queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) });
2630
+ function invalid5(message) {
2631
+ throw new VibeError14({ code: "VIBE_VALIDATION", message });
2632
+ }
2633
+ function createSqlQuery(params) {
2634
+ if (typeof params.render !== "function" || typeof params.decoder?.decode !== "function")
2635
+ invalid5("SQL queries require a renderer and result decoder");
2636
+ const query = Object.freeze(Object.create(null));
2637
+ queries.set(query, Object.freeze({ render: params.render, decoder: params.decoder }));
2638
+ return query;
2639
+ }
2640
+ function createSqlCommand(params) {
2641
+ if (typeof params.render !== "function")
2642
+ invalid5("SQL commands require a renderer");
2643
+ const command = Object.freeze(Object.create(null));
2644
+ commands.set(command, params.render);
2645
+ return command;
2646
+ }
2647
+ function isSqlQuery(value) {
2648
+ return typeof value === "object" && value !== null && queries.has(value);
2649
+ }
2650
+ function isSqlCommand(value) {
2651
+ return typeof value === "object" && value !== null && commands.has(value);
2652
+ }
2653
+ function isReusableSqlQuery(value) {
2654
+ return typeof value === "object" && value !== null && reusableQueries.has(value);
2655
+ }
2656
+ function isReusableSqlCommand(value) {
2657
+ return typeof value === "object" && value !== null && reusableCommands.has(value);
2658
+ }
2659
+ function createReusableSqlQuery(params) {
2660
+ const state = queries.get(params.query) ?? invalid5("Expected an authentic SQL row query");
2661
+ sqlInputScopeKeys({ scope: params.scope });
2662
+ const query = Object.freeze(Object.create(null));
2663
+ reusableQueries.set(query, { ...state, scope: params.scope, cache: new WeakMap });
2664
+ return query;
2665
+ }
2666
+ function createReusableSqlCommand(params) {
2667
+ const render = commands.get(params.query) ?? invalid5("Expected an authentic SQL command");
2668
+ sqlInputScopeKeys({ scope: params.scope });
2669
+ const query = Object.freeze(Object.create(null));
2670
+ reusableCommands.set(query, { render, scope: params.scope, cache: new WeakMap });
2671
+ return query;
2672
+ }
2673
+ function compiledTemplate(params) {
2674
+ const cached = params.state.cache.get(params.dialect);
2675
+ if (cached !== undefined)
2676
+ return cached;
2677
+ const collector = new ParamCollector(params.dialect);
2678
+ startSqlBindingCollection({ collector, dialect: params.dialect, scope: params.state.scope });
2679
+ const text = params.state.render({ dialect: params.dialect, collector });
2680
+ if (collector.count() > params.dialect.paramBudget)
2681
+ invalid5("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
2682
+ const program = finishSqlBindingCollection({ collector });
2683
+ const compiled = Object.freeze({ text, program });
2684
+ params.state.cache.set(params.dialect, compiled);
2685
+ return compiled;
2686
+ }
2687
+ function executionHandle(params) {
2128
2688
  const { value } = params;
2129
- return typeof value === "string" ? `the string ${JSON.stringify(value)}` : `the ${typeof value} ${String(value)}`;
2689
+ return queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) || isSqlFragment(value) || isSqlInput(value) || sqlInputScopeHandle(params);
2690
+ }
2691
+ function bindTemplate(params) {
2692
+ const { scope } = params.state;
2693
+ return bindSqlBindingProgram({ program: params.compiled.program, params: params.inputs, isHandle: (value) => executionHandle({ scope, value }) });
2694
+ }
2695
+ function compileReusableSqlQuery(params) {
2696
+ const state = reusableQueries.get(params.query) ?? invalid5("Expected an authentic reusable SQL row query");
2697
+ const compiled = compiledTemplate({ dialect: params.dialect, state });
2698
+ return { text: compiled.text, values: bindTemplate({ compiled, state, inputs: params.params }), decoder: state.decoder };
2699
+ }
2700
+ function compileReusableSqlCommand(params) {
2701
+ const state = reusableCommands.get(params.query) ?? invalid5("Expected an authentic reusable SQL command");
2702
+ const compiled = compiledTemplate({ dialect: params.dialect, state });
2703
+ return { text: compiled.text, values: bindTemplate({ compiled, state, inputs: params.params }) };
2704
+ }
2705
+ function compileSqlQuery(params) {
2706
+ const state = queries.get(params.query) ?? invalid5("Expected an authentic SQL row query");
2707
+ const collector = new ParamCollector(params.dialect);
2708
+ const text = state.render({ dialect: params.dialect, collector });
2709
+ if (collector.values.length > params.dialect.paramBudget)
2710
+ invalid5("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
2711
+ return { text, values: collector.values, decoder: state.decoder };
2712
+ }
2713
+ function compileSqlCommand(params) {
2714
+ const render = commands.get(params.query) ?? invalid5("Expected an authentic SQL command");
2715
+ const collector = new ParamCollector(params.dialect);
2716
+ const text = render({ dialect: params.dialect, collector });
2717
+ if (collector.values.length > params.dialect.paramBudget)
2718
+ invalid5("SQL command exceeds this dialect's parameter budget; statements are never split implicitly");
2719
+ return { text, values: collector.values };
2130
2720
  }
2131
-
2132
2721
  // packages/sql/src/exclusion.ts
2133
- function invalid5(rule) {
2134
- throw new VibeError12({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
2722
+ import { VibeError as VibeError15, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability14 } from "@vibeorm/schema";
2723
+ function invalid6(rule) {
2724
+ throw new VibeError15({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
2135
2725
  }
2136
2726
  function compileExclusion(params) {
2137
2727
  const { exclusion: spec, model, dialect } = params;
2138
2728
  if (!dialect.capabilities.exclusionConstraints)
2139
- throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
2729
+ throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
2140
2730
  if (!spec.name || spec.name.includes("\x00"))
2141
- invalid5("name");
2731
+ invalid6("name");
2142
2732
  if (!["int8range", "tsrange", "tstzrange"].includes(spec.range.type))
2143
- invalid5("range-type");
2733
+ invalid6("range-type");
2144
2734
  if (!["[)", "[]", "()", "(]"].includes(spec.range.bounds))
2145
- invalid5("bounds");
2735
+ invalid6("bounds");
2146
2736
  const field = (name) => {
2147
2737
  const value = model.fields.find((entry) => entry.name === name);
2148
2738
  if (value === undefined || value.isList || value.isUnsupported)
2149
- invalid5("field");
2739
+ invalid6("field");
2150
2740
  return value;
2151
2741
  };
2152
2742
  const endpoint = (value) => {
@@ -2154,17 +2744,17 @@ function compileExclusion(params) {
2154
2744
  return "NULL";
2155
2745
  const infinity = "infinity" in value ? value.infinity : value.nullAs;
2156
2746
  if (infinity !== undefined && (spec.range.type === "int8range" || !["infinity", "-infinity"].includes(infinity)))
2157
- invalid5("infinity");
2747
+ invalid6("infinity");
2158
2748
  const literal = infinity === undefined ? undefined : `${dialect.quoteStringLiteral({ value: infinity })}::${spec.range.type === "tsrange" ? "timestamp" : "timestamptz"}`;
2159
2749
  if ("infinity" in value)
2160
2750
  return literal;
2161
2751
  const column = field(value.field);
2162
2752
  if (fieldTypeIdentity2({ type: column.type }) !== (spec.range.type === "int8range" ? "BigInt" : "DateTime"))
2163
- invalid5("range-field-type");
2753
+ invalid6("range-field-type");
2164
2754
  if (column.type === "DateTime") {
2165
2755
  const timezone = /timestamptz|timestamp.*with time zone/i.test(column.nativeType ?? "timestamptz");
2166
2756
  if (timezone !== (spec.range.type === "tstzrange"))
2167
- invalid5("range-timezone");
2757
+ invalid6("range-timezone");
2168
2758
  }
2169
2759
  const sql = dialect.quoteIdent({ name: column.dbName ?? column.name });
2170
2760
  return literal === undefined ? sql : `COALESCE(${sql}, ${literal})`;
@@ -2172,15 +2762,15 @@ function compileExclusion(params) {
2172
2762
  const keys = spec.keys.map((key) => {
2173
2763
  if ("expression" in key) {
2174
2764
  if (!key.expression.trim() || key.expression.includes("\x00") || key.fields.length === 0)
2175
- invalid5("expression");
2765
+ invalid6("expression");
2176
2766
  key.fields.forEach(field);
2177
2767
  return `(${key.expression}) WITH =`;
2178
2768
  }
2179
2769
  const column = field(key.field);
2180
2770
  if (column.isOptional)
2181
- invalid5("nullable-key-requires-expression");
2771
+ invalid6("nullable-key-requires-expression");
2182
2772
  if (!isEnumType2(column.type) && !["String", "Int", "BigInt", "Float", "Decimal", "Boolean", "DateTime"].includes(fieldTypeIdentity2({ type: column.type })))
2183
- invalid5("key-type");
2773
+ invalid6("key-type");
2184
2774
  return `${dialect.quoteIdent({ name: column.dbName ?? column.name })} WITH =`;
2185
2775
  });
2186
2776
  keys.push(`${spec.range.type}(${endpoint(spec.range.lower)}, ${endpoint(spec.range.upper)}, ${dialect.quoteStringLiteral({ value: spec.range.bounds })}) WITH &&`);
@@ -2189,13 +2779,13 @@ function compileExclusion(params) {
2189
2779
  }
2190
2780
  function renderExclusionSetup(params) {
2191
2781
  if (!params.dialect.capabilities.exclusionConstraints)
2192
- throw unsupportedCapability10({ dialect: params.dialect.name, feature: "exclusionConstraints" });
2782
+ throw unsupportedCapability14({ dialect: params.dialect.name, feature: "exclusionConstraints" });
2193
2783
  return params.exclusion.requiredExtensions.map((name) => `CREATE EXTENSION IF NOT EXISTS ${params.dialect.quoteIdent({ name })};`);
2194
2784
  }
2195
2785
  function renderExclusionDdl(params) {
2196
2786
  const { dialect, table, exclusion, operation } = params;
2197
2787
  if (!dialect.capabilities.exclusionConstraints)
2198
- throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
2788
+ throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
2199
2789
  const target = `ALTER TABLE ${dialect.quoteIdent({ name: table })}`;
2200
2790
  if (operation === "drop")
2201
2791
  return [`${target} DROP CONSTRAINT ${dialect.quoteIdent({ name: exclusion.name })};`];
@@ -2204,14 +2794,14 @@ function renderExclusionDdl(params) {
2204
2794
  function renderExclusionCatalog(params) {
2205
2795
  const { dialect, probe } = params;
2206
2796
  if (!dialect.capabilities.exclusionConstraints)
2207
- throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
2797
+ throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
2208
2798
  const scope = probe === undefined ? `n.nspname = ${dialect.quoteStringLiteral({ value: params.namespace ?? "public" })}` : `t.oid = ${dialect.quoteStringLiteral({ value: probe })}::regclass`;
2209
2799
  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`;
2210
2800
  }
2211
2801
  function renderExclusionProbe(params) {
2212
2802
  const { dialect, table, probe, definition } = params;
2213
2803
  if (!dialect.capabilities.exclusionConstraints)
2214
- throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
2804
+ throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
2215
2805
  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}`];
2216
2806
  }
2217
2807
  // packages/sql/src/ast.ts
@@ -2243,6 +2833,8 @@ function dbNowMarker(params) {
2243
2833
  function isDbNowMarker(value) {
2244
2834
  if (typeof value !== "object" || value === null || Array.isArray(value))
2245
2835
  return false;
2836
+ if (!Object.hasOwn(value, "__dbNow"))
2837
+ return false;
2246
2838
  const keys = Object.keys(value);
2247
2839
  if (keys.length !== 2 || !keys.includes("__dbNow") || !keys.includes("offsetSeconds"))
2248
2840
  return false;
@@ -2290,7 +2882,7 @@ function chainVetoToken(params) {
2290
2882
  return;
2291
2883
  }
2292
2884
  // packages/sql/src/locks.ts
2293
- import { internalError, unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
2885
+ import { internalError, unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
2294
2886
  var ROW_LOCK_PROSE = Object.freeze({
2295
2887
  strengths: Object.freeze({
2296
2888
  update: "FOR UPDATE",
@@ -2310,7 +2902,7 @@ function renderRowLockClause(params) {
2310
2902
  if (syntax === null) {
2311
2903
  if (lock.strength === "update" && lock.wait === "wait")
2312
2904
  return null;
2313
- throw unsupportedCapability11({
2905
+ throw unsupportedCapability15({
2314
2906
  dialect: dialect.name,
2315
2907
  feature: `a ${describeRowLock({ lock })} locking read`,
2316
2908
  hint: `${dialect.name} has no locking-read clause at all`
@@ -2318,7 +2910,7 @@ function renderRowLockClause(params) {
2318
2910
  }
2319
2911
  const strengthText = syntax.strengths[lock.strength];
2320
2912
  if (strengthText === undefined) {
2321
- throw unsupportedCapability11({
2913
+ throw unsupportedCapability15({
2322
2914
  dialect: dialect.name,
2323
2915
  feature: `the ${describeRowLock({ lock: { ...lock, wait: "wait" } })} row-lock strength`,
2324
2916
  hint: `${dialect.name} can express: ${Object.keys(syntax.strengths).join(", ")}`
@@ -2326,7 +2918,7 @@ function renderRowLockClause(params) {
2326
2918
  }
2327
2919
  const waitText = syntax.wait[lock.wait];
2328
2920
  if (waitText === undefined) {
2329
- throw unsupportedCapability11({
2921
+ throw unsupportedCapability15({
2330
2922
  dialect: dialect.name,
2331
2923
  feature: `the "${lock.wait}" lock wait policy`,
2332
2924
  hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
@@ -2345,7 +2937,7 @@ function renderAdvisoryLock(params) {
2345
2937
  const { dialect, statement, collector } = params;
2346
2938
  const functions = dialect.advisoryLockFunctions;
2347
2939
  if (functions === null) {
2348
- throw unsupportedCapability11({
2940
+ throw unsupportedCapability15({
2349
2941
  dialect: dialect.name,
2350
2942
  feature: "advisory locks",
2351
2943
  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`
@@ -2384,6 +2976,13 @@ var postgresDialect = {
2384
2976
  capabilities: POSTGRES_CAPABILITIES,
2385
2977
  inArrayStrategy: "any",
2386
2978
  tupleInStrategy: "rowValue",
2979
+ keyListStrategy: "unnest",
2980
+ keysetSeekStrategy: "rowValue",
2981
+ keyPageStrategy: "lateral",
2982
+ relationFilterStrategy: "exists",
2983
+ deferredJoinPage: { minSkip: 1000, requiresCoveringIndex: true },
2984
+ nativeNullsOrder: "high",
2985
+ nullsUnionPage: true,
2387
2986
  timeOfDayInStrategy: "in",
2388
2987
  upsertForm: "onConflict",
2389
2988
  conflictKeyEquality: {
@@ -2433,6 +3032,8 @@ var postgresDialect = {
2433
3032
  keyCast: Object.freeze({ single: "::bigint", pair: "::int4" })
2434
3033
  },
2435
3034
  rowDefaultKeyword: true,
3035
+ bulkInsertForm: "unnest",
3036
+ emptyInsert: { form: "defaultValues", conflictClause: true },
2436
3037
  statementChain: "dataModifyingCte",
2437
3038
  paramBudget: 30000,
2438
3039
  booleanLiteral: { true: "true", false: "false" },
@@ -2460,6 +3061,13 @@ var mysqlDialect = {
2460
3061
  capabilities: MYSQL_CAPABILITIES,
2461
3062
  inArrayStrategy: "expand",
2462
3063
  tupleInStrategy: "rowValue",
3064
+ keyListStrategy: "rowValue",
3065
+ keysetSeekStrategy: "or",
3066
+ keyPageStrategy: "window",
3067
+ relationFilterStrategy: "exists",
3068
+ deferredJoinPage: { minSkip: 1000, requiresCoveringIndex: false },
3069
+ nativeNullsOrder: "low",
3070
+ nullsUnionPage: true,
2463
3071
  timeOfDayInStrategy: "equalities",
2464
3072
  upsertForm: "onDuplicateKey",
2465
3073
  conflictKeyEquality: {
@@ -2495,6 +3103,8 @@ var mysqlDialect = {
2495
3103
  },
2496
3104
  advisoryLockFunctions: null,
2497
3105
  rowDefaultKeyword: true,
3106
+ bulkInsertForm: "values",
3107
+ emptyInsert: { form: "emptyRow" },
2498
3108
  statementChain: null,
2499
3109
  paramBudget: 1e4,
2500
3110
  booleanLiteral: { true: "1", false: "0" },
@@ -2525,6 +3135,389 @@ var SQL_DIALECTS = {
2525
3135
  function getSqlDialect(params) {
2526
3136
  return SQL_DIALECTS[params.dialect];
2527
3137
  }
3138
+ // packages/sql/src/key-list.ts
3139
+ import { unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
3140
+ var SCALAR_ELEMENT_TYPES = Object.freeze({
3141
+ String: "text",
3142
+ Boolean: "bool",
3143
+ Int: "int4",
3144
+ BigInt: "int8",
3145
+ Float: "float8",
3146
+ Decimal: "numeric",
3147
+ DateTime: "timestamptz",
3148
+ Json: null,
3149
+ Bytes: "bytea"
3150
+ });
3151
+ var NATIVE_ELEMENT_TYPES = Object.freeze({
3152
+ text: "text",
3153
+ varchar: "varchar",
3154
+ charactervarying: "varchar",
3155
+ char: "bpchar",
3156
+ character: "bpchar",
3157
+ bpchar: "bpchar",
3158
+ uuid: "uuid",
3159
+ citext: "citext",
3160
+ xml: "xml",
3161
+ inet: "inet",
3162
+ money: "money",
3163
+ bit: '"bit"',
3164
+ varbit: "varbit",
3165
+ bitvarying: "varbit",
3166
+ smallint: "int2",
3167
+ int2: "int2",
3168
+ integer: "int4",
3169
+ int: "int4",
3170
+ int4: "int4",
3171
+ bigint: "int8",
3172
+ int8: "int8",
3173
+ oid: "oid",
3174
+ real: "float4",
3175
+ float4: "float4",
3176
+ doubleprecision: "float8",
3177
+ double: "float8",
3178
+ float8: "float8",
3179
+ decimal: "numeric",
3180
+ numeric: "numeric",
3181
+ boolean: "bool",
3182
+ bool: "bool",
3183
+ timestamp: "timestamp",
3184
+ timestampwithouttimezone: "timestamp",
3185
+ timestamptz: "timestamptz",
3186
+ timestampwithtimezone: "timestamptz",
3187
+ date: "date",
3188
+ time: "time",
3189
+ timewithouttimezone: "time",
3190
+ timetz: "timetz",
3191
+ timewithtimezone: "timetz",
3192
+ bytea: "bytea"
3193
+ });
3194
+ function postgresKeyListElementTypeOf(params) {
3195
+ const { dialect, member } = params;
3196
+ if (member.type === "Enum") {
3197
+ if (member.enumType === undefined)
3198
+ return "text";
3199
+ const name = dialect.quoteIdent({ name: member.enumType.name });
3200
+ return member.enumType.namespace === undefined ? name : `${dialect.quoteIdent({ name: member.enumType.namespace })}.${name}`;
3201
+ }
3202
+ const native = member.nativeType === undefined ? undefined : NATIVE_ELEMENT_TYPES[member.nativeType.toLowerCase().replace(/\([^)]*\)/g, "").replace(/\s+/g, "")];
3203
+ const type = member.nativeType === undefined ? SCALAR_ELEMENT_TYPES[member.type] : native;
3204
+ return type === null ? undefined : type;
3205
+ }
3206
+ function postgresKeyListElementType(params) {
3207
+ const { dialect, member } = params;
3208
+ const type = postgresKeyListElementTypeOf({ dialect, member });
3209
+ if (type === undefined) {
3210
+ throw unsupportedCapability16({
3211
+ dialect: dialect.name,
3212
+ feature: `the key-list filter (IN) on a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} member`,
3213
+ hint: "IN binds every member as a typed array; filter this field with equality inside OR instead"
3214
+ });
3215
+ }
3216
+ return type;
3217
+ }
3218
+ function keyListBindsUntypedRows(params) {
3219
+ return params.members.some((member) => member.type === "DateTime" && member.nativeType === undefined);
3220
+ }
3221
+ function postgresBulkInsertArrayCastOf(params) {
3222
+ const { dialect, member } = params;
3223
+ if (member.type === "Json")
3224
+ return member.nativeType?.trim().toLowerCase() === "json" ? "text::json" : "text::jsonb";
3225
+ return postgresKeyListElementTypeOf({ dialect, member });
3226
+ }
3227
+ function postgresBulkInsertArrayCast(params) {
3228
+ const { dialect, member } = params;
3229
+ const cast = postgresBulkInsertArrayCastOf({ dialect, member });
3230
+ if (cast === undefined) {
3231
+ throw unsupportedCapability16({
3232
+ dialect: dialect.name,
3233
+ feature: `a bulk insert binding a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} column as a typed array`,
3234
+ hint: "the query builder keeps VALUES for this column type"
3235
+ });
3236
+ }
3237
+ return cast;
3238
+ }
3239
+ // packages/sql/src/local-date.ts
3240
+ import { localDateCheckName } from "@vibeorm/schema";
3241
+ function renderLocalDateCheck({ dialect, table, column }) {
3242
+ if (dialect.name !== "sqlite")
3243
+ return;
3244
+ const quoted = dialect.quoteIdent({ name: column });
3245
+ return {
3246
+ name: localDateCheckName({ table, column }),
3247
+ expression: `${quoted} IS date(${quoted}) AND (${quoted} IS NULL OR ${quoted} >= ${dialect.quoteStringLiteral({ value: "0001-01-01" })})`
3248
+ };
3249
+ }
3250
+ // packages/sql/src/generated-columns.ts
3251
+ import { VibeError as VibeError16, unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
3252
+
3253
+ // packages/sql/src/dialects/postgres-generated-columns.ts
3254
+ import { unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
3255
+ function renderPostgresGeneratedColumnClause({ generated }) {
3256
+ return `GENERATED ALWAYS AS (${generated.expression}) STORED`;
3257
+ }
3258
+ function renderPostgresGeneratedColumnProbe({ dialect, probe }) {
3259
+ if (dialect.name !== "postgres")
3260
+ throw unsupportedCapability17({ dialect: dialect.name, feature: "rollbackGeneratedColumnProbe" });
3261
+ const q = ({ name }) => dialect.quoteIdent({ name });
3262
+ let text;
3263
+ switch (probe.operation) {
3264
+ case "savepoint":
3265
+ text = `SAVEPOINT ${q({ name: probe.savepoint })}`;
3266
+ break;
3267
+ case "rollback":
3268
+ text = `ROLLBACK TO SAVEPOINT ${q({ name: probe.savepoint })}`;
3269
+ break;
3270
+ case "release":
3271
+ text = `RELEASE SAVEPOINT ${q({ name: probe.savepoint })}`;
3272
+ break;
3273
+ case "add":
3274
+ text = `ALTER TABLE ${q({ name: probe.table.namespace })}.${q({ name: probe.table.name })} ADD COLUMN ${q({ name: probe.column })} ${probe.definition}`;
3275
+ break;
3276
+ }
3277
+ return { text, values: [], returning: "none" };
3278
+ }
3279
+ function renderPostgresDropGeneratedExpression({ dialect, table, column }) {
3280
+ if (dialect.name !== "postgres")
3281
+ throw unsupportedCapability17({ dialect: dialect.name, feature: "dropGeneratedExpression" });
3282
+ 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" };
3283
+ }
3284
+
3285
+ // packages/sql/src/generated-columns.ts
3286
+ function renderGeneratedColumnClause({ dialect, generated }) {
3287
+ if (!("generatedColumns" in dialect.capabilities) || dialect.capabilities.generatedColumns !== true) {
3288
+ throw unsupportedCapability18({ dialect: dialect.name, feature: "generatedColumns" });
3289
+ }
3290
+ if (generated.storage !== "stored") {
3291
+ throw unsupportedCapability18({ dialect: dialect.name, feature: "virtualGeneratedColumns" });
3292
+ }
3293
+ if (typeof generated.expression !== "string" || generated.expression.trim().length === 0 || generated.expression.includes("\x00")) {
3294
+ throw new VibeError16({ code: "VIBE_SCHEMA_INVALID", message: "A generated column requires non-empty, NUL-free SQL", meta: { reason: "generated-expression-invalid" } });
3295
+ }
3296
+ switch (dialect.name) {
3297
+ case "postgres":
3298
+ return renderPostgresGeneratedColumnClause({ generated });
3299
+ case "mysql":
3300
+ return renderMysqlGeneratedColumnClause({ generated });
3301
+ case "sqlite":
3302
+ return renderSqliteGeneratedColumnClause({ generated });
3303
+ }
3304
+ }
3305
+ // packages/sql/src/row-rules.ts
3306
+ import { VibeError as VibeError20, internalError as internalError2, normalizeRowRules, rowRuleGateField, rowRuleIdentities, rowRuleProtectedFields, rowRuleShapeIssue, rowRuleTriggerName, unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
3307
+
3308
+ // packages/sql/src/dialects/postgres-row-rules.ts
3309
+ import { VibeError as VibeError17, unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
3310
+ function comparison({ dialect, field }) {
3311
+ const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
3312
+ const cast = field.type === "Json" ? `::pg_catalog.jsonb${field.isList ? "[]" : ""}` : "";
3313
+ return `OLD.${column}${cast} IS DISTINCT FROM NEW.${column}${cast}`;
3314
+ }
3315
+ function renderPostgresRowRule({ dialect, context }) {
3316
+ if (dialect.name !== "postgres")
3317
+ throw unsupportedCapability19({ dialect: dialect.name, feature: "postgresRowRules" });
3318
+ const { rule, table, functionIdentity, triggerIdentity, protectedFields, gateField } = context;
3319
+ const qualify = ({ name }) => `${dialect.quoteIdent({ name: name.namespace })}.${dialect.quoteIdent({ name: name.name })}`;
3320
+ const functionName = qualify({ name: functionIdentity.name });
3321
+ const tableName = qualify({ name: table });
3322
+ const triggerName = dialect.quoteIdent({ name: triggerIdentity.name });
3323
+ const constraint = dialect.quoteStringLiteral({ value: triggerIdentity.name });
3324
+ const raise = `RAISE EXCEPTION 'VibeORM row rule refused mutation'
3325
+ ${rule.kind === "appendOnly" ? " " : " "}USING ERRCODE = 'check_violation', CONSTRAINT = ${constraint};`;
3326
+ let body;
3327
+ if (rule.kind === "appendOnly") {
3328
+ body = `BEGIN
3329
+ ${raise}
3330
+ END;`;
3331
+ } else {
3332
+ const terms = protectedFields.map((field) => comparison({ dialect, field }));
3333
+ if (terms.length === 0) {
3334
+ throw new VibeError17({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
3335
+ }
3336
+ const condition = gateField === undefined ? terms.join(`
3337
+ OR `) : `OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
3338
+ ${terms.join(`
3339
+ OR `)}
3340
+ )`;
3341
+ body = `BEGIN
3342
+ IF ${condition} THEN
3343
+ ${raise}
3344
+ END IF;
3345
+ RETURN NEW;
3346
+ END;`;
3347
+ }
3348
+ let marker = "$vibe_row_rule$";
3349
+ for (let suffix = 1;body.includes(marker); suffix += 1)
3350
+ marker = `$vibe_row_rule_${suffix}$`;
3351
+ const createFunction = `CREATE FUNCTION ${functionName}() RETURNS pg_catalog.trigger
3352
+ LANGUAGE plpgsql VOLATILE CALLED ON NULL INPUT SECURITY INVOKER
3353
+ SET search_path = pg_catalog
3354
+ AS ${marker}
3355
+ ${body}
3356
+ ${marker};`;
3357
+ const createTrigger = `CREATE TRIGGER ${triggerName} BEFORE UPDATE${rule.kind === "appendOnly" ? " OR DELETE" : ""} ON ${tableName}
3358
+ FOR EACH ROW EXECUTE FUNCTION ${functionName}();`;
3359
+ return {
3360
+ identities: [functionIdentity, triggerIdentity],
3361
+ create: [{ text: createFunction, values: [] }, { text: createTrigger, values: [] }],
3362
+ drop: [{ text: `DROP TRIGGER ${triggerName} ON ${tableName};`, values: [] }, { text: `DROP FUNCTION ${functionName}();`, values: [] }],
3363
+ protectedFields: protectedFields.map((field) => field.name),
3364
+ ...gateField === undefined ? {} : { gateField: gateField.name }
3365
+ };
3366
+ }
3367
+
3368
+ // packages/sql/src/dialects/sqlite-row-rules.ts
3369
+ import { SQLITE_ROW_RULE_ERROR_PREFIX, VibeError as VibeError18, unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
3370
+ function renderSqliteRowRule({ dialect, context }) {
3371
+ if (dialect.name !== "sqlite")
3372
+ throw unsupportedCapability20({ dialect: dialect.name, feature: "sqliteRowRules" });
3373
+ const { rule, table, triggers, protectedFields, gateField } = context;
3374
+ if (table.namespace !== "main")
3375
+ throw unsupportedCapability20({ dialect: dialect.name, feature: "fixedNamespaces" });
3376
+ const terms = protectedFields.map((field) => {
3377
+ const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
3378
+ return `OLD.${column} IS NOT NEW.${column}`;
3379
+ });
3380
+ if (rule.kind !== "appendOnly" && terms.length === 0)
3381
+ throw new VibeError18({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
3382
+ const condition = rule.kind === "appendOnly" ? "" : gateField === undefined ? `WHEN ${terms.join(`
3383
+ OR `)}
3384
+ ` : `WHEN OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
3385
+ ${terms.join(`
3386
+ OR `)}
3387
+ )
3388
+ `;
3389
+ return {
3390
+ identities: triggers.map(({ identity }) => identity),
3391
+ create: triggers.map(({ event, identity }) => ({
3392
+ text: `CREATE TRIGGER ${dialect.quoteIdent({ name: identity.name })} BEFORE ${event.toUpperCase()} ON ${dialect.quoteIdent({ name: table.name })}
3393
+ FOR EACH ROW
3394
+ ${condition}BEGIN
3395
+ SELECT RAISE(ABORT, ${dialect.quoteStringLiteral({ value: `${SQLITE_ROW_RULE_ERROR_PREFIX}${identity.name}` })});
3396
+ END;`,
3397
+ values: []
3398
+ })),
3399
+ drop: [...triggers].reverse().map(({ identity }) => ({ text: `DROP TRIGGER ${dialect.quoteIdent({ name: identity.name })};`, values: [] })),
3400
+ protectedFields: protectedFields.map((field) => field.name),
3401
+ ...gateField === undefined ? {} : { gateField: gateField.name }
3402
+ };
3403
+ }
3404
+
3405
+ // packages/sql/src/dialects/mysql-row-rules.ts
3406
+ import { MYSQL_ROW_RULE_MESSAGE_MAX_BYTES, VibeError as VibeError19, mysqlRowRuleMessageByteLength, unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
3407
+ function diagnosticLiteral({ name, context, dialect }) {
3408
+ const message = `Check constraint '${name}' is violated.`;
3409
+ if (mysqlRowRuleMessageByteLength({ triggerName: name }) > MYSQL_ROW_RULE_MESSAGE_MAX_BYTES)
3410
+ throw new VibeError19({ 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 } });
3411
+ if (message.includes("\\")) {
3412
+ const hex = Array.from(new TextEncoder().encode(message), (byte) => byte.toString(16).padStart(2, "0")).join("");
3413
+ return `_utf8mb4 X'${hex}'`;
3414
+ }
3415
+ return dialect.quoteStringLiteral({ value: message });
3416
+ }
3417
+ function renderMysqlRowRule({ dialect, context }) {
3418
+ if (dialect.name !== "mysql")
3419
+ throw unsupportedCapability21({ dialect: dialect.name, feature: "mysqlRowRules" });
3420
+ const { rule, table, triggers, protectedFields, gateField, placement } = context;
3421
+ for (const name of Object.keys(placement ?? {}))
3422
+ if (!triggers.some(({ identity }) => identity.name === name))
3423
+ throw new VibeError19({ 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 } });
3424
+ const position = ({ name }) => {
3425
+ const entry = placement?.[name];
3426
+ if (entry === undefined)
3427
+ return "";
3428
+ return "follows" in entry ? ` FOLLOWS ${dialect.quoteIdent({ name: entry.follows })}` : ` PRECEDES ${dialect.quoteIdent({ name: entry.precedes })}`;
3429
+ };
3430
+ const qualify = ({ name }) => table.namespace === "public" ? dialect.quoteIdent({ name }) : `${dialect.quoteIdent({ name: table.namespace })}.${dialect.quoteIdent({ name })}`;
3431
+ const terms = protectedFields.map((field) => {
3432
+ const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
3433
+ return `NOT (OLD.${column} <=> NEW.${column})`;
3434
+ });
3435
+ if (rule.kind !== "appendOnly" && terms.length === 0)
3436
+ throw new VibeError19({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
3437
+ const condition = gateField === undefined ? terms.join(`
3438
+ OR `) : `OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
3439
+ ${terms.join(`
3440
+ OR `)}
3441
+ )`;
3442
+ return {
3443
+ identities: triggers.map(({ identity }) => identity),
3444
+ create: triggers.map(({ event, identity }) => {
3445
+ const signal = `SIGNAL SQLSTATE 'HY000' SET MYSQL_ERRNO = 3819, MESSAGE_TEXT = ${diagnosticLiteral({ name: identity.name, context, dialect })};`;
3446
+ const body = rule.kind === "appendOnly" ? ` ${signal}` : ` IF ${condition} THEN
3447
+ ${signal}
3448
+ END IF;`;
3449
+ return { text: `CREATE TRIGGER ${qualify({ name: identity.name })} BEFORE ${event.toUpperCase()} ON ${qualify({ name: table.name })}
3450
+ FOR EACH ROW${position({ name: identity.name })}
3451
+ BEGIN
3452
+ ${body}
3453
+ END;`, values: [] };
3454
+ }),
3455
+ drop: [...triggers].reverse().map(({ identity }) => ({ text: `DROP TRIGGER ${qualify({ name: identity.name })};`, values: [] })),
3456
+ protectedFields: protectedFields.map((field) => field.name),
3457
+ ...gateField === undefined ? {} : { gateField: gateField.name }
3458
+ };
3459
+ }
3460
+
3461
+ // packages/sql/src/row-rules.ts
3462
+ function renderRowRule({ schema, model, rule, dialect, placement }) {
3463
+ const issue = rowRuleShapeIssue({ value: rule });
3464
+ if (issue !== undefined) {
3465
+ throw new VibeError20({ code: "VIBE_SCHEMA_INVALID", message: issue, meta: { model: model.name } });
3466
+ }
3467
+ if (placement !== undefined && Object.keys(placement).length > 0 && dialect.name !== "mysql")
3468
+ throw unsupportedCapability22({ dialect: dialect.name, feature: "rowRuleTriggerPlacement" });
3469
+ const normalized = normalizeRowRules({ schema, model, rules: [rule], dialect: dialect.name })[0];
3470
+ switch (dialect.name) {
3471
+ case "postgres": {
3472
+ const objects = rowRuleIdentities({ schema, model, rule: normalized, dialect: dialect.name });
3473
+ const functionIdentity = objects.find((object) => object.role === "function")?.identity;
3474
+ const triggerIdentity = objects.find((object) => object.role === "trigger")?.identity;
3475
+ if (functionIdentity?.kind !== "function" || triggerIdentity?.kind !== "trigger")
3476
+ throw internalError2({ message: "PostgreSQL row rules own one function and one trigger", meta: { model: model.name, rule: normalized.name } });
3477
+ const gate = rowRuleGateField({ rule: normalized });
3478
+ const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
3479
+ return renderPostgresRowRule({
3480
+ dialect,
3481
+ context: {
3482
+ model: model.name,
3483
+ table: triggerIdentity.table,
3484
+ rule: normalized,
3485
+ functionIdentity,
3486
+ triggerIdentity: { kind: "trigger", table: triggerIdentity.table, name: triggerIdentity.name },
3487
+ protectedFields: rowRuleProtectedFields({ schema, model, rule: normalized }),
3488
+ ...gateField === undefined ? {} : { gateField }
3489
+ }
3490
+ });
3491
+ }
3492
+ case "sqlite":
3493
+ case "mysql": {
3494
+ const objects = rowRuleIdentities({ schema, model, rule: normalized, dialect: dialect.name });
3495
+ const triggers = objects.map(({ role, identity }) => {
3496
+ if (role !== "trigger" || identity.kind !== "trigger")
3497
+ throw internalError2({ message: "Event row rules own only triggers", meta: { model: model.name, rule: normalized.name } });
3498
+ const event = ["update", "delete"].find((event) => identity.name === rowRuleTriggerName({ rule: normalized, event }));
3499
+ if (event === undefined)
3500
+ throw internalError2({ message: "Row-rule identity has no declared event", meta: { model: model.name, rule: normalized.name, trigger: identity.name } });
3501
+ return { event, identity: { kind: "trigger", table: identity.table, name: identity.name } };
3502
+ });
3503
+ const table = triggers[0]?.identity.table;
3504
+ if (table === undefined)
3505
+ throw internalError2({ message: "Event row rules require at least one trigger", meta: { model: model.name, rule: normalized.name } });
3506
+ const gate = rowRuleGateField({ rule: normalized });
3507
+ const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
3508
+ const context = {
3509
+ model: model.name,
3510
+ table,
3511
+ rule: normalized,
3512
+ triggers,
3513
+ protectedFields: rowRuleProtectedFields({ schema, model, rule: normalized }),
3514
+ ...gateField === undefined ? {} : { gateField },
3515
+ ...placement === undefined ? {} : { placement }
3516
+ };
3517
+ return dialect.name === "sqlite" ? renderSqliteRowRule({ dialect, context }) : renderMysqlRowRule({ dialect, context });
3518
+ }
3519
+ }
3520
+ }
2528
3521
  // packages/sql/src/dialects/sqlite-json-equality.ts
2529
3522
  function renderSqliteJsonEquality(params) {
2530
3523
  const q = (name) => params.dialect.quoteIdent({ name });
@@ -2540,7 +3533,7 @@ function renderSqliteJsonEquality(params) {
2540
3533
  }
2541
3534
 
2542
3535
  // packages/sql/src/dialects/diagnostics.ts
2543
- import { unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
3536
+ import { unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
2544
3537
  var QUERIES = Object.freeze({
2545
3538
  postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
2546
3539
  sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
@@ -2548,7 +3541,7 @@ var QUERIES = Object.freeze({
2548
3541
  function renderDiagnostic(params) {
2549
3542
  const query = QUERIES[params.query];
2550
3543
  if (params.dialect.name !== query.dialect) {
2551
- throw unsupportedCapability12({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
3544
+ throw unsupportedCapability23({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
2552
3545
  }
2553
3546
  return { text: query.text, values: [], returning: "none" };
2554
3547
  }
@@ -2557,7 +3550,7 @@ function isFixedDiagnostic(params) {
2557
3550
  }
2558
3551
 
2559
3552
  // packages/sql/src/dialects/postgres-diagnostics.ts
2560
- import { VibeError as VibeError13, unsupportedCapability as unsupportedCapability13 } from "@vibeorm/schema";
3553
+ import { VibeError as VibeError21, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
2561
3554
  function renderPostgresExplain(params) {
2562
3555
  const { dialect, query, options } = params;
2563
3556
  requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
@@ -2592,7 +3585,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
2592
3585
  });
2593
3586
  function requirePostgres(params) {
2594
3587
  if (params.dialect.name !== "postgres") {
2595
- throw unsupportedCapability13({
3588
+ throw unsupportedCapability24({
2596
3589
  dialect: params.dialect.name,
2597
3590
  feature: params.feature,
2598
3591
  hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
@@ -2610,7 +3603,7 @@ function renderPostgresWorkload(params) {
2610
3603
  if (query === "statementSnapshot") {
2611
3604
  const bounded = limit ?? 20;
2612
3605
  if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
2613
- throw new VibeError13({
3606
+ throw new VibeError21({
2614
3607
  code: "VIBE_CONFIG",
2615
3608
  message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
2616
3609
  meta: { limit }
@@ -2629,7 +3622,7 @@ function renderPostgresWorkload(params) {
2629
3622
  function requireArg(params) {
2630
3623
  const { value, name, query } = params;
2631
3624
  if (value === undefined || value.length === 0) {
2632
- throw new VibeError13({
3625
+ throw new VibeError21({
2633
3626
  code: "VIBE_CONFIG",
2634
3627
  message: `the "${query}" diagnostic read requires a ${name}`,
2635
3628
  meta: { query, argument: name }
@@ -2639,10 +3632,10 @@ function requireArg(params) {
2639
3632
  }
2640
3633
 
2641
3634
  // packages/sql/src/dialects/postgres-rls-readiness.ts
2642
- import { unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
3635
+ import { unsupportedCapability as unsupportedCapability26 } from "@vibeorm/schema";
2643
3636
 
2644
3637
  // packages/sql/src/dialects/postgres-rls.ts
2645
- import { RlsContextError, VibeError as VibeError14, assertNativeRlsAdmission, assertValidSchemaIR, unsupportedCapability as unsupportedCapability14, validateRlsContextIR } from "@vibeorm/schema";
3638
+ import { RlsContextError, VibeError as VibeError22, assertNativeRlsAdmission, assertValidSchemaIR, unsupportedCapability as unsupportedCapability25, validateRlsContextIR } from "@vibeorm/schema";
2646
3639
  var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
2647
3640
  var COLUMN_TYPES = {
2648
3641
  String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
@@ -2655,18 +3648,18 @@ var COLUMN_TYPES = {
2655
3648
  };
2656
3649
  function requireRls(params) {
2657
3650
  if (!params.dialect.capabilities.nativeRowLevelSecurity) {
2658
- throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
3651
+ throw unsupportedCapability25({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
2659
3652
  }
2660
3653
  }
2661
3654
  function identifier2(params) {
2662
3655
  if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
2663
- throw new VibeError14({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
3656
+ throw new VibeError22({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
2664
3657
  }
2665
3658
  return params.dialect.quoteIdent({ name: params.name });
2666
3659
  }
2667
3660
  function tableRef(params) {
2668
3661
  if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
2669
- throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
3662
+ throw unsupportedCapability25({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
2670
3663
  }
2671
3664
  return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
2672
3665
  }
@@ -2707,7 +3700,7 @@ function renderRlsColumnType(params) {
2707
3700
  if (native !== undefined)
2708
3701
  return native;
2709
3702
  }
2710
- throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
3703
+ throw unsupportedCapability25({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
2711
3704
  }
2712
3705
  function protectedModel(params) {
2713
3706
  requireRls(params);
@@ -2715,7 +3708,7 @@ function protectedModel(params) {
2715
3708
  assertValidSchemaIR({ schema: params.schema });
2716
3709
  const model = params.schema.models.find((entry) => entry.name === params.model);
2717
3710
  if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
2718
- throw new VibeError14({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
3711
+ throw new VibeError22({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
2719
3712
  }
2720
3713
  return model;
2721
3714
  }
@@ -2748,7 +3741,7 @@ function predicate(params) {
2748
3741
  }).join(" AND ");
2749
3742
  return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
2750
3743
  }
2751
- throw new VibeError14({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
3744
+ throw new VibeError22({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
2752
3745
  }
2753
3746
  function renderRlsPolicy(params) {
2754
3747
  const model = protectedModel(params);
@@ -2782,7 +3775,7 @@ function renderRlsContext(params) {
2782
3775
  function renderRlsRole(params) {
2783
3776
  requireRls(params);
2784
3777
  if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
2785
- throw new VibeError14({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
3778
+ throw new VibeError22({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
2786
3779
  }
2787
3780
  const collector = new ParamCollector(params.dialect);
2788
3781
  return query({ text: `SELECT set_config('role', ${collector.add(params.role)}, true)`, values: collector.values });
@@ -2934,7 +3927,7 @@ ORDER BY c.relname, p.polname`,
2934
3927
  });
2935
3928
  function requireRls2(params) {
2936
3929
  if (!params.dialect.capabilities.nativeRowLevelSecurity) {
2937
- throw unsupportedCapability15({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
3930
+ throw unsupportedCapability26({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
2938
3931
  }
2939
3932
  }
2940
3933
  function renderRlsReadinessQuery(params) {
@@ -2966,7 +3959,7 @@ function renderRlsPolicyPath(params) {
2966
3959
  }
2967
3960
 
2968
3961
  // packages/sql/src/render.ts
2969
- import { assertSqlNamespace as assertSqlNamespace2, internalError as internalError2, unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
3962
+ import { assertSqlNamespace as assertSqlNamespace2, internalError as internalError3, unsupportedCapability as unsupportedCapability27 } from "@vibeorm/schema";
2970
3963
 
2971
3964
  // packages/sql/src/like.ts
2972
3965
  function escapeLikePattern(params) {
@@ -2992,6 +3985,9 @@ function strictMatchPattern(params) {
2992
3985
  }
2993
3986
  return op === "contains" ? containsPattern({ raw: value }) : op === "startsWith" ? startsWithPattern({ raw: value }) : endsWithPattern({ raw: value });
2994
3987
  }
3988
+ function strictMatchCollationPrefilter(params) {
3989
+ return params.strategy === "binary" && params.op === "startsWith" && !params.negate;
3990
+ }
2995
3991
 
2996
3992
  // packages/sql/src/render.ts
2997
3993
  function renderStatement(params) {
@@ -3051,13 +4047,13 @@ function quoteCol(dialect, column) {
3051
4047
  const ref = column.table === undefined ? name : `${dialect.quoteIdent({ name: column.table })}.${name}`;
3052
4048
  if (column.castReal === true) {
3053
4049
  if (dialect.name !== "sqlite") {
3054
- throw internalError2({ message: "ColRef.castReal is a sqlite Decimal construct" });
4050
+ throw internalError3({ message: "ColRef.castReal is a sqlite Decimal construct" });
3055
4051
  }
3056
4052
  return `CAST(${ref} AS REAL)`;
3057
4053
  }
3058
4054
  if (column.castJsonb !== undefined) {
3059
4055
  if (dialect.name !== "postgres") {
3060
- throw internalError2({ message: "ColRef.castJsonb is a postgres json construct" });
4056
+ throw internalError3({ message: "ColRef.castJsonb is a postgres json construct" });
3061
4057
  }
3062
4058
  return `${ref}::${column.castJsonb}`;
3063
4059
  }
@@ -3074,7 +4070,7 @@ function jsonCellCast(dialect, cell) {
3074
4070
  if (cell.storage !== "json" || dialect.jsonParamCast === "")
3075
4071
  return dialect.jsonParamCast;
3076
4072
  if (dialect.name !== "postgres") {
3077
- throw internalError2({ message: "JsonParamCell storage json is a postgres construct" });
4073
+ throw internalError3({ message: "JsonParamCell storage json is a postgres construct" });
3078
4074
  }
3079
4075
  return "::text::json";
3080
4076
  }
@@ -3095,7 +4091,7 @@ function quoteProjection(dialect, column) {
3095
4091
  if (column.transform === "castText")
3096
4092
  return `${textProjection({ dialect, expression: ref })} AS ${alias}`;
3097
4093
  if (dialect.name !== "postgres") {
3098
- throw internalError2({ message: "encodeBase64 is a postgres construct" });
4094
+ throw internalError3({ message: "encodeBase64 is a postgres construct" });
3099
4095
  }
3100
4096
  return `encode(${ref}, 'base64') AS ${alias}`;
3101
4097
  }
@@ -3117,7 +4113,7 @@ function renderTable(dialect, table) {
3117
4113
  }
3118
4114
  function assertCount(value, what) {
3119
4115
  if (!Number.isSafeInteger(value) || value < 0) {
3120
- throw internalError2({ message: `${what} must be a non-negative integer, got ${value}` });
4116
+ throw internalError3({ message: `${what} must be a non-negative integer, got ${value}` });
3121
4117
  }
3122
4118
  }
3123
4119
  var COMPARISON_SQL2 = {
@@ -3149,7 +4145,7 @@ function renderAggRef(dialect, agg) {
3149
4145
  function assertScalarArrays(dialect, feature) {
3150
4146
  if (dialect.capabilities.scalarArrays)
3151
4147
  return;
3152
- throw unsupportedCapability16({
4148
+ throw unsupportedCapability27({
3153
4149
  dialect: dialect.name,
3154
4150
  feature,
3155
4151
  hint: "only postgres has native array columns \u2014 model the list as a related table instead"
@@ -3161,7 +4157,7 @@ function renderExpr(params) {
3161
4157
  case "cmp": {
3162
4158
  const op = COMPARISON_SQL2[expr.op];
3163
4159
  if (op === undefined) {
3164
- throw internalError2({ message: `unknown comparison operator "${expr.op}"` });
4160
+ throw internalError3({ message: `unknown comparison operator "${expr.op}"` });
3165
4161
  }
3166
4162
  if (isDbNowMarker(expr.value)) {
3167
4163
  return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
@@ -3172,7 +4168,7 @@ function renderExpr(params) {
3172
4168
  case "cmpCol": {
3173
4169
  const op = COMPARISON_SQL2[expr.op];
3174
4170
  if (op === undefined) {
3175
- throw internalError2({ message: `unknown comparison operator "${expr.op}"` });
4171
+ throw internalError3({ message: `unknown comparison operator "${expr.op}"` });
3176
4172
  }
3177
4173
  return `${quoteCol(dialect, expr.left)} ${op} ${quoteCol(dialect, expr.right)}`;
3178
4174
  }
@@ -3196,11 +4192,11 @@ function renderExpr(params) {
3196
4192
  }
3197
4193
  case "tupleIn": {
3198
4194
  if (expr.columns.length === 0) {
3199
- throw internalError2({ message: "tupleIn needs at least one column" });
4195
+ throw internalError3({ message: "tupleIn needs at least one column" });
3200
4196
  }
3201
4197
  for (const row of expr.rows) {
3202
4198
  if (row.length !== expr.columns.length) {
3203
- throw internalError2({
4199
+ throw internalError3({
3204
4200
  message: `tupleIn row has ${row.length} values for ${expr.columns.length} columns`
3205
4201
  });
3206
4202
  }
@@ -3221,6 +4217,32 @@ function renderExpr(params) {
3221
4217
  const base = conjunctions.length === 1 ? conjunctions[0] : `(${conjunctions.join(" OR ")})`;
3222
4218
  return expr.negate === true ? `NOT ${base.startsWith("(") ? base : `(${base})`}` : base;
3223
4219
  }
4220
+ case "keyList":
4221
+ return renderKeyList({ dialect, expr, collector });
4222
+ case "rowCompare": {
4223
+ const op = COMPARISON_SQL2[expr.op];
4224
+ if (op === undefined || expr.columns.length === 0 || expr.values.length !== expr.columns.length) {
4225
+ throw internalError3({ message: `rowCompare needs a range operator and one value per column (${expr.columns.length} columns, ${expr.values.length} values)` });
4226
+ }
4227
+ const columns = expr.columns.map((column) => quoteCol(dialect, column));
4228
+ const placeholders = expr.values.map((value) => collector.add(value));
4229
+ return `(${columns.join(", ")}) ${op} (${placeholders.join(", ")})`;
4230
+ }
4231
+ case "inSelect": {
4232
+ if (expr.columns.length === 0)
4233
+ throw internalError3({ message: "inSelect needs at least one column" });
4234
+ if (dialect.name === "mysql" && (expr.select.limit !== undefined || expr.select.offset !== undefined)) {
4235
+ throw unsupportedCapability27({
4236
+ dialect: dialect.name,
4237
+ feature: "LIMIT inside an IN subquery",
4238
+ hint: "mysql refuses LIMIT directly inside an IN subquery (error 1235); wrap the limited select in a derived table"
4239
+ });
4240
+ }
4241
+ const columns = expr.columns.map((column) => quoteCol(dialect, column));
4242
+ const tested = columns.length === 1 ? columns[0] : `(${columns.join(", ")})`;
4243
+ const sub = renderSelect({ dialect, statement: expr.select, collector });
4244
+ return `${tested} IN (${sub.text})`;
4245
+ }
3224
4246
  case "like": {
3225
4247
  const column = quoteCol(dialect, expr.column);
3226
4248
  const placeholder = collector.add(expr.pattern);
@@ -3249,10 +4271,17 @@ function renderExpr(params) {
3249
4271
  if (dialect.strictMatchStrategy === "glob") {
3250
4272
  base = `${column} GLOB ${collector.add(pattern)}`;
3251
4273
  } else {
4274
+ const prefilter = strictMatchCollationPrefilter({
4275
+ strategy: dialect.strictMatchStrategy,
4276
+ op: expr.op,
4277
+ negate: expr.negate === true
4278
+ }) ? `${column} LIKE ${collector.add(pattern)}${dialect.likeEscapeClause ?? ""}` : null;
3252
4279
  const placeholder = collector.add(pattern);
3253
4280
  base = dialect.strictMatchStrategy === "binary" ? `${column} LIKE CAST(${placeholder} AS BINARY)` : `${column} LIKE ${placeholder}`;
3254
4281
  if (dialect.likeEscapeClause !== null)
3255
4282
  base = `${base}${dialect.likeEscapeClause}`;
4283
+ if (prefilter !== null)
4284
+ base = `(${prefilter} AND ${base})`;
3256
4285
  }
3257
4286
  }
3258
4287
  return expr.negate === true ? `NOT (${base})` : base;
@@ -3274,7 +4303,7 @@ function renderExpr(params) {
3274
4303
  case "aggCmp": {
3275
4304
  const op = COMPARISON_SQL2[expr.op];
3276
4305
  if (op === undefined) {
3277
- throw internalError2({ message: `unknown comparison operator "${expr.op}"` });
4306
+ throw internalError3({ message: `unknown comparison operator "${expr.op}"` });
3278
4307
  }
3279
4308
  return `${renderAggRef(dialect, expr.agg)} ${op} ${collector.add(expr.value)}`;
3280
4309
  }
@@ -3352,10 +4381,146 @@ function listComparison(params) {
3352
4381
  }
3353
4382
  return { column: quoteCol(dialect, column), operand: collector.add(value) };
3354
4383
  }
4384
+ function renderKeyList(params) {
4385
+ const { dialect, expr, collector } = params;
4386
+ const arity = expr.columns.length;
4387
+ if (arity === 0)
4388
+ throw internalError3({ message: "keyList needs at least one column" });
4389
+ if (expr.members.length !== arity) {
4390
+ throw internalError3({ message: `keyList has ${expr.members.length} members for ${arity} columns` });
4391
+ }
4392
+ for (const row of expr.rows) {
4393
+ if (row.length !== arity)
4394
+ throw internalError3({ message: `keyList row has ${row.length} values for ${arity} columns` });
4395
+ }
4396
+ if (expr.rows.length === 0)
4397
+ return "1 = 0";
4398
+ const columns = expr.columns.map((column) => quoteCol(dialect, column));
4399
+ const tuple = arity === 1 ? columns[0] : `(${columns.join(", ")})`;
4400
+ if (dialect.keyListStrategy === "unnest") {
4401
+ const types = expr.members.map((member) => postgresKeyListElementType({ dialect, member }));
4402
+ if (!keyListBindsUntypedRows({ members: expr.members })) {
4403
+ const arrays = types.map((type, index) => `${collector.add(expr.rows.map((row) => row[index]))}::${type}[]`);
4404
+ return `${tuple} IN (SELECT * FROM unnest(${arrays.join(", ")}))`;
4405
+ }
4406
+ }
4407
+ if (dialect.timeOfDayInStrategy === "equalities" && expr.members.some((member) => member.timeOfDay === true)) {
4408
+ const conjunctions = expr.rows.map((row) => {
4409
+ const terms = row.map((value, index) => `${columns[index]} = ${collector.add(value)}`);
4410
+ return terms.length === 1 ? terms[0] : `(${terms.join(" AND ")})`;
4411
+ });
4412
+ return conjunctions.length === 1 ? conjunctions[0] : `(${conjunctions.join(" OR ")})`;
4413
+ }
4414
+ const tuples = expr.rows.map((row) => `(${collector.addMany(row).join(", ")})`);
4415
+ return dialect.keyListStrategy === "values" ? `${tuple} IN (VALUES ${tuples.join(", ")})` : `${tuple} IN (${tuples.join(", ")})`;
4416
+ }
4417
+ var KEY_PAGE_KEYS = "__vibe_keys";
4418
+ var KEY_PAGE_IDS = "__vibe_ids";
4419
+ var KEY_PAGE_ROWS = "__vibe_page";
4420
+ var KEY_PAGE_SELECT_MEMBERS = new Set(["kind", "table", "columns", "where", "orderBy", "limit", "offset"]);
4421
+ function renderKeyPage(params) {
4422
+ const { dialect, page, collector } = params;
4423
+ const { select } = page;
4424
+ const arity = page.keyColumns.length;
4425
+ if (arity === 0 || page.members.length !== arity) {
4426
+ throw internalError3({ message: `keyPage has ${page.members.length} members for ${arity} key columns` });
4427
+ }
4428
+ if (page.rows.length === 0)
4429
+ throw internalError3({ message: "keyPage needs at least one key" });
4430
+ for (const row of page.rows) {
4431
+ if (row.length !== arity)
4432
+ throw internalError3({ message: `keyPage row has ${row.length} values for ${arity} key columns` });
4433
+ }
4434
+ if (page.identity.length === 0)
4435
+ throw internalError3({ message: "keyPage needs the child's identity columns" });
4436
+ const order = select.orderBy ?? [];
4437
+ if (order.length === 0 || order.some((term) => !("column" in term))) {
4438
+ throw internalError3({ message: "keyPage needs a non-empty order of plain column terms" });
4439
+ }
4440
+ if (select.table.alias !== undefined || Object.keys(select).some((member) => !KEY_PAGE_SELECT_MEMBERS.has(member))) {
4441
+ throw internalError3({ message: "keyPage select carries a member the per-key page cannot render" });
4442
+ }
4443
+ if (dialect.keyPageStrategy === "window") {
4444
+ throw unsupportedCapability27({
4445
+ dialect: dialect.name,
4446
+ feature: "a per-key page (keyPage)",
4447
+ hint: "the runtime keeps the ROW_NUMBER window on this dialect"
4448
+ });
4449
+ }
4450
+ const pageRef = (column) => ({ ...column, table: KEY_PAGE_ROWS });
4451
+ const projection = select.columns.map((column) => quoteProjection(dialect, pageRef(column)));
4452
+ const withKey = (keyRef) => {
4453
+ const equalities = page.keyColumns.map((column, index) => ({ kind: "cmpCol", op: "eq", left: column, right: keyRef(index) }));
4454
+ const operands = select.where === undefined ? equalities : [...equalities, select.where];
4455
+ return operands.length === 1 ? operands[0] : { kind: "and", operands };
4456
+ };
4457
+ const keyPage = (where, rank) => ({
4458
+ kind: "select",
4459
+ table: select.table,
4460
+ columns: page.identity,
4461
+ ...rank ? { rowNumber: { partitionBy: page.keyColumns, orderBy: order, alias: page.rankAlias } } : {},
4462
+ where,
4463
+ orderBy: order,
4464
+ ...select.limit === undefined ? {} : { limit: select.limit },
4465
+ ...select.offset === undefined ? {} : { offset: select.offset }
4466
+ });
4467
+ const rows = renderTable(dialect, { ...select.table, alias: KEY_PAGE_ROWS });
4468
+ const keyAlias = select.table.name === KEY_PAGE_KEYS ? `${KEY_PAGE_KEYS}_1` : KEY_PAGE_KEYS;
4469
+ const keys = dialect.quoteIdent({ name: keyAlias });
4470
+ if (dialect.keyPageStrategy === "lateral") {
4471
+ if (keyListBindsUntypedRows({ members: page.members })) {
4472
+ throw unsupportedCapability27({
4473
+ dialect: dialect.name,
4474
+ feature: "a per-key page over a DateTime key without a declared native type",
4475
+ hint: "the runtime keeps the ROW_NUMBER window for such keys"
4476
+ });
4477
+ }
4478
+ const types = page.members.map((member) => postgresKeyListElementType({ dialect, member }));
4479
+ const arrays = types.map((type, index) => `${collector.add(page.rows.map((row) => row[index]))}::${type}[]`);
4480
+ const names = page.keyColumns.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
4481
+ const ids = renderSelect({ dialect, statement: keyPage(withKey((index) => ({ table: keyAlias, name: `__vibe_k${index}` })), true), collector });
4482
+ const on = page.identity.map((column) => `${quoteCol(dialect, pageRef(column))} = ${quoteCol(dialect, { table: KEY_PAGE_IDS, name: column.name })}`).join(" AND ");
4483
+ const rank = quoteCol(dialect, { table: KEY_PAGE_IDS, name: page.rankAlias });
4484
+ return `SELECT ${[...projection, rank].join(", ")} FROM unnest(${arrays.join(", ")}) AS ${keys} (${names.join(", ")}) CROSS JOIN LATERAL (${ids.text}) AS ${dialect.quoteIdent({ name: KEY_PAGE_IDS })} INNER JOIN ${rows} ON ${on}`;
4485
+ }
4486
+ const partition = page.keyColumns.map((column) => quoteCol(dialect, pageRef(column))).join(", ");
4487
+ const rankOrder = order.map((term) => renderOrderTerm({ dialect, term: { ...term, column: pageRef(term.column) }, collector })).join(", ");
4488
+ const rank = `ROW_NUMBER() OVER (PARTITION BY ${partition} ORDER BY ${rankOrder}) AS ${dialect.quoteIdent({ name: page.rankAlias })}`;
4489
+ const tuples = page.rows.map((row) => `(${collector.addMany(row).join(", ")})`);
4490
+ const membership = renderExpr({
4491
+ dialect,
4492
+ expr: { kind: "inSelect", columns: page.identity.map(pageRef), select: keyPage(withKey((index) => ({ table: keyAlias, name: `column${index + 1}` })), false) },
4493
+ collector
4494
+ });
4495
+ return `SELECT ${[...projection, rank].join(", ")} FROM (VALUES ${tuples.join(", ")}) AS ${keys} INNER JOIN ${rows} ON ${membership}`;
4496
+ }
4497
+ var UNION_BRANCH_MEMBERS = new Set(["kind", "table", "columns", "where", "orderBy", "limit"]);
4498
+ function renderUnionPage(params) {
4499
+ const { dialect, statement, collector } = params;
4500
+ const branches = statement.fromUnion ?? [];
4501
+ if (branches.length < 2)
4502
+ throw internalError3({ message: "fromUnion needs at least two branches" });
4503
+ if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.table.alias !== undefined) {
4504
+ throw internalError3({ message: "fromUnion uses table.name as the derived-table alias \u2014 fromSelect, keyPage and table.alias must be unset" });
4505
+ }
4506
+ if (statement.rowLock !== undefined)
4507
+ throw internalError3({ message: "a locking read cannot read from a union page" });
4508
+ const outputNames = (branch) => branch.columns.map((column) => column.alias ?? column.name).join("\x00");
4509
+ const names = outputNames(branches[0]);
4510
+ return branches.map((branch, index) => {
4511
+ if (branch.columns.length === 0 || Object.keys(branch).some((member) => !UNION_BRANCH_MEMBERS.has(member))) {
4512
+ throw internalError3({ message: "a fromUnion branch must be a plain SELECT <columns> FROM <table> [WHERE] [ORDER BY] [LIMIT]" });
4513
+ }
4514
+ if (outputNames(branch) !== names)
4515
+ throw internalError3({ message: "fromUnion branches must project the same output names in the same order" });
4516
+ const inner = renderSelect({ dialect, statement: branch, collector });
4517
+ return `SELECT * FROM (${inner.text}) AS ${dialect.quoteIdent({ name: `__vibe_u${index}` })}`;
4518
+ }).join(" UNION ALL ");
4519
+ }
3355
4520
  function jsonStringPattern(params) {
3356
4521
  const { expr } = params;
3357
4522
  if (typeof expr.value !== "string") {
3358
- throw internalError2({ message: `json ${expr.op} expects a string operand` });
4523
+ throw internalError3({ message: `json ${expr.op} expects a string operand` });
3359
4524
  }
3360
4525
  if (expr.op === "string_contains")
3361
4526
  return containsPattern({ raw: expr.value });
@@ -3379,7 +4544,7 @@ function renderJsonLike(params) {
3379
4544
  function renderJsonFilter(params) {
3380
4545
  const { dialect, expr, collector } = params;
3381
4546
  if (!dialect.supportedJsonOps.includes(expr.op)) {
3382
- throw unsupportedCapability16({
4547
+ throw unsupportedCapability27({
3383
4548
  dialect: dialect.name,
3384
4549
  feature: `the json filter "${expr.op}"`,
3385
4550
  hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
@@ -3446,7 +4611,7 @@ function renderJsonFilter(params) {
3446
4611
  const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
3447
4612
  const tests = elements.map((element) => {
3448
4613
  if (element === null || typeof element === "object" && element !== null) {
3449
- throw unsupportedCapability16({
4614
+ throw unsupportedCapability27({
3450
4615
  dialect: dialect.name,
3451
4616
  feature: "json array_contains with object/array/null elements",
3452
4617
  hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
@@ -3460,7 +4625,7 @@ function renderJsonFilter(params) {
3460
4625
  return tests.length === 0 ? guard : `(${[guard, ...tests].join(" AND ")})`;
3461
4626
  }
3462
4627
  default:
3463
- throw internalError2({ message: `json op "${expr.op}" leaked past supportedJsonOps on sqlite` });
4628
+ throw internalError3({ message: `json op "${expr.op}" leaked past supportedJsonOps on sqlite` });
3464
4629
  }
3465
4630
  }
3466
4631
  const column = quoteCol(dialect, expr.column);
@@ -3515,11 +4680,11 @@ function renderSelectExpr(params) {
3515
4680
  function renderPositionalRow(params) {
3516
4681
  const { dialect, join, subAlias } = params;
3517
4682
  if (join.select.columns.length === 0 || join.select.selectExpr !== undefined || (join.select.aggregates?.length ?? 0) > 0 || join.select.rowNumber !== undefined) {
3518
- throw internalError2({ message: "positional lateral rows require an explicit column-only projection" });
4683
+ throw internalError3({ message: "positional lateral rows require an explicit column-only projection" });
3519
4684
  }
3520
4685
  const names = join.select.columns.map((column) => projectionOutputName({ column }));
3521
4686
  if (new Set(names).size !== names.length) {
3522
- throw internalError2({ message: "positional lateral projection has duplicate output names" });
4687
+ throw internalError3({ message: "positional lateral projection has duplicate output names" });
3523
4688
  }
3524
4689
  const values = names.map((name) => `${subAlias}.${dialect.quoteIdent({ name })}`);
3525
4690
  const chunks = [];
@@ -3527,7 +4692,7 @@ function renderPositionalRow(params) {
3527
4692
  chunks.push(`json_build_array(${values.slice(index, index + JSON_TRANSPORT.chunkSize).join(", ")})`);
3528
4693
  }
3529
4694
  if (chunks.length > JSON_TRANSPORT.chunkSize) {
3530
- throw internalError2({ message: "positional lateral projection exceeds the transport width limit" });
4695
+ throw internalError3({ message: "positional lateral projection exceeds the transport width limit" });
3531
4696
  }
3532
4697
  return chunks.length === 1 ? chunks[0] : `json_build_array(${chunks.join(", ")})`;
3533
4698
  }
@@ -3535,8 +4700,8 @@ function renderLateralJoin(params) {
3535
4700
  const { dialect, join, collector } = params;
3536
4701
  const inner = renderSelect({ dialect, statement: join.select, collector });
3537
4702
  if (join.agg === "count") {
3538
- 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) {
3539
- throw internalError2({ message: "count lateral must return one ungrouped COUNT(*) under its column alias" });
4703
+ 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) {
4704
+ throw internalError3({ message: "count lateral must return one ungrouped COUNT(*) under its column alias" });
3540
4705
  }
3541
4706
  return `LEFT JOIN LATERAL (${inner.text}) AS ${dialect.quoteIdent({ name: join.alias })} ON true`;
3542
4707
  }
@@ -3567,8 +4732,8 @@ function renderLateralJoin(params) {
3567
4732
  function renderCountSubquery(params) {
3568
4733
  const { dialect, subquery, collector } = params;
3569
4734
  const select = subquery.select;
3570
- 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.rowLock !== undefined || select.countSubqueries !== undefined) {
3571
- throw internalError2({ message: "count subquery must be one ungrouped COUNT(*)" });
4735
+ 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) {
4736
+ throw internalError3({ message: "count subquery must be one ungrouped COUNT(*)" });
3572
4737
  }
3573
4738
  const inner = renderSelect({ dialect, statement: select, collector });
3574
4739
  return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
@@ -3598,27 +4763,27 @@ function renderKeyTableJoin(params) {
3598
4763
  const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
3599
4764
  const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
3600
4765
  if (!admitted) {
3601
- throw unsupportedCapability16({
4766
+ throw unsupportedCapability27({
3602
4767
  dialect: dialect.name,
3603
4768
  feature: "relation key table",
3604
4769
  hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
3605
4770
  });
3606
4771
  }
3607
4772
  if (join.columns.length === 0 || join.columns.length !== join.members.length) {
3608
- throw internalError2({ message: `key table needs one member per column, got ${join.members.length} for ${join.columns.length}` });
4773
+ throw internalError3({ message: `key table needs one member per column, got ${join.members.length} for ${join.columns.length}` });
3609
4774
  }
3610
4775
  if (join.rows.length === 0)
3611
- throw internalError2({ message: "key table needs at least one key row" });
4776
+ throw internalError3({ message: "key table needs at least one key row" });
3612
4777
  for (const member of join.members) {
3613
4778
  if (member.kind === "decimal" && !(Number.isInteger(member.scale) && member.scale >= 0 && member.scale <= 30)) {
3614
- throw internalError2({ message: `key table decimal scale must be an integer 0-30, got ${String(member.scale)}` });
4779
+ throw internalError3({ message: `key table decimal scale must be an integer 0-30, got ${String(member.scale)}` });
3615
4780
  }
3616
4781
  }
3617
4782
  const alias = dialect.quoteIdent({ name: join.alias });
3618
4783
  const memberNames = join.members.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
3619
4784
  const rows = join.rows.map((row, ordinal) => {
3620
4785
  if (row.length !== join.members.length) {
3621
- throw internalError2({ message: `key table row has ${row.length} values for ${join.members.length} members` });
4786
+ throw internalError3({ message: `key table row has ${row.length} values for ${join.members.length} members` });
3622
4787
  }
3623
4788
  const cells = row.map((value, index) => {
3624
4789
  const placeholder = collector.add(value);
@@ -3645,13 +4810,25 @@ function renderKeyTableJoin(params) {
3645
4810
  const on = join.columns.map((column, index) => `${quoteCol(dialect, column)} = ${alias}.${memberNames[index]}`).join(" AND ");
3646
4811
  return `INNER JOIN (VALUES ${rows.join(", ")}) AS ${alias} (${[...memberNames, dialect.quoteIdent({ name: join.ordinalAlias })].join(", ")}) ON ${on}`;
3647
4812
  }
4813
+ function renderLeftJoin(params) {
4814
+ const { dialect, join, collector } = params;
4815
+ if (join.on.length === 0) {
4816
+ throw internalError3({ message: "left join needs at least one ON pair" });
4817
+ }
4818
+ if (join.select.columns.length === 0 || join.select.columns.some((column) => column.alias === undefined)) {
4819
+ throw internalError3({ message: "a left join's derived select must alias every projected column" });
4820
+ }
4821
+ const inner = renderSelect({ dialect, statement: join.select, collector });
4822
+ const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
4823
+ return `LEFT JOIN (${inner.text}) AS ${dialect.quoteIdent({ name: join.alias })} ON ${on}`;
4824
+ }
3648
4825
  function renderSelect(params) {
3649
4826
  const { dialect, statement, collector } = params;
3650
4827
  if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
3651
- throw internalError2({ message: "only countAll can combine with a column list" });
4828
+ throw internalError3({ message: "only countAll can combine with a column list" });
3652
4829
  }
3653
4830
  if (statement.selectExpr !== undefined && statement.aggregates !== undefined && statement.aggregates.length > 0) {
3654
- throw internalError2({ message: "selectExpr and aggregates cannot combine" });
4831
+ throw internalError3({ message: "selectExpr and aggregates cannot combine" });
3655
4832
  }
3656
4833
  const columnList = statement.columns.map((column) => quoteProjection(dialect, column));
3657
4834
  if (statement.selectExpr !== undefined) {
@@ -3672,16 +4849,19 @@ function renderSelect(params) {
3672
4849
  if (statement.rowNumber !== undefined) {
3673
4850
  const rowNumber = statement.rowNumber;
3674
4851
  if (rowNumber.partitionBy.length === 0) {
3675
- throw internalError2({ message: "rowNumber needs at least one partition column" });
4852
+ throw internalError3({ message: "rowNumber needs at least one partition column" });
3676
4853
  }
3677
4854
  const partition = rowNumber.partitionBy.map((column) => quoteCol(dialect, column)).join(", ");
3678
4855
  const order = rowNumber.orderBy.length === 0 ? "" : ` ORDER BY ${rowNumber.orderBy.map((term) => renderOrderTerm({ dialect, term, collector })).join(", ")}`;
3679
4856
  columnList.push(`ROW_NUMBER() OVER (PARTITION BY ${partition}${order}) AS ${dialect.quoteIdent({ name: rowNumber.alias })}`);
3680
4857
  }
3681
- let distinctPrefix = "";
4858
+ let distinctPrefix = statement.distinct === true ? "DISTINCT " : "";
4859
+ if (statement.distinct === true && statement.distinctOn !== undefined) {
4860
+ throw internalError3({ message: "distinct and distinctOn cannot combine" });
4861
+ }
3682
4862
  if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
3683
4863
  if (!dialect.capabilities.distinctOn) {
3684
- throw unsupportedCapability16({
4864
+ throw unsupportedCapability27({
3685
4865
  dialect: dialect.name,
3686
4866
  feature: "SELECT DISTINCT ON",
3687
4867
  hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
@@ -3691,9 +4871,16 @@ function renderSelect(params) {
3691
4871
  }
3692
4872
  const columns = columnList.length === 0 ? "*" : columnList.join(", ");
3693
4873
  let from;
3694
- if (statement.fromSelect !== undefined) {
4874
+ if (statement.fromUnion !== undefined) {
4875
+ from = `(${renderUnionPage({ dialect, statement, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
4876
+ } else if (statement.keyPage !== undefined) {
4877
+ if (statement.fromSelect !== undefined || statement.table.alias !== undefined) {
4878
+ throw internalError3({ message: "keyPage uses table.name as the derived-table alias \u2014 fromSelect and table.alias must be unset" });
4879
+ }
4880
+ from = `(${renderKeyPage({ dialect, page: statement.keyPage, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
4881
+ } else if (statement.fromSelect !== undefined) {
3695
4882
  if (statement.table.alias !== undefined) {
3696
- throw internalError2({ message: "fromSelect uses table.name as the subquery alias \u2014 table.alias must be unset" });
4883
+ throw internalError3({ message: "fromSelect uses table.name as the subquery alias \u2014 table.alias must be unset" });
3697
4884
  }
3698
4885
  const inner = renderSelect({ dialect, statement: statement.fromSelect, collector });
3699
4886
  from = `(${inner.text}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
@@ -3704,7 +4891,7 @@ function renderSelect(params) {
3704
4891
  if (statement.innerJoins !== undefined && statement.innerJoins.length > 0) {
3705
4892
  for (const join of statement.innerJoins) {
3706
4893
  if (join.on.length === 0) {
3707
- throw internalError2({ message: "inner join needs at least one ON pair" });
4894
+ throw internalError3({ message: "inner join needs at least one ON pair" });
3708
4895
  }
3709
4896
  const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
3710
4897
  text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
@@ -3713,9 +4900,17 @@ function renderSelect(params) {
3713
4900
  if (statement.keyTable !== undefined) {
3714
4901
  text += ` ${renderKeyTableJoin({ dialect, join: statement.keyTable, collector })}`;
3715
4902
  }
4903
+ if (statement.leftJoins !== undefined && statement.leftJoins.length > 0) {
4904
+ if (statement.rowLock !== undefined) {
4905
+ throw internalError3({ message: "a locking read cannot carry a left join \u2014 postgres refuses FOR UPDATE on the nullable side of an outer join" });
4906
+ }
4907
+ for (const join of statement.leftJoins) {
4908
+ text += ` ${renderLeftJoin({ dialect, join, collector })}`;
4909
+ }
4910
+ }
3716
4911
  if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
3717
4912
  if (!dialect.capabilities.lateralJoin) {
3718
- throw unsupportedCapability16({
4913
+ throw unsupportedCapability27({
3719
4914
  dialect: dialect.name,
3720
4915
  feature: "LATERAL JOIN relation loading",
3721
4916
  hint: 'use the portable "query" relation strategy on this dialect'
@@ -3756,17 +4951,79 @@ function renderSelect(params) {
3756
4951
  }
3757
4952
  return { text, values: collector.values, returning: "none" };
3758
4953
  }
4954
+ var UNNEST_ROWS_ALIAS = "__vibe_rows";
4955
+ var UNNEST_ORDINALITY = "ord";
4956
+ function unnestElement(params) {
4957
+ const { value, column, jsonStorage } = params;
4958
+ if (value === null || value === undefined)
4959
+ return null;
4960
+ if (isJsonParamCell(value)) {
4961
+ if (jsonStorage === undefined || (value.storage ?? "jsonb") !== jsonStorage) {
4962
+ throw internalError3({ message: `unnest insert column "${column}" got a Json cell its declared type does not take` });
4963
+ }
4964
+ return value.text;
4965
+ }
4966
+ if (jsonStorage === undefined) {
4967
+ switch (typeof value) {
4968
+ case "string":
4969
+ case "number":
4970
+ case "boolean":
4971
+ case "bigint":
4972
+ return value;
4973
+ case "object":
4974
+ if (value instanceof Date || value instanceof Uint8Array)
4975
+ return value;
4976
+ }
4977
+ }
4978
+ throw internalError3({ 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` });
4979
+ }
4980
+ function renderUnnestRows(params) {
4981
+ const { dialect, statement, members, collector } = params;
4982
+ if (dialect.bulkInsertForm !== "unnest") {
4983
+ throw unsupportedCapability27({
4984
+ dialect: dialect.name,
4985
+ feature: "a bulk insert bound as typed arrays (INSERT \u2026 SELECT FROM unnest)",
4986
+ hint: "this dialect binds createMany rows as VALUES"
4987
+ });
4988
+ }
4989
+ if (statement.columns.length === 0)
4990
+ throw internalError3({ message: "an unnest insert needs at least one column" });
4991
+ if (members.length !== statement.columns.length) {
4992
+ throw internalError3({ message: `unnest insert has ${members.length} column types for ${statement.columns.length} columns` });
4993
+ }
4994
+ if (statement.onConflict !== undefined && !("doNothing" in statement.onConflict)) {
4995
+ throw internalError3({ message: "an unnest insert takes no DO UPDATE arm \u2014 upserts bind VALUES" });
4996
+ }
4997
+ const aliases = statement.columns.map((_column, index) => dialect.quoteIdent({ name: `c${index + 1}` }));
4998
+ const arrays = members.map((member, index) => {
4999
+ const cast = postgresBulkInsertArrayCast({ dialect, member });
5000
+ const jsonStorage = member.type !== "Json" ? undefined : cast === "text::json" ? "json" : "jsonb";
5001
+ const column = statement.columns[index];
5002
+ const values = statement.rows.map((row) => unnestElement({ value: row[index], column, jsonStorage }));
5003
+ return `${collector.add(values)}::${cast}[]`;
5004
+ });
5005
+ const ord = dialect.quoteIdent({ name: UNNEST_ORDINALITY });
5006
+ return `SELECT ${aliases.join(", ")} FROM unnest(${arrays.join(", ")}) WITH ORDINALITY AS ${dialect.quoteIdent({ name: UNNEST_ROWS_ALIAS })} (${aliases.join(", ")}, ${ord}) ORDER BY ${ord}`;
5007
+ }
3759
5008
  function renderInsert(params) {
3760
5009
  const { dialect, statement, collector } = params;
3761
- if (statement.columns.length === 0) {
3762
- throw internalError2({ message: "insert requires at least one column" });
3763
- }
3764
5010
  if (statement.rows.length === 0) {
3765
- throw internalError2({ message: "insert requires at least one row" });
5011
+ throw internalError3({ message: "insert requires at least one row" });
5012
+ }
5013
+ const defaultValues = statement.columns.length === 0 && dialect.emptyInsert.form === "defaultValues";
5014
+ if (defaultValues && statement.rows.length !== 1) {
5015
+ throw internalError3({ message: `${dialect.name} inserts an all-default row as DEFAULT VALUES, one row per statement` });
5016
+ }
5017
+ if (defaultValues && statement.onConflict !== undefined && !(dialect.emptyInsert.form === "defaultValues" && dialect.emptyInsert.conflictClause)) {
5018
+ throw unsupportedCapability27({
5019
+ dialect: dialect.name,
5020
+ feature: "a conflict clause on an all-default row (DEFAULT VALUES)",
5021
+ hint: "supply at least one column value, or drop the conflict clause"
5022
+ });
3766
5023
  }
3767
5024
  for (const row of statement.rows) {
3768
5025
  if (row.length !== statement.columns.length) {
3769
- throw internalError2({
5026
+ throw internalError3({
3770
5027
  message: `insert row has ${row.length} values but ${statement.columns.length} columns`
3771
5028
  });
3772
5029
  }
@@ -3775,7 +5032,8 @@ function renderInsert(params) {
3775
5032
  const skipConflicts = onConflict !== undefined && "doNothing" in onConflict;
3776
5033
  const insertKeyword = skipConflicts && dialect.doNothingForm === "insertIgnore" ? "INSERT IGNORE INTO" : "INSERT INTO";
3777
5034
  const columnList = statement.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
3778
- const rowsSql = statement.rows.map((row) => `(${row.map((value) => {
5035
+ const unnestSource = statement.unnest === undefined ? undefined : renderUnnestRows({ dialect, statement, members: statement.unnest, collector });
5036
+ const rowsSql = unnestSource !== undefined ? "" : statement.rows.map((row) => `(${row.map((value) => {
3779
5037
  if (isJsonParamCell(value))
3780
5038
  return `${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
3781
5039
  if (isDbNowMarker(value)) {
@@ -3784,13 +5042,13 @@ function renderInsert(params) {
3784
5042
  if (!isSqlDefaultCell(value))
3785
5043
  return collector.add(value);
3786
5044
  if (!dialect.rowDefaultKeyword) {
3787
- throw internalError2({
5045
+ throw internalError3({
3788
5046
  message: `${dialect.name} cannot render a per-row DEFAULT cell \u2014 rows must be grouped by column set`
3789
5047
  });
3790
5048
  }
3791
5049
  return "DEFAULT";
3792
5050
  }).join(", ")})`).join(", ");
3793
- let text = `${insertKeyword} ${renderTable(dialect, statement.table)} (${columnList}) VALUES ${rowsSql}`;
5051
+ let text = defaultValues ? `${insertKeyword} ${renderTable(dialect, statement.table)} DEFAULT VALUES` : `${insertKeyword} ${renderTable(dialect, statement.table)} (${columnList}) ${unnestSource ?? `VALUES ${rowsSql}`}`;
3794
5052
  if (onConflict !== undefined && !("doNothing" in onConflict) && dialect.excludedRowRef.form === "rowAlias" && Object.values(onConflict.update).some((value) => isExcludedRef(value) || isSqlFragment(value) && sqlUsesIncoming({ fragment: value }))) {
3795
5053
  text += ` AS ${dialect.quoteIdent({ name: dialect.excludedRowRef.alias })}`;
3796
5054
  }
@@ -3800,7 +5058,7 @@ function renderInsert(params) {
3800
5058
  let target = "";
3801
5059
  if (onConflict.constraint !== undefined) {
3802
5060
  if (dialect.name !== "postgres") {
3803
- throw unsupportedCapability16({
5061
+ throw unsupportedCapability27({
3804
5062
  dialect: dialect.name,
3805
5063
  feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
3806
5064
  hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
@@ -3815,7 +5073,7 @@ function renderInsert(params) {
3815
5073
  }
3816
5074
  text += ` ON CONFLICT${target} DO NOTHING`;
3817
5075
  } else if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
3818
- throw unsupportedCapability16({
5076
+ throw unsupportedCapability27({
3819
5077
  dialect: dialect.name,
3820
5078
  feature: "a refined conflict target (constraint / partial-index predicate) on INSERT IGNORE",
3821
5079
  hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
@@ -3824,23 +5082,23 @@ function renderInsert(params) {
3824
5082
  } else {
3825
5083
  const updates = Object.entries(onConflict.update);
3826
5084
  if (updates.length === 0) {
3827
- throw internalError2({ message: "upsert update must set at least one column" });
5085
+ throw internalError3({ message: "upsert update must set at least one column" });
3828
5086
  }
3829
5087
  const selfRef = dialect.quoteIdent({ name: statement.table.name });
3830
5088
  const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true, tableName: statement.table.name });
3831
5089
  if (dialect.upsertForm === "onConflict") {
3832
5090
  if (onConflict.lastInsertIdColumn !== undefined) {
3833
- throw internalError2({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
5091
+ throw internalError3({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
3834
5092
  }
3835
5093
  if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
3836
- throw unsupportedCapability16({
5094
+ throw unsupportedCapability27({
3837
5095
  dialect: dialect.name,
3838
5096
  feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
3839
5097
  hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
3840
5098
  });
3841
5099
  }
3842
5100
  if (onConflict.constraint !== undefined && onConflict.conflictWhere !== undefined) {
3843
- throw internalError2({
5101
+ throw internalError3({
3844
5102
  message: "a named-constraint conflict target already identifies the index \u2014 it cannot carry a partial predicate too"
3845
5103
  });
3846
5104
  }
@@ -3849,14 +5107,14 @@ function renderInsert(params) {
3849
5107
  text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
3850
5108
  } else {
3851
5109
  if (onConflict.constraint !== undefined) {
3852
- throw unsupportedCapability16({
5110
+ throw unsupportedCapability27({
3853
5111
  dialect: dialect.name,
3854
5112
  feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
3855
5113
  hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
3856
5114
  });
3857
5115
  }
3858
5116
  if (onConflict.conflictWhere !== undefined) {
3859
- throw unsupportedCapability16({
5117
+ throw unsupportedCapability27({
3860
5118
  dialect: dialect.name,
3861
5119
  feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
3862
5120
  hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
@@ -3874,7 +5132,7 @@ function renderUpdate(params) {
3874
5132
  const { dialect, statement, collector } = params;
3875
5133
  const assignments = Object.entries(statement.set);
3876
5134
  if (assignments.length === 0) {
3877
- throw internalError2({ message: "update must set at least one column" });
5135
+ throw internalError3({ message: "update must set at least one column" });
3878
5136
  }
3879
5137
  const setSql = renderAssignments({ dialect, entries: assignments, collector });
3880
5138
  let text = `UPDATE ${renderTable(dialect, statement.table)} SET ${setSql}`;
@@ -3897,12 +5155,12 @@ function renderAssignments(params) {
3897
5155
  const read = selfRef === undefined ? column : `${selfRef}.${column}`;
3898
5156
  if (isSqlFragment(value)) {
3899
5157
  if (params.allowExcluded !== true || selfRef === undefined)
3900
- throw internalError2({ message: "SQL assignment fragments require a conflict update" });
5158
+ throw internalError3({ message: "SQL assignment fragments require a conflict update" });
3901
5159
  return `${column} = ${renderSqlFragment({ dialect, fragment: value, collector, conflict: { table: params.tableName } })}`;
3902
5160
  }
3903
5161
  if (isExcludedRef(value)) {
3904
5162
  if (params.allowExcluded !== true) {
3905
- throw internalError2({
5163
+ throw internalError3({
3906
5164
  message: `an incoming-row reference (${value.column}) is only meaningful inside an upsert's update arm`
3907
5165
  });
3908
5166
  }
@@ -3943,19 +5201,19 @@ var CHAIN_ONE_ALIAS = "__vibe_one";
3943
5201
  function renderChain(params) {
3944
5202
  const { dialect, statement, collector } = params;
3945
5203
  if (dialect.statementChain === null) {
3946
- throw unsupportedCapability16({
5204
+ throw unsupportedCapability27({
3947
5205
  dialect: dialect.name,
3948
5206
  feature: "a statement chain (data-modifying WITH steps)",
3949
5207
  hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
3950
5208
  });
3951
5209
  }
3952
5210
  if (statement.steps.length === 0)
3953
- throw internalError2({ message: "a statement chain needs at least one step" });
5211
+ throw internalError3({ message: "a statement chain needs at least one step" });
3954
5212
  const defined = new Set;
3955
5213
  const steps = [];
3956
5214
  for (const step of statement.steps) {
3957
5215
  if (step.name === CHAIN_ROWS_ALIAS || step.name === CHAIN_ONE_ALIAS || defined.has(step.name)) {
3958
- throw internalError2({ message: `chain step name "${step.name}" is reserved or repeated` });
5216
+ throw internalError3({ message: `chain step name "${step.name}" is reserved or repeated` });
3959
5217
  }
3960
5218
  const body = renderChainStep({ dialect, statement: step.statement, collector, earlier: defined });
3961
5219
  steps.push(`${dialect.quoteIdent({ name: step.name })} AS (${body})`);
@@ -3963,11 +5221,11 @@ function renderChain(params) {
3963
5221
  }
3964
5222
  const result = statement.result;
3965
5223
  if (result.rowsFrom.length === 0 || result.columns.length === 0) {
3966
- throw internalError2({ message: "a chain result needs at least one source step and one column" });
5224
+ throw internalError3({ message: "a chain result needs at least one source step and one column" });
3967
5225
  }
3968
5226
  for (const name of result.rowsFrom) {
3969
5227
  if (!defined.has(name))
3970
- throw internalError2({ message: `chain result reads undefined step "${name}"` });
5228
+ throw internalError3({ message: `chain result reads undefined step "${name}"` });
3971
5229
  }
3972
5230
  const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
3973
5231
  const union = result.rowsFrom.map((name) => `SELECT ${columnList} FROM ${dialect.quoteIdent({ name })}`).join(" UNION ALL ");
@@ -3976,7 +5234,7 @@ function renderChain(params) {
3976
5234
  const extra = [];
3977
5235
  for (const alias of [result.verdict?.alias, result.tally?.alias]) {
3978
5236
  if (alias !== undefined && result.columns.includes(alias)) {
3979
- throw internalError2({ message: `chain result alias "${alias}" collides with a result column` });
5237
+ throw internalError3({ message: `chain result alias "${alias}" collides with a result column` });
3980
5238
  }
3981
5239
  }
3982
5240
  const rows = dialect.quoteIdent({ name: CHAIN_ROWS_ALIAS });
@@ -4013,21 +5271,21 @@ function renderSelectCell(params) {
4013
5271
  if (isDbNowMarker(value))
4014
5272
  return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
4015
5273
  if (isSqlDefaultCell(value)) {
4016
- throw internalError2({ message: "DEFAULT is not allowed in an INSERT \u2026 SELECT list \u2014 omit the column instead" });
5274
+ throw internalError3({ message: "DEFAULT is not allowed in an INSERT \u2026 SELECT list \u2014 omit the column instead" });
4017
5275
  }
4018
5276
  return collector.add(value);
4019
5277
  }
4020
5278
  function renderRowSource(params) {
4021
5279
  const { dialect, source, collector } = params;
4022
5280
  if (source.columns.length === 0 || source.rows.length === 0) {
4023
- throw internalError2({ message: "a row source needs at least one column and one row" });
5281
+ throw internalError3({ message: "a row source needs at least one column and one row" });
4024
5282
  }
4025
5283
  const typedBy = renderTable(dialect, source.typedBy);
4026
5284
  const columns = source.columns.map((name) => dialect.quoteIdent({ name }));
4027
5285
  const rows = [`(${columns.map((column) => `(SELECT ${column} FROM ${typedBy} WHERE FALSE)`).join(", ")})`];
4028
5286
  for (const row of source.rows) {
4029
5287
  if (row.length !== source.columns.length) {
4030
- throw internalError2({ message: `row source row has ${row.length} values but ${source.columns.length} columns` });
5288
+ throw internalError3({ message: `row source row has ${row.length} values but ${source.columns.length} columns` });
4031
5289
  }
4032
5290
  rows.push(`(${row.map((value) => renderSelectCell({ dialect, value, collector })).join(", ")})`);
4033
5291
  }
@@ -4039,12 +5297,12 @@ function renderTallyColumn(params) {
4039
5297
  const { dialect, result, defined, collector } = params;
4040
5298
  const tally = result.tally;
4041
5299
  if (tally === undefined)
4042
- throw internalError2({ message: "a chain veto needs a tally column to guard" });
5300
+ throw internalError3({ message: "a chain veto needs a tally column to guard" });
4043
5301
  if (tally.steps.length === 0)
4044
- throw internalError2({ message: "a chain tally needs at least one step" });
5302
+ throw internalError3({ message: "a chain tally needs at least one step" });
4045
5303
  const step = (name) => {
4046
5304
  if (!defined.has(name))
4047
- throw internalError2({ message: `chain tally or veto reads undefined step "${name}"` });
5305
+ throw internalError3({ message: `chain tally or veto reads undefined step "${name}"` });
4048
5306
  return dialect.quoteIdent({ name });
4049
5307
  };
4050
5308
  const count = (name) => `(SELECT count(*) FROM ${step(name)})`;
@@ -4054,12 +5312,12 @@ function renderTallyColumn(params) {
4054
5312
  return `${sum} AS ${dialect.quoteIdent({ name: tally.alias })}`;
4055
5313
  const arms = vetoes.map((veto) => {
4056
5314
  if (!CHAIN_VETO_TOKEN_PATTERN.test(veto.token))
4057
- throw internalError2({ message: `chain veto token "${veto.token}" is not a lower-case word` });
5315
+ throw internalError3({ message: `chain veto token "${veto.token}" is not a lower-case word` });
4058
5316
  if (typeof veto.nonce !== "string" || veto.nonce === "")
4059
- throw internalError2({ message: `chain veto "${veto.token}" needs a non-empty nonce` });
5317
+ throw internalError3({ message: `chain veto "${veto.token}" needs a non-empty nonce` });
4060
5318
  const minRows = veto.minRows ?? 1;
4061
5319
  if (!Number.isSafeInteger(minRows) || minRows < 1)
4062
- throw internalError2({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
5320
+ throw internalError3({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
4063
5321
  const when = minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
4064
5322
  return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
4065
5323
  });
@@ -4068,25 +5326,25 @@ function renderTallyColumn(params) {
4068
5326
  function renderInsertSelect(params) {
4069
5327
  const { dialect, statement, collector } = params;
4070
5328
  if (statement.columns.length === 0)
4071
- throw internalError2({ message: "insert requires at least one column" });
5329
+ throw internalError3({ message: "insert requires at least one column" });
4072
5330
  if (statement.cells.length !== statement.columns.length) {
4073
- throw internalError2({
5331
+ throw internalError3({
4074
5332
  message: `insert row has ${statement.cells.length} values but ${statement.columns.length} columns`
4075
5333
  });
4076
5334
  }
4077
5335
  const from = statement.from ?? [];
4078
5336
  for (const name of from) {
4079
5337
  if (!params.earlier.has(name))
4080
- throw internalError2({ message: `INSERT \u2026 SELECT reads step "${name}" before it is defined` });
5338
+ throw internalError3({ message: `INSERT \u2026 SELECT reads step "${name}" before it is defined` });
4081
5339
  }
4082
5340
  const source = statement.source;
4083
5341
  if (source !== undefined && (params.earlier.has(source.alias) || from.includes(source.alias))) {
4084
- throw internalError2({ message: `row source alias "${source.alias}" collides with a chain step` });
5342
+ throw internalError3({ message: `row source alias "${source.alias}" collides with a chain step` });
4085
5343
  }
4086
5344
  const cells = statement.cells.map((value) => {
4087
5345
  if (isStepColumnRef(value)) {
4088
5346
  if (!from.includes(value.step) && value.step !== source?.alias) {
4089
- throw internalError2({ message: `cell reads step "${value.step}", which the INSERT \u2026 SELECT does not name in FROM` });
5347
+ throw internalError3({ message: `cell reads step "${value.step}", which the INSERT \u2026 SELECT does not name in FROM` });
4090
5348
  }
4091
5349
  return `${dialect.quoteIdent({ name: value.step })}.${dialect.quoteIdent({ name: value.column })}`;
4092
5350
  }
@@ -4105,7 +5363,7 @@ function renderInsertSelect(params) {
4105
5363
  return text + applyReturning({ dialect, requested: statement.returning }).suffix;
4106
5364
  }
4107
5365
  // packages/sql/src/dialects/postgres-type-identity.ts
4108
- import { unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
5366
+ import { unsupportedCapability as unsupportedCapability28 } from "@vibeorm/schema";
4109
5367
  var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
4110
5368
  var MODIFIER_VALUE = `(?:[+-]?\\d+|${IDENTIFIER}|'(?:[^'\\x00]|'')*')`;
4111
5369
  var TYPE_NAME = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*(\\(\\s*${MODIFIER_VALUE}(?:\\s*,\\s*${MODIFIER_VALUE})*\\s*\\))?\\s*((?:\\[\\s*\\]\\s*)*)$`);
@@ -4132,13 +5390,13 @@ function parsePostgresTypeName(params) {
4132
5390
  }
4133
5391
  function renderPostgresTypeName(params) {
4134
5392
  if (params.dialect.name !== "postgres")
4135
- throw unsupportedCapability17({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
5393
+ throw unsupportedCapability28({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
4136
5394
  const { dialect, type } = params;
4137
5395
  return `${type.namespace === undefined ? "" : `${dialect.quoteIdent({ name: type.namespace })}.`}${dialect.quoteIdent({ name: type.name })}${type.modifier}${type.array ? "[]" : ""}`;
4138
5396
  }
4139
5397
  function renderPostgresTypeIdentity(params) {
4140
5398
  if (params.dialect.name !== "postgres")
4141
- throw unsupportedCapability17({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
5399
+ throw unsupportedCapability28({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
4142
5400
  const collector = new ParamCollector(params.dialect);
4143
5401
  const { source } = params;
4144
5402
  let text;
@@ -4151,11 +5409,11 @@ function renderPostgresTypeIdentity(params) {
4151
5409
  return { text, values: collector.values, returning: "none" };
4152
5410
  }
4153
5411
  // packages/sql/src/dialects/postgres-default-identity.ts
4154
- import { unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
5412
+ import { unsupportedCapability as unsupportedCapability29 } from "@vibeorm/schema";
4155
5413
  function renderPostgresDefaultFunctionIdentity(params) {
4156
5414
  const { dialect, table, column } = params;
4157
5415
  if (dialect.name !== "postgres")
4158
- throw unsupportedCapability18({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
5416
+ throw unsupportedCapability29({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
4159
5417
  const collector = new ParamCollector(dialect);
4160
5418
  const namespace = collector.add(table.namespace);
4161
5419
  const name = collector.add(table.name);
@@ -4179,36 +5437,36 @@ WHERE n.nspname = ${namespace} AND c.relname = ${name} AND a.attname = ${field}
4179
5437
  };
4180
5438
  }
4181
5439
  // packages/sql/src/dialects/constraint-owned-index.ts
4182
- import { unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
5440
+ import { unsupportedCapability as unsupportedCapability30 } from "@vibeorm/schema";
4183
5441
  function renderConstraintOwnedIndexDrop(params) {
4184
5442
  if (params.dialect.name !== "postgres")
4185
- throw unsupportedCapability19({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
5443
+ throw unsupportedCapability30({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
4186
5444
  const quote = (name) => params.dialect.quoteIdent({ name });
4187
5445
  return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
4188
5446
  }
4189
5447
  // packages/sql/src/dialects/mysql-check-probe.ts
4190
- import { unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
5448
+ import { unsupportedCapability as unsupportedCapability31 } from "@vibeorm/schema";
4191
5449
  function renderMysqlCheckProbe(params) {
4192
5450
  if (params.dialect.name !== "mysql")
4193
- throw unsupportedCapability20({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
5451
+ throw unsupportedCapability31({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
4194
5452
  const q = (name) => params.dialect.quoteIdent({ name });
4195
5453
  const probe = q(params.probe);
4196
5454
  switch (params.operation) {
4197
5455
  case "clone":
4198
5456
  if (params.table === undefined)
4199
- throw unsupportedCapability20({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
5457
+ throw unsupportedCapability31({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
4200
5458
  return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
4201
5459
  case "add": {
4202
5460
  if (params.expression === undefined || params.definition === undefined)
4203
- throw unsupportedCapability20({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
4204
- const parts = tableDefinitionParts({ sql: params.definition });
5461
+ throw unsupportedCapability31({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
5462
+ const parts = tableDefinitionParts({ sql: params.definition, backslashEscapes: true });
4205
5463
  return { text: `CREATE TEMPORARY TABLE ${probe} (
4206
5464
  ${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
4207
5465
  `)}
4208
5466
  )${parts.suffix}`, values: [], returning: "none" };
4209
5467
  }
4210
5468
  case "read":
4211
- return { text: `SHOW CREATE TABLE ${probe}`, values: [], returning: "none" };
5469
+ return { text: `SHOW CREATE TABLE ${params.table === undefined ? probe : `${q(params.table.namespace)}.${q(params.table.name)}`}`, values: [], returning: "none" };
4212
5470
  case "drop":
4213
5471
  return { text: `DROP TEMPORARY TABLE ${probe}`, values: [], returning: "none" };
4214
5472
  }
@@ -4246,31 +5504,31 @@ function sqliteTableConstraints(params) {
4246
5504
  return result;
4247
5505
  }
4248
5506
  // packages/sql/src/dialects/migration-visibility.ts
4249
- import { unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
5507
+ import { unsupportedCapability as unsupportedCapability32 } from "@vibeorm/schema";
4250
5508
  function renderMigrationVisibility(params) {
4251
5509
  if (params.dialect.name !== "mysql")
4252
- throw unsupportedCapability21({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
5510
+ throw unsupportedCapability32({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
4253
5511
  const collector = new ParamCollector(params.dialect);
4254
5512
  const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
4255
5513
  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`, values: collector.values, returning: "none" };
4256
5514
  }
4257
5515
  // packages/sql/src/dialects/migration-inventory.ts
4258
- import { unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
5516
+ import { unsupportedCapability as unsupportedCapability33 } from "@vibeorm/schema";
4259
5517
  function renderSqliteColumnInventory(params) {
4260
5518
  if (params.dialect.name !== "sqlite")
4261
- throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
5519
+ throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
4262
5520
  const collector = new ParamCollector(params.dialect);
4263
5521
  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" };
4264
5522
  }
4265
5523
  function renderSqliteForeignKeyInventory(params) {
4266
5524
  if (params.dialect.name !== "sqlite")
4267
- throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
5525
+ throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
4268
5526
  const collector = new ParamCollector(params.dialect);
4269
5527
  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" };
4270
5528
  }
4271
5529
  function renderSqliteRebuildObjects(params) {
4272
5530
  if (params.dialect.name !== "sqlite")
4273
- throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
5531
+ throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
4274
5532
  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" };
4275
5533
  }
4276
5534
  function renderMigrationInventory(params) {
@@ -4309,7 +5567,7 @@ ORDER BY kind, parent, name`, values: collector.values, returning: "none" };
4309
5567
  return { text: `SELECT type AS kind, name, tbl_name AS parent, sql AS definition FROM ${schema}.sqlite_schema WHERE name NOT LIKE 'sqlite\\_%' ESCAPE '\\' AND name <> '_vibe_migrations' ORDER BY type, tbl_name, name`, values: [], returning: "none" };
4310
5568
  }
4311
5569
  // packages/sql/src/dialects/migration-objects.ts
4312
- import { internalError as internalError3, unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
5570
+ import { internalError as internalError4, unsupportedCapability as unsupportedCapability34 } from "@vibeorm/schema";
4313
5571
  function renderMigrationObjectQuery(params) {
4314
5572
  return objectQuery({ ...params, collector: new ParamCollector(params.dialect) });
4315
5573
  }
@@ -4317,7 +5575,7 @@ function objectQuery(params) {
4317
5575
  const { dialect, identity: object, collector } = params;
4318
5576
  if (object.kind === "namespace") {
4319
5577
  if (dialect.name !== "postgres")
4320
- throw unsupportedCapability23({ dialect: dialect.name, feature: "fixedNamespaces" });
5578
+ throw unsupportedCapability34({ dialect: dialect.name, feature: "fixedNamespaces" });
4321
5579
  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" };
4322
5580
  }
4323
5581
  const physical = "table" in object ? object.table : object.name;
@@ -4381,7 +5639,7 @@ function objectQuery(params) {
4381
5639
  break;
4382
5640
  case "constraint":
4383
5641
  if (object.constraintType === "exclusion")
4384
- throw unsupportedCapability23({ dialect: "mysql", feature: "exclusionConstraints" });
5642
+ throw unsupportedCapability34({ dialect: "mysql", feature: "exclusionConstraints" });
4385
5643
  text = `SELECT tc.CONSTRAINT_TYPE AS type, tc.ENFORCED AS enforced, cc.CHECK_CLAUSE AS definition, rc.UPDATE_RULE AS onUpdate, rc.DELETE_RULE AS onDelete, rc.MATCH_OPTION AS matchOption FROM information_schema.TABLE_CONSTRAINTS tc LEFT JOIN information_schema.CHECK_CONSTRAINTS cc ON cc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA AND cc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME LEFT JOIN information_schema.REFERENTIAL_CONSTRAINTS rc ON rc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA AND rc.TABLE_NAME = tc.TABLE_NAME AND rc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME WHERE tc.TABLE_SCHEMA = ${namespace} AND tc.TABLE_NAME = ${name} AND tc.CONSTRAINT_NAME = ${collector.add(object.name)}`;
4386
5644
  break;
4387
5645
  case "index":
@@ -4391,22 +5649,22 @@ function objectQuery(params) {
4391
5649
  text = `SELECT TABLE_TYPE AS type, ENGINE AS engine, TABLE_COLLATION AS collation FROM information_schema.TABLES WHERE TABLE_SCHEMA = ${namespace} AND TABLE_NAME = ${name}`;
4392
5650
  break;
4393
5651
  default:
4394
- throw unsupportedCapability23({ dialect: "mysql", feature: `inspect:${object.kind}` });
5652
+ throw unsupportedCapability34({ dialect: "mysql", feature: `inspect:${object.kind}` });
4395
5653
  }
4396
5654
  } else {
4397
5655
  if (physical.namespace !== "main" && physical.namespace !== "temp")
4398
- throw unsupportedCapability23({ dialect: "sqlite", feature: "attachedSchemaInspection" });
5656
+ throw unsupportedCapability34({ dialect: "sqlite", feature: "attachedSchemaInspection" });
4399
5657
  const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
4400
5658
  const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
4401
5659
  if (["function", "sequence", "postgresExtension", "enum"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
4402
- throw unsupportedCapability23({ dialect: "sqlite", feature: `inspect:${object.kind}` });
5660
+ throw unsupportedCapability34({ dialect: "sqlite", feature: `inspect:${object.kind}` });
4403
5661
  const sqliteParams = new ParamCollector(dialect);
4404
5662
  if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
4405
5663
  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)}`;
4406
5664
  return { text, values: sqliteParams.values, returning: "none" };
4407
5665
  }
4408
5666
  const objectName = object.kind === "trigger" || object.kind === "index" ? object.name : physical.name;
4409
- text = `SELECT sql AS definition, type AS type FROM ${catalog} WHERE name = ${sqliteParams.add(objectName)} AND type = ${sqliteParams.add(kind)}`;
5667
+ text = `SELECT sql AS definition, type AS type FROM ${catalog} WHERE name = ${sqliteParams.add(objectName)} AND type = ${sqliteParams.add(kind)}${object.kind === "trigger" ? ` AND tbl_name = ${sqliteParams.add(physical.name)}` : ""}`;
4410
5668
  return { text, values: sqliteParams.values, returning: "none" };
4411
5669
  }
4412
5670
  return { text, values: collector.values, returning: "none" };
@@ -4429,7 +5687,7 @@ function renderMigrationDependencies(params) {
4429
5687
  }
4430
5688
  function dependencyQuery(params) {
4431
5689
  if (params.dialect.name !== "postgres")
4432
- throw unsupportedCapability23({ dialect: params.dialect.name, feature: "catalogDependencies" });
5690
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "catalogDependencies" });
4433
5691
  const object = params.identity;
4434
5692
  const collector = params.collector;
4435
5693
  if (object.kind === "namespace") {
@@ -4486,7 +5744,7 @@ function inspectionReferences(params) {
4486
5744
  function renderMigrationObjectBatch(params) {
4487
5745
  const { dialect } = params;
4488
5746
  if (dialect.name !== "postgres")
4489
- throw unsupportedCapability23({ dialect: dialect.name, feature: "batchedMigrationInspection" });
5747
+ throw unsupportedCapability34({ dialect: dialect.name, feature: "batchedMigrationInspection" });
4490
5748
  const groups = new Map;
4491
5749
  for (const [position, identity] of params.identities.entries()) {
4492
5750
  const kind = identity.kind === "constraint" ? `${identity.kind}:${identity.constraintType}` : identity.kind === "generatedColumn" ? "columnDefault" : identity.kind;
@@ -4499,7 +5757,7 @@ function renderMigrationObjectBatch(params) {
4499
5757
  const facts = objectQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
4500
5758
  const dependencies = dependencyQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
4501
5759
  if (JSON.stringify(dependencies.values) !== JSON.stringify(facts.values))
4502
- throw internalError3({ message: "Batched catalog queries bind their identity in different orders", meta: { kind: identity.kind } });
5760
+ throw internalError4({ message: "Batched catalog queries bind their identity in different orders", meta: { kind: identity.kind } });
4503
5761
  const columns = facts.values.map((value, index) => `${dialect.quoteIdent({ name: `p${index + 1}` })} ${Array.isArray(value) ? "text[]" : "text"}`);
4504
5762
  const requests = group.map(({ identity, position }) => ({ position, ...Object.fromEntries(renderMigrationObjectQuery({ dialect, identity }).values.map((value, index) => [`p${index + 1}`, value])) }));
4505
5763
  const collector = new ParamCollector(dialect);
@@ -4519,16 +5777,16 @@ function renderMigrationNamespace(params) {
4519
5777
  }
4520
5778
  function renderCheckProbe(params) {
4521
5779
  if (params.dialect.name !== "postgres")
4522
- throw unsupportedCapability23({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
5780
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
4523
5781
  const q = (name) => params.dialect.quoteIdent({ name });
4524
5782
  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" };
4525
5783
  }
4526
5784
  // packages/sql/src/dialects/postgres-migration.ts
4527
- import { VibeError as VibeError15, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
5785
+ import { VibeError as VibeError23, unsupportedCapability as unsupportedCapability35 } from "@vibeorm/schema";
4528
5786
  var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
4529
5787
  function requirePostgres2(params) {
4530
5788
  if (params.dialect.name !== "postgres") {
4531
- throw unsupportedCapability24({ dialect: params.dialect.name, feature: params.feature });
5789
+ throw unsupportedCapability35({ dialect: params.dialect.name, feature: params.feature });
4532
5790
  }
4533
5791
  }
4534
5792
  function renderMigrationSessionRead(params) {
@@ -4543,7 +5801,7 @@ function renderMigrationSessionSet(params) {
4543
5801
  requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
4544
5802
  const { milliseconds, setting } = params;
4545
5803
  if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
4546
- throw new VibeError15({
5804
+ throw new VibeError23({
4547
5805
  code: "VIBE_MIGRATION",
4548
5806
  message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
4549
5807
  meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
@@ -4661,6 +5919,7 @@ export {
4661
5919
  SQLITE_SESSION_SQL,
4662
5920
  SQL_DEFAULT_CELL,
4663
5921
  SQL_DIALECTS,
5922
+ assertIndexExpressionVersion,
4664
5923
  assertSqlParamBuilding,
4665
5924
  chainVetoToken,
4666
5925
  cloneTimestampDate,
@@ -4688,6 +5947,9 @@ export {
4688
5947
  escapeGlobPattern,
4689
5948
  escapeLikePattern,
4690
5949
  excludedRef,
5950
+ extractIndexKeys,
5951
+ extractMysqlGeneratedColumn,
5952
+ extractSqliteGeneratedColumn,
4691
5953
  float32Codec,
4692
5954
  getSqlDialect,
4693
5955
  isDbNowMarker,
@@ -4709,6 +5971,8 @@ export {
4709
5971
  isStepColumnRef,
4710
5972
  jsonListParameter,
4711
5973
  jsonParamCell,
5974
+ keyListBindsUntypedRows,
5975
+ localDateCodec,
4712
5976
  mapConflictColumns,
4713
5977
  mysqlDialect,
4714
5978
  nativeEnumArrayParameter,
@@ -4718,7 +5982,9 @@ export {
4718
5982
  parsePgArrayLiteralNested,
4719
5983
  parsePostgresTypeName,
4720
5984
  postgresArrayElementFromText,
5985
+ postgresBulkInsertArrayCastOf,
4721
5986
  postgresDialect,
5987
+ postgresKeyListElementTypeOf,
4722
5988
  projectionOutputName,
4723
5989
  recordSqlBinding,
4724
5990
  recordSqlBindingCheck,
@@ -4737,8 +6003,12 @@ export {
4737
6003
  renderExternalDependents,
4738
6004
  renderExternalOwnershipCatalog,
4739
6005
  renderFragment,
6006
+ renderGeneratedColumnClause,
4740
6007
  renderIndexColumns,
6008
+ renderIndexExpressionProbe,
6009
+ renderIndexKeys,
4741
6010
  renderIndexStateQuery,
6011
+ renderLocalDateCheck,
4742
6012
  renderMigrationDependencies,
4743
6013
  renderMigrationHistoryQuery,
4744
6014
  renderMigrationInventory,
@@ -4753,10 +6023,15 @@ export {
4753
6023
  renderModuleDependencyQuery,
4754
6024
  renderModuleJournal,
4755
6025
  renderMysqlCheckProbe,
6026
+ renderMysqlGeneratedColumnProbe,
4756
6027
  renderMysqlObjectMembers,
4757
6028
  renderPostgresDefaultFunctionIdentity,
6029
+ renderPostgresDropGeneratedExpression,
4758
6030
  renderPostgresExplain,
6031
+ renderPostgresGeneratedColumnProbe,
4759
6032
  renderPostgresIndexKeys,
6033
+ renderPostgresJitRestore,
6034
+ renderPostgresJitSuspend,
4760
6035
  renderPostgresPhysicalCatalog,
4761
6036
  renderPostgresTypeIdentity,
4762
6037
  renderPostgresTypeName,
@@ -4779,6 +6054,8 @@ export {
4779
6054
  renderRlsReadinessQuery,
4780
6055
  renderRlsRole,
4781
6056
  renderRowLockClause,
6057
+ renderRowRule,
6058
+ renderSchemaExpression,
4782
6059
  renderShadowOperation,
4783
6060
  renderSqlFragment,
4784
6061
  renderSqliteColumnInventory,
@@ -4786,6 +6063,7 @@ export {
4786
6063
  renderSqliteFtsUpdateTrigger,
4787
6064
  renderSqliteRebuildObjects,
4788
6065
  renderStatement,
6066
+ resolveIndexKeys,
4789
6067
  resultScalarContract,
4790
6068
  returningColumnName,
4791
6069
  sameResultDecoder,
@@ -4802,6 +6080,7 @@ export {
4802
6080
  sqliteTableConstraints,
4803
6081
  startsWithPattern,
4804
6082
  stepColumnRef,
6083
+ strictMatchCollationPrefilter,
4805
6084
  strictMatchPattern,
4806
6085
  tableDefinitionParts,
4807
6086
  timeOfDayCodec,
@@ -4810,4 +6089,4 @@ export {
4810
6089
  toPgArrayLiteral
4811
6090
  };
4812
6091
 
4813
- //# debugId=02AD9A2144138F0F64756E2164756E21
6092
+ //# debugId=75BF4FE272330AE564756E2164756E21