@vibeorm/sql 4.2.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,159 +1219,1078 @@ 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;
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
+ });
1343
+ }
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 }
1350
+ });
1351
+ }
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
+ });
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: "*"
1400
+ };
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" });
1416
+ }
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 };
1419
+ }
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 };
1428
+ }
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 };
1440
+ }
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
+ });
1480
+ }
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);
1516
+ }
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
+ });
1528
+ }
1529
+ return value;
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`);
1541
+ }
1542
+ }
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
+ });
1573
+ }
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;
1584
+ }
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 });
1596
+ }
1597
+ function describe(params) {
1598
+ const { value } = params;
1599
+ return typeof value === "string" ? `the string ${JSON.stringify(value)}` : `the ${typeof value} ${String(value)}`;
1600
+ }
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++;
1614
+ continue;
1615
+ }
1616
+ if (char === "-" && sql[index + 1] === "-" || char === "#") {
1617
+ const end = sql.indexOf(`
1618
+ `, index + 1);
1619
+ index = end < 0 ? sql.length : end + 1;
1620
+ continue;
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;
1627
+ continue;
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 });
1655
+ continue;
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++;
1672
+ }
1673
+ if (depth !== 0)
1674
+ throw unavailable();
1675
+ return tokens;
1676
+ }
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) };
1702
+ }
1703
+ function unavailable() {
1704
+ return new VibeError8({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
1705
+ }
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 });
1717
+ }
1718
+ return columns;
1719
+ }
1720
+ function sqliteColumnClauses({ sql }) {
1721
+ return tableColumnClauses({ sql });
1722
+ }
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 });
1761
+ }
1762
+ }
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;
1777
+ };
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;
1822
+ }
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;
1828
+ }
1829
+ function extractSqliteIndexKeys({ definition, index }) {
1830
+ return extractStandaloneIndexKeys({ definition, index });
1831
+ }
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" }];
1857
+ }
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;
1907
+ }
1908
+ function extractSqliteGeneratedColumn({ definition, column }) {
1909
+ return extractGenerationFromDefinition({ definition, column });
1910
+ }
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" };
1965
+ }
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 } });
1968
+ }
1969
+ function extractMysqlGeneratedColumn({ definition, column, backslashEscapes }) {
1970
+ return extractGenerationFromDefinition({ definition, column, backslashEscapes, mysql: true });
1971
+ }
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" }];
2039
+ }
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
+ });
2103
+ }
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)}` });
2137
+ }
2138
+ function invalid3({ site, reason }) {
2139
+ return new VibeError11({ code: "VIBE_SCHEMA_INVALID", message: `Invalid index keys at ${site}: ${reason}`, meta: { rule: reason, site } });
2140
+ }
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
+ };
2166
+ }
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" };
2185
+ }
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
+ }
2274
+ }
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;
1305
2294
  case "insert":
1306
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(", ")})`;
1307
2296
  break;
@@ -1320,10 +2309,10 @@ import {
1320
2309
  } from "@vibeorm/schema";
1321
2310
 
1322
2311
  // packages/sql/src/identifiers.ts
1323
- import { VibeError as VibeError7 } from "@vibeorm/schema";
2312
+ import { VibeError as VibeError12 } from "@vibeorm/schema";
1324
2313
  function assertNoNul(params) {
1325
2314
  if (params.name.includes("\x00")) {
1326
- throw new VibeError7({
2315
+ throw new VibeError12({
1327
2316
  code: "VIBE_VALIDATION",
1328
2317
  message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
1329
2318
  meta: { name: params.name.replaceAll("\x00", "\\0") }
@@ -1338,6 +2327,13 @@ var sqliteDialect = {
1338
2327
  capabilities: SQLITE_CAPABILITIES,
1339
2328
  inArrayStrategy: "expand",
1340
2329
  tupleInStrategy: "rowValue",
2330
+ keyListStrategy: "values",
2331
+ keysetSeekStrategy: "rowValue",
2332
+ keyPageStrategy: "inLimit",
2333
+ relationFilterStrategy: "inSelect",
2334
+ deferredJoinPage: null,
2335
+ nativeNullsOrder: "low",
2336
+ nullsUnionPage: false,
1341
2337
  timeOfDayInStrategy: "in",
1342
2338
  upsertForm: "onConflict",
1343
2339
  conflictKeyEquality: {
@@ -1369,6 +2365,7 @@ var sqliteDialect = {
1369
2365
  rowLockSyntax: null,
1370
2366
  advisoryLockFunctions: null,
1371
2367
  rowDefaultKeyword: false,
2368
+ bulkInsertForm: "values",
1372
2369
  emptyInsert: { form: "defaultValues", conflictClause: false },
1373
2370
  statementChain: null,
1374
2371
  paramBudget: 900,
@@ -1392,84 +2389,6 @@ var sqliteDialect = {
1392
2389
  }
1393
2390
  };
1394
2391
 
1395
- // packages/sql/src/dialects/sqlite-table-definition.ts
1396
- import { VibeError as VibeError8 } from "@vibeorm/schema";
1397
- function tableDefinitionParts(params) {
1398
- const sql = params.sql;
1399
- if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql))
1400
- throw unavailable();
1401
- const parts = [];
1402
- let start = -1;
1403
- let depth = 0;
1404
- let quote;
1405
- for (let index = 0;index < sql.length; index++) {
1406
- const char = sql[index];
1407
- if (quote !== undefined) {
1408
- if (char === quote) {
1409
- if (quote !== "]" && sql[index + 1] === quote)
1410
- index++;
1411
- else
1412
- quote = undefined;
1413
- }
1414
- continue;
1415
- }
1416
- if (char === "'" || char === '"' || char === "`") {
1417
- quote = char;
1418
- continue;
1419
- }
1420
- if (char === "[") {
1421
- quote = "]";
1422
- continue;
1423
- }
1424
- if (char === "-" && sql[index + 1] === "-") {
1425
- const end = sql.indexOf(`
1426
- `, index + 2);
1427
- if (end < 0)
1428
- break;
1429
- index = end;
1430
- continue;
1431
- }
1432
- if (char === "/" && sql[index + 1] === "*") {
1433
- const end = sql.indexOf("*/", index + 2);
1434
- if (end < 0)
1435
- break;
1436
- index = end + 1;
1437
- continue;
1438
- }
1439
- if (char === "(") {
1440
- if (depth === 0)
1441
- start = index + 1;
1442
- depth++;
1443
- } else if (char === ")") {
1444
- depth--;
1445
- if (depth === 0 && start >= 0) {
1446
- parts.push(sql.slice(start, index).trim());
1447
- return { clauses: parts, suffix: sql.slice(index + 1) };
1448
- }
1449
- } else if (char === "," && depth === 1) {
1450
- parts.push(sql.slice(start, index).trim());
1451
- start = index + 1;
1452
- }
1453
- }
1454
- throw unavailable();
1455
- }
1456
- function unavailable() {
1457
- return new VibeError8({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
1458
- }
1459
- function sqliteColumnClauses(params) {
1460
- const columns = [];
1461
- for (const part of tableDefinitionParts(params).clauses) {
1462
- const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
1463
- if (token === null)
1464
- throw unavailable();
1465
- if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
1466
- continue;
1467
- const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
1468
- columns.push({ name, sql: part });
1469
- }
1470
- return columns;
1471
- }
1472
-
1473
2392
  // packages/sql/src/dialects/sqlite-fts.ts
1474
2393
  function updateStatements(params) {
1475
2394
  const quote = (name) => sqliteDialect.quoteIdent({ name });
@@ -1531,11 +2450,11 @@ var SQLITE_SESSION_SQL = Object.freeze({
1531
2450
  foreignKeysOn: "PRAGMA foreign_keys = ON"
1532
2451
  });
1533
2452
  // packages/sql/src/composable.ts
1534
- import { VibeError as VibeError9 } from "@vibeorm/schema";
2453
+ import { VibeError as VibeError13 } from "@vibeorm/schema";
1535
2454
  var fragments = new WeakMap;
1536
2455
  registerSqlHandleCheck({ check: (value) => fragments.has(value) });
1537
- function invalid3(message) {
1538
- throw new VibeError9({ code: "VIBE_VALIDATION", message });
2456
+ function invalid4(message) {
2457
+ throw new VibeError13({ code: "VIBE_VALIDATION", message });
1539
2458
  }
1540
2459
  function construct(parts) {
1541
2460
  const snapshot = Object.freeze([...parts]);
@@ -1549,19 +2468,19 @@ function isSqlFragment(value) {
1549
2468
  return typeof value === "object" && value !== null && fragments.has(value);
1550
2469
  }
1551
2470
  function partsOf(value) {
1552
- return fragments.get(value) ?? invalid3("Expected a constructed SQL fragment");
2471
+ return fragments.get(value) ?? invalid4("Expected a constructed SQL fragment");
1553
2472
  }
1554
2473
  function containsGreatest(fragment) {
1555
2474
  return partsOf(fragment).some((part) => typeof part !== "string" && ("greatest" in part));
1556
2475
  }
1557
2476
  function identifier(parts) {
1558
2477
  if (!Array.isArray(parts) || parts.length === 0 || parts.some((part) => typeof part !== "string" || part.length === 0 || part.includes("\x00"))) {
1559
- invalid3("SQL identifiers require non-empty, NUL-free name components");
2478
+ invalid4("SQL identifiers require non-empty, NUL-free name components");
1560
2479
  }
1561
2480
  }
1562
2481
  function template(strings, ...values) {
1563
2482
  if (!Array.isArray(strings) || !Array.isArray(strings.raw) || strings.length !== values.length + 1 || strings.some((text) => typeof text !== "string")) {
1564
- invalid3("Use sql as a template tag");
2483
+ invalid4("Use sql as a template tag");
1565
2484
  }
1566
2485
  const parts = [];
1567
2486
  for (let index = 0;index < strings.length; index++) {
@@ -1579,7 +2498,7 @@ function template(strings, ...values) {
1579
2498
  var sql = Object.freeze(Object.assign(template, {
1580
2499
  text: ({ text }) => {
1581
2500
  if (typeof text !== "string" || text.includes("\x00"))
1582
- invalid3("SQL text must be a NUL-free string");
2501
+ invalid4("SQL text must be a NUL-free string");
1583
2502
  return construct([text]);
1584
2503
  },
1585
2504
  ident: ({ parts }) => {
@@ -1589,7 +2508,7 @@ var sql = Object.freeze(Object.assign(template, {
1589
2508
  value: ({ value, decoder }) => construct([Object.freeze({ value: snapshotSqlValue(value), ...decoder === undefined ? {} : { decoder } })]),
1590
2509
  join: ({ items, separator }) => {
1591
2510
  if (!Array.isArray(items))
1592
- invalid3("sql.join requires an array");
2511
+ invalid4("sql.join requires an array");
1593
2512
  const between = separator === undefined ? [", "] : partsOf(separator);
1594
2513
  const parts = [];
1595
2514
  items.forEach((value, index) => {
@@ -1609,7 +2528,7 @@ var sql = Object.freeze(Object.assign(template, {
1609
2528
  },
1610
2529
  greatest: ({ values }) => {
1611
2530
  if (!Array.isArray(values) || values.length !== 2 || values.some((value) => !isSqlFragment(value)))
1612
- invalid3("sql.greatest requires two SQL fragments");
2531
+ invalid4("sql.greatest requires two SQL fragments");
1613
2532
  return construct([Object.freeze({ greatest: Object.freeze([...values]) })]);
1614
2533
  }
1615
2534
  }));
@@ -1618,7 +2537,7 @@ function renderSqlFragment(params) {
1618
2537
  const render = (fragment) => renderSqlFragment({ ...params, fragment });
1619
2538
  const encode = (part) => {
1620
2539
  if (part.value === undefined)
1621
- invalid3("SQL parameters cannot be undefined; use null explicitly");
2540
+ invalid4("SQL parameters cannot be undefined; use null explicitly");
1622
2541
  return snapshotSqlValue(part.decoder === undefined ? part.value : part.decoder.encode({ value: snapshotSqlValue(part.value), dialect: dialect.name }));
1623
2542
  };
1624
2543
  const recording = isSqlBindingCollector({ collector });
@@ -1633,12 +2552,12 @@ function renderSqlFragment(params) {
1633
2552
  const only = parts.length === 1 ? parts[0] : undefined;
1634
2553
  if (recording && only !== undefined && typeof only !== "string" && "value" in only) {
1635
2554
  if (only.decoder !== undefined && resultScalarContract({ decoder: only.decoder }) === undefined)
1636
- throw new VibeError9({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL cannot compile an opaque encoder whose output controls a greatest NULL frame" });
2555
+ throw new VibeError13({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL cannot compile an opaque encoder whose output controls a greatest NULL frame" });
1637
2556
  if (only.value === null) {
1638
2557
  if (only.decoder !== undefined)
1639
2558
  recordSqlBindingCheck({ collector, check: () => {
1640
2559
  if (encode({ ...only, value: snapshotSqlValue(only.value) }) !== null)
1641
- throw new VibeError9({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL NULL frame encoding changed its declared structure" });
2560
+ throw new VibeError13({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL NULL frame encoding changed its declared structure" });
1642
2561
  } });
1643
2562
  return "NULL";
1644
2563
  }
@@ -1660,10 +2579,10 @@ function renderSqlFragment(params) {
1660
2579
  return part.ident.map((name) => dialect.quoteIdent({ name })).join(".");
1661
2580
  if ("row" in part) {
1662
2581
  if (conflict === undefined)
1663
- invalid3("Existing/incoming row references require a conflict update");
2582
+ invalid4("Existing/incoming row references require a conflict update");
1664
2583
  const name = conflict.columns === undefined ? part.column : conflict.columns[part.column];
1665
2584
  if (name === undefined)
1666
- invalid3(`Unknown conflict-expression field "${part.column}"`);
2585
+ invalid4(`Unknown conflict-expression field "${part.column}"`);
1667
2586
  const ref = dialect.excludedRowRef;
1668
2587
  const qualifier = part.row === "existing" ? dialect.quoteIdent({ name: conflict.table }) : ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
1669
2588
  return `${qualifier}.${dialect.quoteIdent({ name })}`;
@@ -1685,7 +2604,7 @@ function mapConflictColumns(params) {
1685
2604
  if ("row" in part) {
1686
2605
  const column = Object.hasOwn(params.columns, part.column) ? params.columns[part.column] : undefined;
1687
2606
  if (column === undefined)
1688
- invalid3(`Unknown conflict-expression field "${part.column}"`);
2607
+ invalid4(`Unknown conflict-expression field "${part.column}"`);
1689
2608
  return Object.freeze({ ...part, column });
1690
2609
  }
1691
2610
  if ("greatest" in part)
@@ -1702,25 +2621,25 @@ function sqlUsesIncoming(params) {
1702
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 }))));
1703
2622
  }
1704
2623
  // packages/sql/src/query-plan.ts
1705
- import { VibeError as VibeError10 } from "@vibeorm/schema";
2624
+ import { VibeError as VibeError14 } from "@vibeorm/schema";
1706
2625
  var queries = new WeakMap;
1707
2626
  var commands = new WeakMap;
1708
2627
  var reusableQueries = new WeakMap;
1709
2628
  var reusableCommands = new WeakMap;
1710
2629
  registerSqlHandleCheck({ check: (value) => queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) });
1711
- function invalid4(message) {
1712
- throw new VibeError10({ code: "VIBE_VALIDATION", message });
2630
+ function invalid5(message) {
2631
+ throw new VibeError14({ code: "VIBE_VALIDATION", message });
1713
2632
  }
1714
2633
  function createSqlQuery(params) {
1715
2634
  if (typeof params.render !== "function" || typeof params.decoder?.decode !== "function")
1716
- invalid4("SQL queries require a renderer and result decoder");
2635
+ invalid5("SQL queries require a renderer and result decoder");
1717
2636
  const query = Object.freeze(Object.create(null));
1718
2637
  queries.set(query, Object.freeze({ render: params.render, decoder: params.decoder }));
1719
2638
  return query;
1720
2639
  }
1721
2640
  function createSqlCommand(params) {
1722
2641
  if (typeof params.render !== "function")
1723
- invalid4("SQL commands require a renderer");
2642
+ invalid5("SQL commands require a renderer");
1724
2643
  const command = Object.freeze(Object.create(null));
1725
2644
  commands.set(command, params.render);
1726
2645
  return command;
@@ -1738,14 +2657,14 @@ function isReusableSqlCommand(value) {
1738
2657
  return typeof value === "object" && value !== null && reusableCommands.has(value);
1739
2658
  }
1740
2659
  function createReusableSqlQuery(params) {
1741
- const state = queries.get(params.query) ?? invalid4("Expected an authentic SQL row query");
2660
+ const state = queries.get(params.query) ?? invalid5("Expected an authentic SQL row query");
1742
2661
  sqlInputScopeKeys({ scope: params.scope });
1743
2662
  const query = Object.freeze(Object.create(null));
1744
2663
  reusableQueries.set(query, { ...state, scope: params.scope, cache: new WeakMap });
1745
2664
  return query;
1746
2665
  }
1747
2666
  function createReusableSqlCommand(params) {
1748
- const render = commands.get(params.query) ?? invalid4("Expected an authentic SQL command");
2667
+ const render = commands.get(params.query) ?? invalid5("Expected an authentic SQL command");
1749
2668
  sqlInputScopeKeys({ scope: params.scope });
1750
2669
  const query = Object.freeze(Object.create(null));
1751
2670
  reusableCommands.set(query, { render, scope: params.scope, cache: new WeakMap });
@@ -1759,7 +2678,7 @@ function compiledTemplate(params) {
1759
2678
  startSqlBindingCollection({ collector, dialect: params.dialect, scope: params.state.scope });
1760
2679
  const text = params.state.render({ dialect: params.dialect, collector });
1761
2680
  if (collector.count() > params.dialect.paramBudget)
1762
- invalid4("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
2681
+ invalid5("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
1763
2682
  const program = finishSqlBindingCollection({ collector });
1764
2683
  const compiled = Object.freeze({ text, program });
1765
2684
  params.state.cache.set(params.dialect, compiled);
@@ -1769,385 +2688,55 @@ function executionHandle(params) {
1769
2688
  const { value } = params;
1770
2689
  return queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) || isSqlFragment(value) || isSqlInput(value) || sqlInputScopeHandle(params);
1771
2690
  }
1772
- function bindTemplate(params) {
1773
- const { scope } = params.state;
1774
- return bindSqlBindingProgram({ program: params.compiled.program, params: params.inputs, isHandle: (value) => executionHandle({ scope, value }) });
1775
- }
1776
- function compileReusableSqlQuery(params) {
1777
- const state = reusableQueries.get(params.query) ?? invalid4("Expected an authentic reusable SQL row query");
1778
- const compiled = compiledTemplate({ dialect: params.dialect, state });
1779
- return { text: compiled.text, values: bindTemplate({ compiled, state, inputs: params.params }), decoder: state.decoder };
1780
- }
1781
- function compileReusableSqlCommand(params) {
1782
- const state = reusableCommands.get(params.query) ?? invalid4("Expected an authentic reusable SQL command");
1783
- const compiled = compiledTemplate({ dialect: params.dialect, state });
1784
- return { text: compiled.text, values: bindTemplate({ compiled, state, inputs: params.params }) };
1785
- }
1786
- function compileSqlQuery(params) {
1787
- const state = queries.get(params.query) ?? invalid4("Expected an authentic SQL row query");
1788
- const collector = new ParamCollector(params.dialect);
1789
- const text = state.render({ dialect: params.dialect, collector });
1790
- if (collector.values.length > params.dialect.paramBudget)
1791
- invalid4("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
1792
- return { text, values: collector.values, decoder: state.decoder };
1793
- }
1794
- function compileSqlCommand(params) {
1795
- const render = commands.get(params.query) ?? invalid4("Expected an authentic SQL command");
1796
- const collector = new ParamCollector(params.dialect);
1797
- const text = render({ dialect: params.dialect, collector });
1798
- if (collector.values.length > params.dialect.paramBudget)
1799
- invalid4("SQL command exceeds this dialect's parameter budget; statements are never split implicitly");
1800
- return { text, values: collector.values };
1801
- }
1802
- // packages/sql/src/exclusion.ts
1803
- import { VibeError as VibeError12, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
1804
-
1805
- // packages/sql/src/predicate.ts
1806
- import { VibeError as VibeError11, assertPredicateCapabilities, fieldTypeIdentity, isEnumType, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
1807
- function renderPredicate(params) {
1808
- assertPredicateCapabilities({ predicate: params.predicate, dialect: params.dialect.name, usage: params.usage });
1809
- if (params.usage === "exclusionPredicate" && !params.dialect.capabilities.exclusionConstraints)
1810
- throw unsupportedCapability9({ dialect: params.dialect.name, feature: "exclusionConstraints" });
1811
- if (params.usage === "indexPredicate" && !params.dialect.capabilities.partialIndexes) {
1812
- throw unsupportedCapability9({ dialect: params.dialect.name, feature: "partialIndexes" });
1813
- }
1814
- const context = {
1815
- model: params.model,
1816
- dialect: params.dialect,
1817
- enums: params.enums ?? [],
1818
- usage: params.usage,
1819
- site: params.site
1820
- };
1821
- return renderNode({ node: params.predicate, context });
1822
- }
1823
- var PRECEDENCE = {
1824
- or: 1,
1825
- and: 2,
1826
- not: 3,
1827
- compare: 4,
1828
- inList: 4,
1829
- isNull: 4,
1830
- isTrue: 4
1831
- };
1832
- var COMPARISON_SQL = {
1833
- eq: "=",
1834
- ne: "<>",
1835
- gt: ">",
1836
- gte: ">=",
1837
- lt: "<",
1838
- lte: "<="
1839
- };
1840
- var EXACT_INTEGER = /^-?(0|[1-9]\d*)$/;
1841
- var EXACT_DECIMAL = /^-?(0|[1-9]\d*)(\.\d+)?$/;
1842
- var INT32_MIN = -2147483648n;
1843
- var INT32_MAX = 2147483647n;
1844
- var INT64_MIN = -(2n ** 63n);
1845
- var INT64_MAX = 2n ** 63n - 1n;
1846
- function refuse2(params) {
1847
- const { context, rule, message } = params;
1848
- throw new VibeError11({
1849
- code: "VIBE_SCHEMA_INVALID",
1850
- message: `${context.site} on "${context.model.name}": ${message}`,
1851
- meta: { rule, model: context.model.name, site: context.site, ...params.meta ?? {} }
1852
- });
1853
- }
1854
- function renderChild(params) {
1855
- const { node, parent, context } = params;
1856
- if ((node.kind === "and" || node.kind === "or") && node.operands.length === 1) {
1857
- return renderChild({ node: node.operands[0], parent, context });
1858
- }
1859
- const text = renderNode({ node, context });
1860
- return PRECEDENCE[node.kind] < PRECEDENCE[parent] ? `(${text})` : text;
1861
- }
1862
- function renderNode(params) {
1863
- const { node, context } = params;
1864
- switch (node.kind) {
1865
- case "and":
1866
- case "or": {
1867
- if (node.operands.length === 0) {
1868
- refuse2({
1869
- context,
1870
- rule: "predicate-empty",
1871
- message: `${node.kind === "and" ? "and()" : "or()"} has no operands \u2014 an empty boolean group has no honest SQL spelling`
1872
- });
1873
- }
1874
- if (node.operands.length === 1)
1875
- return renderNode({ node: node.operands[0], context });
1876
- const separator = node.kind === "and" ? " AND " : " OR ";
1877
- return node.operands.map((operand) => renderChild({ node: operand, parent: node.kind, context })).join(separator);
1878
- }
1879
- case "not":
1880
- return `NOT ${renderChild({ node: node.operand, parent: "not", context })}`;
1881
- case "isTrue":
1882
- return `COALESCE(${renderNode({ node: node.operand, context })}, FALSE)`;
1883
- case "isNull": {
1884
- const field = resolveField({ name: node.field.name, context, nullTest: true });
1885
- return `${quoteColumn({ field, context })} IS ${node.negate === true ? "NOT NULL" : "NULL"}`;
1886
- }
1887
- case "compare": {
1888
- const left = renderExpression({ expression: node.left, context });
1889
- const right = renderOperand({ operand: node.right, field: left.field, context });
1890
- return `${left.text} ${COMPARISON_SQL[node.op]} ${right}`;
1891
- }
1892
- case "inList": {
1893
- if (node.values.length === 0) {
1894
- refuse2({
1895
- context,
1896
- rule: "predicate-empty",
1897
- message: `inList("${node.field.name}") has no values \u2014 an empty IN list is UNKNOWN on every row and never what an author means`,
1898
- meta: { field: node.field.name }
1899
- });
1900
- }
1901
- const field = resolveField({ name: node.field.name, context });
1902
- const values = node.values.map((value) => renderLiteral({ value, field, context })).join(", ");
1903
- return `${compareColumn({ field, context })} ${node.negate === true ? "NOT IN" : "IN"} (${values})`;
1904
- }
1905
- }
1906
- }
1907
- function resolveField(params) {
1908
- const { name, context } = params;
1909
- const field = context.model.fields.find((candidate) => candidate.name === name);
1910
- if (field === undefined) {
1911
- refuse2({
1912
- context,
1913
- rule: "predicate-unknown-field",
1914
- message: `references field "${name}", which the model does not declare`,
1915
- meta: { field: name }
1916
- });
1917
- }
1918
- if (field.isList) {
1919
- refuse2({
1920
- context,
1921
- rule: "predicate-list-field",
1922
- message: `compares scalar list "${name}" \u2014 array operators differ per dialect; use a raw expression for them`,
1923
- meta: { field: name }
1924
- });
1925
- }
1926
- if (field.isUnsupported === true) {
1927
- refuse2({
1928
- context,
1929
- rule: "predicate-unsupported-type",
1930
- message: `references "${name}", an Unsupported("${field.nativeType ?? "?"}") column whose operators VibeORM does not model \u2014 use a raw expression`,
1931
- meta: { field: name }
1932
- });
1933
- }
1934
- if (params.nullTest !== true && (field.type === "Json" || field.type === "Bytes")) {
1935
- refuse2({
1936
- context,
1937
- rule: "predicate-unsupported-type",
1938
- message: `references ${field.type} column "${name}" \u2014 ${field.type} comparison operators differ per dialect; use a raw expression`,
1939
- meta: { field: name, type: field.type }
1940
- });
1941
- }
1942
- if (context.usage === "check" && context.dialect.name === "mysql" && field.default?.kind === "autoincrement") {
1943
- throw unsupportedCapability9({
1944
- dialect: "mysql",
1945
- feature: `CHECK constraint referencing auto-increment column "${context.model.name}.${name}"`,
1946
- hint: "mysql refuses this with error 3818 \u2014 enforce the rule in application code or on a non-generated column"
1947
- });
1948
- }
1949
- return field;
1950
- }
1951
- function quoteColumn(params) {
1952
- const { field, context } = params;
1953
- return context.dialect.quoteIdent({ name: field.dbName ?? field.name });
1954
- }
1955
- function compareColumn(params) {
1956
- const { field, context } = params;
1957
- const quoted = quoteColumn({ field, context });
1958
- return context.dialect.name === "sqlite" && field.type === "Decimal" ? `CAST(${quoted} AS REAL)` : quoted;
1959
- }
1960
- function renderExpression(params) {
1961
- const { expression, context } = params;
1962
- if (expression.kind === "field") {
1963
- const field = resolveField({ name: expression.name, context });
1964
- return { text: compareColumn({ field, context }), field };
1965
- }
1966
- if (expression.kind === "jsonText") {
1967
- const field = resolveField({ name: expression.field.name, context, nullTest: true });
1968
- if (field.type !== "Json")
1969
- refuse2({ context, rule: "predicate-type-mismatch", message: `jsonText requires a scalar Json field, not "${field.name}"` });
1970
- if (!Array.isArray(expression.path) || Array.from(expression.path).some((segment) => typeof segment !== "string" || segment.includes("\x00"))) {
1971
- refuse2({ context, rule: "predicate-json-path", message: "jsonText requires a string-only path without NUL bytes" });
1972
- }
1973
- const path = expression.path.map((value) => context.dialect.quoteStringLiteral({ value })).join(", ");
1974
- return { text: `(${quoteColumn({ field, context })} #>> ARRAY[${path}]::text[])`, field: { ...field, type: "String" } };
1975
- }
1976
- const inner = renderExpression({ expression: expression.expression, context });
1977
- if (inner.field.type !== "String")
1978
- refuse2({ context, rule: "predicate-type-mismatch", message: "length requires a text expression" });
1979
- return { text: `length(${inner.text})`, field: { ...inner.field, type: "Int" } };
1980
- }
1981
- function renderOperand(params) {
1982
- const { operand, field, context } = params;
1983
- if (operand.kind === "value")
1984
- return renderLiteral({ value: operand, field, context });
1985
- const other = renderExpression({ expression: operand, context });
1986
- const leftType = fieldTypeIdentity({ type: field.type });
1987
- const rightType = fieldTypeIdentity({ type: other.field.type });
1988
- if (leftType !== rightType) {
1989
- refuse2({
1990
- context,
1991
- rule: "predicate-type-mismatch",
1992
- message: `compares "${field.name}" (${leftType}) with "${other.field.name}" (${rightType}) \u2014 both sides of a comparison need the same scalar type`,
1993
- meta: { field: field.name, other: other.field.name, fieldType: leftType, otherType: rightType }
1994
- });
1995
- }
1996
- return other.text;
2691
+ function bindTemplate(params) {
2692
+ const { scope } = params.state;
2693
+ return bindSqlBindingProgram({ program: params.compiled.program, params: params.inputs, isHandle: (value) => executionHandle({ scope, value }) });
1997
2694
  }
1998
- function renderLiteral(params) {
1999
- const { field, context } = params;
2000
- const { value } = params.value;
2001
- const type = field.type;
2002
- const wrongCarrier = (expected) => refuse2({
2003
- context,
2004
- rule: "predicate-type-mismatch",
2005
- message: `compares ${fieldTypeIdentity({ type })} field "${field.name}" with ${describe({ value })} \u2014 ${expected}`,
2006
- meta: { field: field.name, fieldType: fieldTypeIdentity({ type }), value }
2007
- });
2008
- if (isEnumType(type)) {
2009
- if (typeof value !== "string")
2010
- return wrongCarrier(`enum ${type.enum} takes one of its member names as a string`);
2011
- const declared = context.enums.find((entry) => entry.name === type.enum);
2012
- if (declared !== undefined && !declared.values.includes(value)) {
2013
- refuse2({
2014
- context,
2015
- rule: "predicate-enum-member",
2016
- message: `compares "${field.name}" with "${value}", which is not a member of enum ${type.enum} (${declared.values.join(", ")})`,
2017
- meta: { field: field.name, enum: type.enum, value, members: [...declared.values] }
2018
- });
2019
- }
2020
- return stringLiteral({ value, context, field });
2021
- }
2022
- switch (type) {
2023
- case "String":
2024
- if (typeof value !== "string")
2025
- return wrongCarrier("a String column takes a string literal");
2026
- return stringLiteral({ value, context, field });
2027
- case "Boolean":
2028
- if (typeof value !== "boolean")
2029
- return wrongCarrier("a Boolean column takes `true` or `false`");
2030
- return value ? context.dialect.booleanLiteral.true : context.dialect.booleanLiteral.false;
2031
- case "Int":
2032
- return integerLiteral({ value, field, context, min: INT32_MIN, max: INT32_MAX, label: "Int" });
2033
- case "BigInt":
2034
- return integerLiteral({ value, field, context, min: INT64_MIN, max: INT64_MAX, label: "BigInt" });
2035
- case "Float": {
2036
- if (typeof value !== "number")
2037
- return wrongCarrier("a Float column takes a number");
2038
- if (!Number.isFinite(value)) {
2039
- refuse2({
2040
- context,
2041
- rule: "predicate-literal-invalid",
2042
- message: `compares "${field.name}" with ${String(value)} \u2014 a Float literal must be finite`,
2043
- meta: { field: field.name, value: String(value) }
2044
- });
2045
- }
2046
- return String(value);
2047
- }
2048
- case "Decimal": {
2049
- if (typeof value !== "string") {
2050
- return wrongCarrier('a Decimal column takes exact decimal TEXT ("0", "19.99") \u2014 a JS number cannot carry an exact decimal');
2051
- }
2052
- if (!EXACT_DECIMAL.test(value)) {
2053
- refuse2({
2054
- context,
2055
- rule: "predicate-literal-invalid",
2056
- message: `compares "${field.name}" with "${value}", which is not an exact decimal literal`,
2057
- meta: { field: field.name, value }
2058
- });
2059
- }
2060
- return value;
2061
- }
2062
- case "DateTime":
2063
- refuse2({
2064
- context,
2065
- rule: "predicate-unsupported-literal",
2066
- 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`,
2067
- meta: { field: field.name }
2068
- });
2069
- case "Json":
2070
- case "Bytes":
2071
- return wrongCarrier(`${type} columns take no predicate literal`);
2072
- }
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 };
2073
2699
  }
2074
- function integerLiteral(params) {
2075
- const { value, field, context, min, max, label } = params;
2076
- let text;
2077
- if (typeof value === "number") {
2078
- if (!Number.isSafeInteger(value)) {
2079
- refuse2({
2080
- context,
2081
- rule: "predicate-literal-invalid",
2082
- 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`,
2083
- meta: { field: field.name, value: String(value) }
2084
- });
2085
- }
2086
- text = String(value);
2087
- } else if (typeof value === "string") {
2088
- if (!EXACT_INTEGER.test(value)) {
2089
- refuse2({
2090
- context,
2091
- rule: "predicate-literal-invalid",
2092
- message: `compares "${field.name}" with "${value}", which is not an exact integer literal`,
2093
- meta: { field: field.name, value }
2094
- });
2095
- }
2096
- text = value;
2097
- } else {
2098
- refuse2({
2099
- context,
2100
- rule: "predicate-type-mismatch",
2101
- message: `compares ${label} field "${field.name}" with ${describe({ value })} \u2014 an ${label} column takes an integer`,
2102
- meta: { field: field.name, value }
2103
- });
2104
- }
2105
- const parsed = BigInt(text);
2106
- if (parsed < min || parsed > max) {
2107
- refuse2({
2108
- context,
2109
- rule: "predicate-literal-invalid",
2110
- message: `compares "${field.name}" with ${text}, which is outside the range an ${label} column can hold (${min}\u2026${max})`,
2111
- meta: { field: field.name, value: text }
2112
- });
2113
- }
2114
- return text;
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 }) };
2115
2704
  }
2116
- function stringLiteral(params) {
2117
- const { value, context, field } = params;
2118
- if (value.includes("\x00")) {
2119
- refuse2({
2120
- context,
2121
- rule: "predicate-literal-invalid",
2122
- message: `compares "${field.name}" with a string containing a NUL byte, which no dialect here can store`,
2123
- meta: { field: field.name }
2124
- });
2125
- }
2126
- return context.dialect.quoteStringLiteral({ value });
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 };
2127
2712
  }
2128
- function describe(params) {
2129
- const { value } = params;
2130
- return typeof value === "string" ? `the string ${JSON.stringify(value)}` : `the ${typeof value} ${String(value)}`;
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 };
2131
2720
  }
2132
-
2133
2721
  // packages/sql/src/exclusion.ts
2134
- function invalid5(rule) {
2135
- 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}` } });
2136
2725
  }
2137
2726
  function compileExclusion(params) {
2138
2727
  const { exclusion: spec, model, dialect } = params;
2139
2728
  if (!dialect.capabilities.exclusionConstraints)
2140
- throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
2729
+ throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
2141
2730
  if (!spec.name || spec.name.includes("\x00"))
2142
- invalid5("name");
2731
+ invalid6("name");
2143
2732
  if (!["int8range", "tsrange", "tstzrange"].includes(spec.range.type))
2144
- invalid5("range-type");
2733
+ invalid6("range-type");
2145
2734
  if (!["[)", "[]", "()", "(]"].includes(spec.range.bounds))
2146
- invalid5("bounds");
2735
+ invalid6("bounds");
2147
2736
  const field = (name) => {
2148
2737
  const value = model.fields.find((entry) => entry.name === name);
2149
2738
  if (value === undefined || value.isList || value.isUnsupported)
2150
- invalid5("field");
2739
+ invalid6("field");
2151
2740
  return value;
2152
2741
  };
2153
2742
  const endpoint = (value) => {
@@ -2155,17 +2744,17 @@ function compileExclusion(params) {
2155
2744
  return "NULL";
2156
2745
  const infinity = "infinity" in value ? value.infinity : value.nullAs;
2157
2746
  if (infinity !== undefined && (spec.range.type === "int8range" || !["infinity", "-infinity"].includes(infinity)))
2158
- invalid5("infinity");
2747
+ invalid6("infinity");
2159
2748
  const literal = infinity === undefined ? undefined : `${dialect.quoteStringLiteral({ value: infinity })}::${spec.range.type === "tsrange" ? "timestamp" : "timestamptz"}`;
2160
2749
  if ("infinity" in value)
2161
2750
  return literal;
2162
2751
  const column = field(value.field);
2163
2752
  if (fieldTypeIdentity2({ type: column.type }) !== (spec.range.type === "int8range" ? "BigInt" : "DateTime"))
2164
- invalid5("range-field-type");
2753
+ invalid6("range-field-type");
2165
2754
  if (column.type === "DateTime") {
2166
2755
  const timezone = /timestamptz|timestamp.*with time zone/i.test(column.nativeType ?? "timestamptz");
2167
2756
  if (timezone !== (spec.range.type === "tstzrange"))
2168
- invalid5("range-timezone");
2757
+ invalid6("range-timezone");
2169
2758
  }
2170
2759
  const sql = dialect.quoteIdent({ name: column.dbName ?? column.name });
2171
2760
  return literal === undefined ? sql : `COALESCE(${sql}, ${literal})`;
@@ -2173,15 +2762,15 @@ function compileExclusion(params) {
2173
2762
  const keys = spec.keys.map((key) => {
2174
2763
  if ("expression" in key) {
2175
2764
  if (!key.expression.trim() || key.expression.includes("\x00") || key.fields.length === 0)
2176
- invalid5("expression");
2765
+ invalid6("expression");
2177
2766
  key.fields.forEach(field);
2178
2767
  return `(${key.expression}) WITH =`;
2179
2768
  }
2180
2769
  const column = field(key.field);
2181
2770
  if (column.isOptional)
2182
- invalid5("nullable-key-requires-expression");
2771
+ invalid6("nullable-key-requires-expression");
2183
2772
  if (!isEnumType2(column.type) && !["String", "Int", "BigInt", "Float", "Decimal", "Boolean", "DateTime"].includes(fieldTypeIdentity2({ type: column.type })))
2184
- invalid5("key-type");
2773
+ invalid6("key-type");
2185
2774
  return `${dialect.quoteIdent({ name: column.dbName ?? column.name })} WITH =`;
2186
2775
  });
2187
2776
  keys.push(`${spec.range.type}(${endpoint(spec.range.lower)}, ${endpoint(spec.range.upper)}, ${dialect.quoteStringLiteral({ value: spec.range.bounds })}) WITH &&`);
@@ -2190,13 +2779,13 @@ function compileExclusion(params) {
2190
2779
  }
2191
2780
  function renderExclusionSetup(params) {
2192
2781
  if (!params.dialect.capabilities.exclusionConstraints)
2193
- throw unsupportedCapability10({ dialect: params.dialect.name, feature: "exclusionConstraints" });
2782
+ throw unsupportedCapability14({ dialect: params.dialect.name, feature: "exclusionConstraints" });
2194
2783
  return params.exclusion.requiredExtensions.map((name) => `CREATE EXTENSION IF NOT EXISTS ${params.dialect.quoteIdent({ name })};`);
2195
2784
  }
2196
2785
  function renderExclusionDdl(params) {
2197
2786
  const { dialect, table, exclusion, operation } = params;
2198
2787
  if (!dialect.capabilities.exclusionConstraints)
2199
- throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
2788
+ throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
2200
2789
  const target = `ALTER TABLE ${dialect.quoteIdent({ name: table })}`;
2201
2790
  if (operation === "drop")
2202
2791
  return [`${target} DROP CONSTRAINT ${dialect.quoteIdent({ name: exclusion.name })};`];
@@ -2205,14 +2794,14 @@ function renderExclusionDdl(params) {
2205
2794
  function renderExclusionCatalog(params) {
2206
2795
  const { dialect, probe } = params;
2207
2796
  if (!dialect.capabilities.exclusionConstraints)
2208
- throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
2797
+ throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
2209
2798
  const scope = probe === undefined ? `n.nspname = ${dialect.quoteStringLiteral({ value: params.namespace ?? "public" })}` : `t.oid = ${dialect.quoteStringLiteral({ value: probe })}::regclass`;
2210
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`;
2211
2800
  }
2212
2801
  function renderExclusionProbe(params) {
2213
2802
  const { dialect, table, probe, definition } = params;
2214
2803
  if (!dialect.capabilities.exclusionConstraints)
2215
- throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
2804
+ throw unsupportedCapability14({ dialect: dialect.name, feature: "exclusionConstraints" });
2216
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}`];
2217
2806
  }
2218
2807
  // packages/sql/src/ast.ts
@@ -2244,6 +2833,8 @@ function dbNowMarker(params) {
2244
2833
  function isDbNowMarker(value) {
2245
2834
  if (typeof value !== "object" || value === null || Array.isArray(value))
2246
2835
  return false;
2836
+ if (!Object.hasOwn(value, "__dbNow"))
2837
+ return false;
2247
2838
  const keys = Object.keys(value);
2248
2839
  if (keys.length !== 2 || !keys.includes("__dbNow") || !keys.includes("offsetSeconds"))
2249
2840
  return false;
@@ -2291,7 +2882,7 @@ function chainVetoToken(params) {
2291
2882
  return;
2292
2883
  }
2293
2884
  // packages/sql/src/locks.ts
2294
- import { internalError, unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
2885
+ import { internalError, unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
2295
2886
  var ROW_LOCK_PROSE = Object.freeze({
2296
2887
  strengths: Object.freeze({
2297
2888
  update: "FOR UPDATE",
@@ -2311,7 +2902,7 @@ function renderRowLockClause(params) {
2311
2902
  if (syntax === null) {
2312
2903
  if (lock.strength === "update" && lock.wait === "wait")
2313
2904
  return null;
2314
- throw unsupportedCapability11({
2905
+ throw unsupportedCapability15({
2315
2906
  dialect: dialect.name,
2316
2907
  feature: `a ${describeRowLock({ lock })} locking read`,
2317
2908
  hint: `${dialect.name} has no locking-read clause at all`
@@ -2319,7 +2910,7 @@ function renderRowLockClause(params) {
2319
2910
  }
2320
2911
  const strengthText = syntax.strengths[lock.strength];
2321
2912
  if (strengthText === undefined) {
2322
- throw unsupportedCapability11({
2913
+ throw unsupportedCapability15({
2323
2914
  dialect: dialect.name,
2324
2915
  feature: `the ${describeRowLock({ lock: { ...lock, wait: "wait" } })} row-lock strength`,
2325
2916
  hint: `${dialect.name} can express: ${Object.keys(syntax.strengths).join(", ")}`
@@ -2327,7 +2918,7 @@ function renderRowLockClause(params) {
2327
2918
  }
2328
2919
  const waitText = syntax.wait[lock.wait];
2329
2920
  if (waitText === undefined) {
2330
- throw unsupportedCapability11({
2921
+ throw unsupportedCapability15({
2331
2922
  dialect: dialect.name,
2332
2923
  feature: `the "${lock.wait}" lock wait policy`,
2333
2924
  hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
@@ -2346,7 +2937,7 @@ function renderAdvisoryLock(params) {
2346
2937
  const { dialect, statement, collector } = params;
2347
2938
  const functions = dialect.advisoryLockFunctions;
2348
2939
  if (functions === null) {
2349
- throw unsupportedCapability11({
2940
+ throw unsupportedCapability15({
2350
2941
  dialect: dialect.name,
2351
2942
  feature: "advisory locks",
2352
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`
@@ -2385,6 +2976,13 @@ var postgresDialect = {
2385
2976
  capabilities: POSTGRES_CAPABILITIES,
2386
2977
  inArrayStrategy: "any",
2387
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,
2388
2986
  timeOfDayInStrategy: "in",
2389
2987
  upsertForm: "onConflict",
2390
2988
  conflictKeyEquality: {
@@ -2434,6 +3032,7 @@ var postgresDialect = {
2434
3032
  keyCast: Object.freeze({ single: "::bigint", pair: "::int4" })
2435
3033
  },
2436
3034
  rowDefaultKeyword: true,
3035
+ bulkInsertForm: "unnest",
2437
3036
  emptyInsert: { form: "defaultValues", conflictClause: true },
2438
3037
  statementChain: "dataModifyingCte",
2439
3038
  paramBudget: 30000,
@@ -2462,6 +3061,13 @@ var mysqlDialect = {
2462
3061
  capabilities: MYSQL_CAPABILITIES,
2463
3062
  inArrayStrategy: "expand",
2464
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,
2465
3071
  timeOfDayInStrategy: "equalities",
2466
3072
  upsertForm: "onDuplicateKey",
2467
3073
  conflictKeyEquality: {
@@ -2497,6 +3103,7 @@ var mysqlDialect = {
2497
3103
  },
2498
3104
  advisoryLockFunctions: null,
2499
3105
  rowDefaultKeyword: true,
3106
+ bulkInsertForm: "values",
2500
3107
  emptyInsert: { form: "emptyRow" },
2501
3108
  statementChain: null,
2502
3109
  paramBudget: 1e4,
@@ -2528,6 +3135,389 @@ var SQL_DIALECTS = {
2528
3135
  function getSqlDialect(params) {
2529
3136
  return SQL_DIALECTS[params.dialect];
2530
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
+ }
2531
3521
  // packages/sql/src/dialects/sqlite-json-equality.ts
2532
3522
  function renderSqliteJsonEquality(params) {
2533
3523
  const q = (name) => params.dialect.quoteIdent({ name });
@@ -2543,7 +3533,7 @@ function renderSqliteJsonEquality(params) {
2543
3533
  }
2544
3534
 
2545
3535
  // packages/sql/src/dialects/diagnostics.ts
2546
- import { unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
3536
+ import { unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
2547
3537
  var QUERIES = Object.freeze({
2548
3538
  postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
2549
3539
  sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
@@ -2551,7 +3541,7 @@ var QUERIES = Object.freeze({
2551
3541
  function renderDiagnostic(params) {
2552
3542
  const query = QUERIES[params.query];
2553
3543
  if (params.dialect.name !== query.dialect) {
2554
- 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}` });
2555
3545
  }
2556
3546
  return { text: query.text, values: [], returning: "none" };
2557
3547
  }
@@ -2560,7 +3550,7 @@ function isFixedDiagnostic(params) {
2560
3550
  }
2561
3551
 
2562
3552
  // packages/sql/src/dialects/postgres-diagnostics.ts
2563
- import { VibeError as VibeError13, unsupportedCapability as unsupportedCapability13 } from "@vibeorm/schema";
3553
+ import { VibeError as VibeError21, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
2564
3554
  function renderPostgresExplain(params) {
2565
3555
  const { dialect, query, options } = params;
2566
3556
  requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
@@ -2595,7 +3585,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
2595
3585
  });
2596
3586
  function requirePostgres(params) {
2597
3587
  if (params.dialect.name !== "postgres") {
2598
- throw unsupportedCapability13({
3588
+ throw unsupportedCapability24({
2599
3589
  dialect: params.dialect.name,
2600
3590
  feature: params.feature,
2601
3591
  hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
@@ -2613,7 +3603,7 @@ function renderPostgresWorkload(params) {
2613
3603
  if (query === "statementSnapshot") {
2614
3604
  const bounded = limit ?? 20;
2615
3605
  if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
2616
- throw new VibeError13({
3606
+ throw new VibeError21({
2617
3607
  code: "VIBE_CONFIG",
2618
3608
  message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
2619
3609
  meta: { limit }
@@ -2632,7 +3622,7 @@ function renderPostgresWorkload(params) {
2632
3622
  function requireArg(params) {
2633
3623
  const { value, name, query } = params;
2634
3624
  if (value === undefined || value.length === 0) {
2635
- throw new VibeError13({
3625
+ throw new VibeError21({
2636
3626
  code: "VIBE_CONFIG",
2637
3627
  message: `the "${query}" diagnostic read requires a ${name}`,
2638
3628
  meta: { query, argument: name }
@@ -2642,10 +3632,10 @@ function requireArg(params) {
2642
3632
  }
2643
3633
 
2644
3634
  // packages/sql/src/dialects/postgres-rls-readiness.ts
2645
- import { unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
3635
+ import { unsupportedCapability as unsupportedCapability26 } from "@vibeorm/schema";
2646
3636
 
2647
3637
  // packages/sql/src/dialects/postgres-rls.ts
2648
- 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";
2649
3639
  var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
2650
3640
  var COLUMN_TYPES = {
2651
3641
  String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
@@ -2658,18 +3648,18 @@ var COLUMN_TYPES = {
2658
3648
  };
2659
3649
  function requireRls(params) {
2660
3650
  if (!params.dialect.capabilities.nativeRowLevelSecurity) {
2661
- throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
3651
+ throw unsupportedCapability25({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
2662
3652
  }
2663
3653
  }
2664
3654
  function identifier2(params) {
2665
3655
  if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
2666
- 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" });
2667
3657
  }
2668
3658
  return params.dialect.quoteIdent({ name: params.name });
2669
3659
  }
2670
3660
  function tableRef(params) {
2671
3661
  if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
2672
- 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" });
2673
3663
  }
2674
3664
  return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
2675
3665
  }
@@ -2710,7 +3700,7 @@ function renderRlsColumnType(params) {
2710
3700
  if (native !== undefined)
2711
3701
  return native;
2712
3702
  }
2713
- 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}` });
2714
3704
  }
2715
3705
  function protectedModel(params) {
2716
3706
  requireRls(params);
@@ -2718,7 +3708,7 @@ function protectedModel(params) {
2718
3708
  assertValidSchemaIR({ schema: params.schema });
2719
3709
  const model = params.schema.models.find((entry) => entry.name === params.model);
2720
3710
  if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
2721
- 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 } });
2722
3712
  }
2723
3713
  return model;
2724
3714
  }
@@ -2751,7 +3741,7 @@ function predicate(params) {
2751
3741
  }).join(" AND ");
2752
3742
  return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
2753
3743
  }
2754
- 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" });
2755
3745
  }
2756
3746
  function renderRlsPolicy(params) {
2757
3747
  const model = protectedModel(params);
@@ -2785,7 +3775,7 @@ function renderRlsContext(params) {
2785
3775
  function renderRlsRole(params) {
2786
3776
  requireRls(params);
2787
3777
  if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
2788
- 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" });
2789
3779
  }
2790
3780
  const collector = new ParamCollector(params.dialect);
2791
3781
  return query({ text: `SELECT set_config('role', ${collector.add(params.role)}, true)`, values: collector.values });
@@ -2937,7 +3927,7 @@ ORDER BY c.relname, p.polname`,
2937
3927
  });
2938
3928
  function requireRls2(params) {
2939
3929
  if (!params.dialect.capabilities.nativeRowLevelSecurity) {
2940
- throw unsupportedCapability15({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
3930
+ throw unsupportedCapability26({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
2941
3931
  }
2942
3932
  }
2943
3933
  function renderRlsReadinessQuery(params) {
@@ -2969,7 +3959,7 @@ function renderRlsPolicyPath(params) {
2969
3959
  }
2970
3960
 
2971
3961
  // packages/sql/src/render.ts
2972
- 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";
2973
3963
 
2974
3964
  // packages/sql/src/like.ts
2975
3965
  function escapeLikePattern(params) {
@@ -2995,6 +3985,9 @@ function strictMatchPattern(params) {
2995
3985
  }
2996
3986
  return op === "contains" ? containsPattern({ raw: value }) : op === "startsWith" ? startsWithPattern({ raw: value }) : endsWithPattern({ raw: value });
2997
3987
  }
3988
+ function strictMatchCollationPrefilter(params) {
3989
+ return params.strategy === "binary" && params.op === "startsWith" && !params.negate;
3990
+ }
2998
3991
 
2999
3992
  // packages/sql/src/render.ts
3000
3993
  function renderStatement(params) {
@@ -3054,13 +4047,13 @@ function quoteCol(dialect, column) {
3054
4047
  const ref = column.table === undefined ? name : `${dialect.quoteIdent({ name: column.table })}.${name}`;
3055
4048
  if (column.castReal === true) {
3056
4049
  if (dialect.name !== "sqlite") {
3057
- throw internalError2({ message: "ColRef.castReal is a sqlite Decimal construct" });
4050
+ throw internalError3({ message: "ColRef.castReal is a sqlite Decimal construct" });
3058
4051
  }
3059
4052
  return `CAST(${ref} AS REAL)`;
3060
4053
  }
3061
4054
  if (column.castJsonb !== undefined) {
3062
4055
  if (dialect.name !== "postgres") {
3063
- throw internalError2({ message: "ColRef.castJsonb is a postgres json construct" });
4056
+ throw internalError3({ message: "ColRef.castJsonb is a postgres json construct" });
3064
4057
  }
3065
4058
  return `${ref}::${column.castJsonb}`;
3066
4059
  }
@@ -3077,7 +4070,7 @@ function jsonCellCast(dialect, cell) {
3077
4070
  if (cell.storage !== "json" || dialect.jsonParamCast === "")
3078
4071
  return dialect.jsonParamCast;
3079
4072
  if (dialect.name !== "postgres") {
3080
- throw internalError2({ message: "JsonParamCell storage json is a postgres construct" });
4073
+ throw internalError3({ message: "JsonParamCell storage json is a postgres construct" });
3081
4074
  }
3082
4075
  return "::text::json";
3083
4076
  }
@@ -3098,7 +4091,7 @@ function quoteProjection(dialect, column) {
3098
4091
  if (column.transform === "castText")
3099
4092
  return `${textProjection({ dialect, expression: ref })} AS ${alias}`;
3100
4093
  if (dialect.name !== "postgres") {
3101
- throw internalError2({ message: "encodeBase64 is a postgres construct" });
4094
+ throw internalError3({ message: "encodeBase64 is a postgres construct" });
3102
4095
  }
3103
4096
  return `encode(${ref}, 'base64') AS ${alias}`;
3104
4097
  }
@@ -3120,7 +4113,7 @@ function renderTable(dialect, table) {
3120
4113
  }
3121
4114
  function assertCount(value, what) {
3122
4115
  if (!Number.isSafeInteger(value) || value < 0) {
3123
- 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}` });
3124
4117
  }
3125
4118
  }
3126
4119
  var COMPARISON_SQL2 = {
@@ -3152,7 +4145,7 @@ function renderAggRef(dialect, agg) {
3152
4145
  function assertScalarArrays(dialect, feature) {
3153
4146
  if (dialect.capabilities.scalarArrays)
3154
4147
  return;
3155
- throw unsupportedCapability16({
4148
+ throw unsupportedCapability27({
3156
4149
  dialect: dialect.name,
3157
4150
  feature,
3158
4151
  hint: "only postgres has native array columns \u2014 model the list as a related table instead"
@@ -3164,7 +4157,7 @@ function renderExpr(params) {
3164
4157
  case "cmp": {
3165
4158
  const op = COMPARISON_SQL2[expr.op];
3166
4159
  if (op === undefined) {
3167
- throw internalError2({ message: `unknown comparison operator "${expr.op}"` });
4160
+ throw internalError3({ message: `unknown comparison operator "${expr.op}"` });
3168
4161
  }
3169
4162
  if (isDbNowMarker(expr.value)) {
3170
4163
  return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
@@ -3175,7 +4168,7 @@ function renderExpr(params) {
3175
4168
  case "cmpCol": {
3176
4169
  const op = COMPARISON_SQL2[expr.op];
3177
4170
  if (op === undefined) {
3178
- throw internalError2({ message: `unknown comparison operator "${expr.op}"` });
4171
+ throw internalError3({ message: `unknown comparison operator "${expr.op}"` });
3179
4172
  }
3180
4173
  return `${quoteCol(dialect, expr.left)} ${op} ${quoteCol(dialect, expr.right)}`;
3181
4174
  }
@@ -3199,11 +4192,11 @@ function renderExpr(params) {
3199
4192
  }
3200
4193
  case "tupleIn": {
3201
4194
  if (expr.columns.length === 0) {
3202
- throw internalError2({ message: "tupleIn needs at least one column" });
4195
+ throw internalError3({ message: "tupleIn needs at least one column" });
3203
4196
  }
3204
4197
  for (const row of expr.rows) {
3205
4198
  if (row.length !== expr.columns.length) {
3206
- throw internalError2({
4199
+ throw internalError3({
3207
4200
  message: `tupleIn row has ${row.length} values for ${expr.columns.length} columns`
3208
4201
  });
3209
4202
  }
@@ -3224,6 +4217,32 @@ function renderExpr(params) {
3224
4217
  const base = conjunctions.length === 1 ? conjunctions[0] : `(${conjunctions.join(" OR ")})`;
3225
4218
  return expr.negate === true ? `NOT ${base.startsWith("(") ? base : `(${base})`}` : base;
3226
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
+ }
3227
4246
  case "like": {
3228
4247
  const column = quoteCol(dialect, expr.column);
3229
4248
  const placeholder = collector.add(expr.pattern);
@@ -3252,10 +4271,17 @@ function renderExpr(params) {
3252
4271
  if (dialect.strictMatchStrategy === "glob") {
3253
4272
  base = `${column} GLOB ${collector.add(pattern)}`;
3254
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;
3255
4279
  const placeholder = collector.add(pattern);
3256
4280
  base = dialect.strictMatchStrategy === "binary" ? `${column} LIKE CAST(${placeholder} AS BINARY)` : `${column} LIKE ${placeholder}`;
3257
4281
  if (dialect.likeEscapeClause !== null)
3258
4282
  base = `${base}${dialect.likeEscapeClause}`;
4283
+ if (prefilter !== null)
4284
+ base = `(${prefilter} AND ${base})`;
3259
4285
  }
3260
4286
  }
3261
4287
  return expr.negate === true ? `NOT (${base})` : base;
@@ -3277,7 +4303,7 @@ function renderExpr(params) {
3277
4303
  case "aggCmp": {
3278
4304
  const op = COMPARISON_SQL2[expr.op];
3279
4305
  if (op === undefined) {
3280
- throw internalError2({ message: `unknown comparison operator "${expr.op}"` });
4306
+ throw internalError3({ message: `unknown comparison operator "${expr.op}"` });
3281
4307
  }
3282
4308
  return `${renderAggRef(dialect, expr.agg)} ${op} ${collector.add(expr.value)}`;
3283
4309
  }
@@ -3355,10 +4381,146 @@ function listComparison(params) {
3355
4381
  }
3356
4382
  return { column: quoteCol(dialect, column), operand: collector.add(value) };
3357
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
+ }
3358
4520
  function jsonStringPattern(params) {
3359
4521
  const { expr } = params;
3360
4522
  if (typeof expr.value !== "string") {
3361
- throw internalError2({ message: `json ${expr.op} expects a string operand` });
4523
+ throw internalError3({ message: `json ${expr.op} expects a string operand` });
3362
4524
  }
3363
4525
  if (expr.op === "string_contains")
3364
4526
  return containsPattern({ raw: expr.value });
@@ -3382,7 +4544,7 @@ function renderJsonLike(params) {
3382
4544
  function renderJsonFilter(params) {
3383
4545
  const { dialect, expr, collector } = params;
3384
4546
  if (!dialect.supportedJsonOps.includes(expr.op)) {
3385
- throw unsupportedCapability16({
4547
+ throw unsupportedCapability27({
3386
4548
  dialect: dialect.name,
3387
4549
  feature: `the json filter "${expr.op}"`,
3388
4550
  hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
@@ -3449,7 +4611,7 @@ function renderJsonFilter(params) {
3449
4611
  const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
3450
4612
  const tests = elements.map((element) => {
3451
4613
  if (element === null || typeof element === "object" && element !== null) {
3452
- throw unsupportedCapability16({
4614
+ throw unsupportedCapability27({
3453
4615
  dialect: dialect.name,
3454
4616
  feature: "json array_contains with object/array/null elements",
3455
4617
  hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
@@ -3463,7 +4625,7 @@ function renderJsonFilter(params) {
3463
4625
  return tests.length === 0 ? guard : `(${[guard, ...tests].join(" AND ")})`;
3464
4626
  }
3465
4627
  default:
3466
- 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` });
3467
4629
  }
3468
4630
  }
3469
4631
  const column = quoteCol(dialect, expr.column);
@@ -3518,11 +4680,11 @@ function renderSelectExpr(params) {
3518
4680
  function renderPositionalRow(params) {
3519
4681
  const { dialect, join, subAlias } = params;
3520
4682
  if (join.select.columns.length === 0 || join.select.selectExpr !== undefined || (join.select.aggregates?.length ?? 0) > 0 || join.select.rowNumber !== undefined) {
3521
- 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" });
3522
4684
  }
3523
4685
  const names = join.select.columns.map((column) => projectionOutputName({ column }));
3524
4686
  if (new Set(names).size !== names.length) {
3525
- throw internalError2({ message: "positional lateral projection has duplicate output names" });
4687
+ throw internalError3({ message: "positional lateral projection has duplicate output names" });
3526
4688
  }
3527
4689
  const values = names.map((name) => `${subAlias}.${dialect.quoteIdent({ name })}`);
3528
4690
  const chunks = [];
@@ -3530,7 +4692,7 @@ function renderPositionalRow(params) {
3530
4692
  chunks.push(`json_build_array(${values.slice(index, index + JSON_TRANSPORT.chunkSize).join(", ")})`);
3531
4693
  }
3532
4694
  if (chunks.length > JSON_TRANSPORT.chunkSize) {
3533
- throw internalError2({ message: "positional lateral projection exceeds the transport width limit" });
4695
+ throw internalError3({ message: "positional lateral projection exceeds the transport width limit" });
3534
4696
  }
3535
4697
  return chunks.length === 1 ? chunks[0] : `json_build_array(${chunks.join(", ")})`;
3536
4698
  }
@@ -3538,8 +4700,8 @@ function renderLateralJoin(params) {
3538
4700
  const { dialect, join, collector } = params;
3539
4701
  const inner = renderSelect({ dialect, statement: join.select, collector });
3540
4702
  if (join.agg === "count") {
3541
- 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) {
3542
- 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" });
3543
4705
  }
3544
4706
  return `LEFT JOIN LATERAL (${inner.text}) AS ${dialect.quoteIdent({ name: join.alias })} ON true`;
3545
4707
  }
@@ -3570,8 +4732,8 @@ function renderLateralJoin(params) {
3570
4732
  function renderCountSubquery(params) {
3571
4733
  const { dialect, subquery, collector } = params;
3572
4734
  const select = subquery.select;
3573
- 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) {
3574
- 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(*)" });
3575
4737
  }
3576
4738
  const inner = renderSelect({ dialect, statement: select, collector });
3577
4739
  return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
@@ -3601,27 +4763,27 @@ function renderKeyTableJoin(params) {
3601
4763
  const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
3602
4764
  const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
3603
4765
  if (!admitted) {
3604
- throw unsupportedCapability16({
4766
+ throw unsupportedCapability27({
3605
4767
  dialect: dialect.name,
3606
4768
  feature: "relation key table",
3607
4769
  hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
3608
4770
  });
3609
4771
  }
3610
4772
  if (join.columns.length === 0 || join.columns.length !== join.members.length) {
3611
- 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}` });
3612
4774
  }
3613
4775
  if (join.rows.length === 0)
3614
- throw internalError2({ message: "key table needs at least one key row" });
4776
+ throw internalError3({ message: "key table needs at least one key row" });
3615
4777
  for (const member of join.members) {
3616
4778
  if (member.kind === "decimal" && !(Number.isInteger(member.scale) && member.scale >= 0 && member.scale <= 30)) {
3617
- 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)}` });
3618
4780
  }
3619
4781
  }
3620
4782
  const alias = dialect.quoteIdent({ name: join.alias });
3621
4783
  const memberNames = join.members.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
3622
4784
  const rows = join.rows.map((row, ordinal) => {
3623
4785
  if (row.length !== join.members.length) {
3624
- 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` });
3625
4787
  }
3626
4788
  const cells = row.map((value, index) => {
3627
4789
  const placeholder = collector.add(value);
@@ -3648,13 +4810,25 @@ function renderKeyTableJoin(params) {
3648
4810
  const on = join.columns.map((column, index) => `${quoteCol(dialect, column)} = ${alias}.${memberNames[index]}`).join(" AND ");
3649
4811
  return `INNER JOIN (VALUES ${rows.join(", ")}) AS ${alias} (${[...memberNames, dialect.quoteIdent({ name: join.ordinalAlias })].join(", ")}) ON ${on}`;
3650
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
+ }
3651
4825
  function renderSelect(params) {
3652
4826
  const { dialect, statement, collector } = params;
3653
4827
  if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
3654
- throw internalError2({ message: "only countAll can combine with a column list" });
4828
+ throw internalError3({ message: "only countAll can combine with a column list" });
3655
4829
  }
3656
4830
  if (statement.selectExpr !== undefined && statement.aggregates !== undefined && statement.aggregates.length > 0) {
3657
- throw internalError2({ message: "selectExpr and aggregates cannot combine" });
4831
+ throw internalError3({ message: "selectExpr and aggregates cannot combine" });
3658
4832
  }
3659
4833
  const columnList = statement.columns.map((column) => quoteProjection(dialect, column));
3660
4834
  if (statement.selectExpr !== undefined) {
@@ -3675,16 +4849,19 @@ function renderSelect(params) {
3675
4849
  if (statement.rowNumber !== undefined) {
3676
4850
  const rowNumber = statement.rowNumber;
3677
4851
  if (rowNumber.partitionBy.length === 0) {
3678
- throw internalError2({ message: "rowNumber needs at least one partition column" });
4852
+ throw internalError3({ message: "rowNumber needs at least one partition column" });
3679
4853
  }
3680
4854
  const partition = rowNumber.partitionBy.map((column) => quoteCol(dialect, column)).join(", ");
3681
4855
  const order = rowNumber.orderBy.length === 0 ? "" : ` ORDER BY ${rowNumber.orderBy.map((term) => renderOrderTerm({ dialect, term, collector })).join(", ")}`;
3682
4856
  columnList.push(`ROW_NUMBER() OVER (PARTITION BY ${partition}${order}) AS ${dialect.quoteIdent({ name: rowNumber.alias })}`);
3683
4857
  }
3684
- 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
+ }
3685
4862
  if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
3686
4863
  if (!dialect.capabilities.distinctOn) {
3687
- throw unsupportedCapability16({
4864
+ throw unsupportedCapability27({
3688
4865
  dialect: dialect.name,
3689
4866
  feature: "SELECT DISTINCT ON",
3690
4867
  hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
@@ -3694,9 +4871,16 @@ function renderSelect(params) {
3694
4871
  }
3695
4872
  const columns = columnList.length === 0 ? "*" : columnList.join(", ");
3696
4873
  let from;
3697
- 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) {
3698
4882
  if (statement.table.alias !== undefined) {
3699
- 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" });
3700
4884
  }
3701
4885
  const inner = renderSelect({ dialect, statement: statement.fromSelect, collector });
3702
4886
  from = `(${inner.text}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
@@ -3707,7 +4891,7 @@ function renderSelect(params) {
3707
4891
  if (statement.innerJoins !== undefined && statement.innerJoins.length > 0) {
3708
4892
  for (const join of statement.innerJoins) {
3709
4893
  if (join.on.length === 0) {
3710
- throw internalError2({ message: "inner join needs at least one ON pair" });
4894
+ throw internalError3({ message: "inner join needs at least one ON pair" });
3711
4895
  }
3712
4896
  const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
3713
4897
  text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
@@ -3716,9 +4900,17 @@ function renderSelect(params) {
3716
4900
  if (statement.keyTable !== undefined) {
3717
4901
  text += ` ${renderKeyTableJoin({ dialect, join: statement.keyTable, collector })}`;
3718
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
+ }
3719
4911
  if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
3720
4912
  if (!dialect.capabilities.lateralJoin) {
3721
- throw unsupportedCapability16({
4913
+ throw unsupportedCapability27({
3722
4914
  dialect: dialect.name,
3723
4915
  feature: "LATERAL JOIN relation loading",
3724
4916
  hint: 'use the portable "query" relation strategy on this dialect'
@@ -3759,17 +4951,71 @@ function renderSelect(params) {
3759
4951
  }
3760
4952
  return { text, values: collector.values, returning: "none" };
3761
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
+ }
3762
5008
  function renderInsert(params) {
3763
5009
  const { dialect, statement, collector } = params;
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" });
3766
5012
  }
3767
5013
  const defaultValues = statement.columns.length === 0 && dialect.emptyInsert.form === "defaultValues";
3768
5014
  if (defaultValues && statement.rows.length !== 1) {
3769
- throw internalError2({ message: `${dialect.name} inserts an all-default row as DEFAULT VALUES, one row per statement` });
5015
+ throw internalError3({ message: `${dialect.name} inserts an all-default row as DEFAULT VALUES, one row per statement` });
3770
5016
  }
3771
5017
  if (defaultValues && statement.onConflict !== undefined && !(dialect.emptyInsert.form === "defaultValues" && dialect.emptyInsert.conflictClause)) {
3772
- throw unsupportedCapability16({
5018
+ throw unsupportedCapability27({
3773
5019
  dialect: dialect.name,
3774
5020
  feature: "a conflict clause on an all-default row (DEFAULT VALUES)",
3775
5021
  hint: "supply at least one column value, or drop the conflict clause"
@@ -3777,7 +5023,7 @@ function renderInsert(params) {
3777
5023
  }
3778
5024
  for (const row of statement.rows) {
3779
5025
  if (row.length !== statement.columns.length) {
3780
- throw internalError2({
5026
+ throw internalError3({
3781
5027
  message: `insert row has ${row.length} values but ${statement.columns.length} columns`
3782
5028
  });
3783
5029
  }
@@ -3786,7 +5032,8 @@ function renderInsert(params) {
3786
5032
  const skipConflicts = onConflict !== undefined && "doNothing" in onConflict;
3787
5033
  const insertKeyword = skipConflicts && dialect.doNothingForm === "insertIgnore" ? "INSERT IGNORE INTO" : "INSERT INTO";
3788
5034
  const columnList = statement.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
3789
- 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) => {
3790
5037
  if (isJsonParamCell(value))
3791
5038
  return `${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
3792
5039
  if (isDbNowMarker(value)) {
@@ -3795,13 +5042,13 @@ function renderInsert(params) {
3795
5042
  if (!isSqlDefaultCell(value))
3796
5043
  return collector.add(value);
3797
5044
  if (!dialect.rowDefaultKeyword) {
3798
- throw internalError2({
5045
+ throw internalError3({
3799
5046
  message: `${dialect.name} cannot render a per-row DEFAULT cell \u2014 rows must be grouped by column set`
3800
5047
  });
3801
5048
  }
3802
5049
  return "DEFAULT";
3803
5050
  }).join(", ")})`).join(", ");
3804
- let text = defaultValues ? `${insertKeyword} ${renderTable(dialect, statement.table)} DEFAULT VALUES` : `${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}`}`;
3805
5052
  if (onConflict !== undefined && !("doNothing" in onConflict) && dialect.excludedRowRef.form === "rowAlias" && Object.values(onConflict.update).some((value) => isExcludedRef(value) || isSqlFragment(value) && sqlUsesIncoming({ fragment: value }))) {
3806
5053
  text += ` AS ${dialect.quoteIdent({ name: dialect.excludedRowRef.alias })}`;
3807
5054
  }
@@ -3811,7 +5058,7 @@ function renderInsert(params) {
3811
5058
  let target = "";
3812
5059
  if (onConflict.constraint !== undefined) {
3813
5060
  if (dialect.name !== "postgres") {
3814
- throw unsupportedCapability16({
5061
+ throw unsupportedCapability27({
3815
5062
  dialect: dialect.name,
3816
5063
  feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
3817
5064
  hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
@@ -3826,7 +5073,7 @@ function renderInsert(params) {
3826
5073
  }
3827
5074
  text += ` ON CONFLICT${target} DO NOTHING`;
3828
5075
  } else if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
3829
- throw unsupportedCapability16({
5076
+ throw unsupportedCapability27({
3830
5077
  dialect: dialect.name,
3831
5078
  feature: "a refined conflict target (constraint / partial-index predicate) on INSERT IGNORE",
3832
5079
  hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
@@ -3835,23 +5082,23 @@ function renderInsert(params) {
3835
5082
  } else {
3836
5083
  const updates = Object.entries(onConflict.update);
3837
5084
  if (updates.length === 0) {
3838
- throw internalError2({ message: "upsert update must set at least one column" });
5085
+ throw internalError3({ message: "upsert update must set at least one column" });
3839
5086
  }
3840
5087
  const selfRef = dialect.quoteIdent({ name: statement.table.name });
3841
5088
  const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true, tableName: statement.table.name });
3842
5089
  if (dialect.upsertForm === "onConflict") {
3843
5090
  if (onConflict.lastInsertIdColumn !== undefined) {
3844
- throw internalError2({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
5091
+ throw internalError3({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
3845
5092
  }
3846
5093
  if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
3847
- throw unsupportedCapability16({
5094
+ throw unsupportedCapability27({
3848
5095
  dialect: dialect.name,
3849
5096
  feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
3850
5097
  hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
3851
5098
  });
3852
5099
  }
3853
5100
  if (onConflict.constraint !== undefined && onConflict.conflictWhere !== undefined) {
3854
- throw internalError2({
5101
+ throw internalError3({
3855
5102
  message: "a named-constraint conflict target already identifies the index \u2014 it cannot carry a partial predicate too"
3856
5103
  });
3857
5104
  }
@@ -3860,14 +5107,14 @@ function renderInsert(params) {
3860
5107
  text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
3861
5108
  } else {
3862
5109
  if (onConflict.constraint !== undefined) {
3863
- throw unsupportedCapability16({
5110
+ throw unsupportedCapability27({
3864
5111
  dialect: dialect.name,
3865
5112
  feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
3866
5113
  hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
3867
5114
  });
3868
5115
  }
3869
5116
  if (onConflict.conflictWhere !== undefined) {
3870
- throw unsupportedCapability16({
5117
+ throw unsupportedCapability27({
3871
5118
  dialect: dialect.name,
3872
5119
  feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
3873
5120
  hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
@@ -3885,7 +5132,7 @@ function renderUpdate(params) {
3885
5132
  const { dialect, statement, collector } = params;
3886
5133
  const assignments = Object.entries(statement.set);
3887
5134
  if (assignments.length === 0) {
3888
- throw internalError2({ message: "update must set at least one column" });
5135
+ throw internalError3({ message: "update must set at least one column" });
3889
5136
  }
3890
5137
  const setSql = renderAssignments({ dialect, entries: assignments, collector });
3891
5138
  let text = `UPDATE ${renderTable(dialect, statement.table)} SET ${setSql}`;
@@ -3908,12 +5155,12 @@ function renderAssignments(params) {
3908
5155
  const read = selfRef === undefined ? column : `${selfRef}.${column}`;
3909
5156
  if (isSqlFragment(value)) {
3910
5157
  if (params.allowExcluded !== true || selfRef === undefined)
3911
- throw internalError2({ message: "SQL assignment fragments require a conflict update" });
5158
+ throw internalError3({ message: "SQL assignment fragments require a conflict update" });
3912
5159
  return `${column} = ${renderSqlFragment({ dialect, fragment: value, collector, conflict: { table: params.tableName } })}`;
3913
5160
  }
3914
5161
  if (isExcludedRef(value)) {
3915
5162
  if (params.allowExcluded !== true) {
3916
- throw internalError2({
5163
+ throw internalError3({
3917
5164
  message: `an incoming-row reference (${value.column}) is only meaningful inside an upsert's update arm`
3918
5165
  });
3919
5166
  }
@@ -3954,19 +5201,19 @@ var CHAIN_ONE_ALIAS = "__vibe_one";
3954
5201
  function renderChain(params) {
3955
5202
  const { dialect, statement, collector } = params;
3956
5203
  if (dialect.statementChain === null) {
3957
- throw unsupportedCapability16({
5204
+ throw unsupportedCapability27({
3958
5205
  dialect: dialect.name,
3959
5206
  feature: "a statement chain (data-modifying WITH steps)",
3960
5207
  hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
3961
5208
  });
3962
5209
  }
3963
5210
  if (statement.steps.length === 0)
3964
- throw internalError2({ message: "a statement chain needs at least one step" });
5211
+ throw internalError3({ message: "a statement chain needs at least one step" });
3965
5212
  const defined = new Set;
3966
5213
  const steps = [];
3967
5214
  for (const step of statement.steps) {
3968
5215
  if (step.name === CHAIN_ROWS_ALIAS || step.name === CHAIN_ONE_ALIAS || defined.has(step.name)) {
3969
- 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` });
3970
5217
  }
3971
5218
  const body = renderChainStep({ dialect, statement: step.statement, collector, earlier: defined });
3972
5219
  steps.push(`${dialect.quoteIdent({ name: step.name })} AS (${body})`);
@@ -3974,11 +5221,11 @@ function renderChain(params) {
3974
5221
  }
3975
5222
  const result = statement.result;
3976
5223
  if (result.rowsFrom.length === 0 || result.columns.length === 0) {
3977
- 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" });
3978
5225
  }
3979
5226
  for (const name of result.rowsFrom) {
3980
5227
  if (!defined.has(name))
3981
- throw internalError2({ message: `chain result reads undefined step "${name}"` });
5228
+ throw internalError3({ message: `chain result reads undefined step "${name}"` });
3982
5229
  }
3983
5230
  const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
3984
5231
  const union = result.rowsFrom.map((name) => `SELECT ${columnList} FROM ${dialect.quoteIdent({ name })}`).join(" UNION ALL ");
@@ -3987,7 +5234,7 @@ function renderChain(params) {
3987
5234
  const extra = [];
3988
5235
  for (const alias of [result.verdict?.alias, result.tally?.alias]) {
3989
5236
  if (alias !== undefined && result.columns.includes(alias)) {
3990
- 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` });
3991
5238
  }
3992
5239
  }
3993
5240
  const rows = dialect.quoteIdent({ name: CHAIN_ROWS_ALIAS });
@@ -4024,21 +5271,21 @@ function renderSelectCell(params) {
4024
5271
  if (isDbNowMarker(value))
4025
5272
  return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
4026
5273
  if (isSqlDefaultCell(value)) {
4027
- 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" });
4028
5275
  }
4029
5276
  return collector.add(value);
4030
5277
  }
4031
5278
  function renderRowSource(params) {
4032
5279
  const { dialect, source, collector } = params;
4033
5280
  if (source.columns.length === 0 || source.rows.length === 0) {
4034
- 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" });
4035
5282
  }
4036
5283
  const typedBy = renderTable(dialect, source.typedBy);
4037
5284
  const columns = source.columns.map((name) => dialect.quoteIdent({ name }));
4038
5285
  const rows = [`(${columns.map((column) => `(SELECT ${column} FROM ${typedBy} WHERE FALSE)`).join(", ")})`];
4039
5286
  for (const row of source.rows) {
4040
5287
  if (row.length !== source.columns.length) {
4041
- 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` });
4042
5289
  }
4043
5290
  rows.push(`(${row.map((value) => renderSelectCell({ dialect, value, collector })).join(", ")})`);
4044
5291
  }
@@ -4050,12 +5297,12 @@ function renderTallyColumn(params) {
4050
5297
  const { dialect, result, defined, collector } = params;
4051
5298
  const tally = result.tally;
4052
5299
  if (tally === undefined)
4053
- 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" });
4054
5301
  if (tally.steps.length === 0)
4055
- throw internalError2({ message: "a chain tally needs at least one step" });
5302
+ throw internalError3({ message: "a chain tally needs at least one step" });
4056
5303
  const step = (name) => {
4057
5304
  if (!defined.has(name))
4058
- throw internalError2({ message: `chain tally or veto reads undefined step "${name}"` });
5305
+ throw internalError3({ message: `chain tally or veto reads undefined step "${name}"` });
4059
5306
  return dialect.quoteIdent({ name });
4060
5307
  };
4061
5308
  const count = (name) => `(SELECT count(*) FROM ${step(name)})`;
@@ -4065,12 +5312,12 @@ function renderTallyColumn(params) {
4065
5312
  return `${sum} AS ${dialect.quoteIdent({ name: tally.alias })}`;
4066
5313
  const arms = vetoes.map((veto) => {
4067
5314
  if (!CHAIN_VETO_TOKEN_PATTERN.test(veto.token))
4068
- 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` });
4069
5316
  if (typeof veto.nonce !== "string" || veto.nonce === "")
4070
- 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` });
4071
5318
  const minRows = veto.minRows ?? 1;
4072
5319
  if (!Number.isSafeInteger(minRows) || minRows < 1)
4073
- 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` });
4074
5321
  const when = minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
4075
5322
  return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
4076
5323
  });
@@ -4079,25 +5326,25 @@ function renderTallyColumn(params) {
4079
5326
  function renderInsertSelect(params) {
4080
5327
  const { dialect, statement, collector } = params;
4081
5328
  if (statement.columns.length === 0)
4082
- throw internalError2({ message: "insert requires at least one column" });
5329
+ throw internalError3({ message: "insert requires at least one column" });
4083
5330
  if (statement.cells.length !== statement.columns.length) {
4084
- throw internalError2({
5331
+ throw internalError3({
4085
5332
  message: `insert row has ${statement.cells.length} values but ${statement.columns.length} columns`
4086
5333
  });
4087
5334
  }
4088
5335
  const from = statement.from ?? [];
4089
5336
  for (const name of from) {
4090
5337
  if (!params.earlier.has(name))
4091
- 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` });
4092
5339
  }
4093
5340
  const source = statement.source;
4094
5341
  if (source !== undefined && (params.earlier.has(source.alias) || from.includes(source.alias))) {
4095
- 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` });
4096
5343
  }
4097
5344
  const cells = statement.cells.map((value) => {
4098
5345
  if (isStepColumnRef(value)) {
4099
5346
  if (!from.includes(value.step) && value.step !== source?.alias) {
4100
- 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` });
4101
5348
  }
4102
5349
  return `${dialect.quoteIdent({ name: value.step })}.${dialect.quoteIdent({ name: value.column })}`;
4103
5350
  }
@@ -4116,7 +5363,7 @@ function renderInsertSelect(params) {
4116
5363
  return text + applyReturning({ dialect, requested: statement.returning }).suffix;
4117
5364
  }
4118
5365
  // packages/sql/src/dialects/postgres-type-identity.ts
4119
- import { unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
5366
+ import { unsupportedCapability as unsupportedCapability28 } from "@vibeorm/schema";
4120
5367
  var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
4121
5368
  var MODIFIER_VALUE = `(?:[+-]?\\d+|${IDENTIFIER}|'(?:[^'\\x00]|'')*')`;
4122
5369
  var TYPE_NAME = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*(\\(\\s*${MODIFIER_VALUE}(?:\\s*,\\s*${MODIFIER_VALUE})*\\s*\\))?\\s*((?:\\[\\s*\\]\\s*)*)$`);
@@ -4143,13 +5390,13 @@ function parsePostgresTypeName(params) {
4143
5390
  }
4144
5391
  function renderPostgresTypeName(params) {
4145
5392
  if (params.dialect.name !== "postgres")
4146
- throw unsupportedCapability17({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
5393
+ throw unsupportedCapability28({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
4147
5394
  const { dialect, type } = params;
4148
5395
  return `${type.namespace === undefined ? "" : `${dialect.quoteIdent({ name: type.namespace })}.`}${dialect.quoteIdent({ name: type.name })}${type.modifier}${type.array ? "[]" : ""}`;
4149
5396
  }
4150
5397
  function renderPostgresTypeIdentity(params) {
4151
5398
  if (params.dialect.name !== "postgres")
4152
- throw unsupportedCapability17({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
5399
+ throw unsupportedCapability28({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
4153
5400
  const collector = new ParamCollector(params.dialect);
4154
5401
  const { source } = params;
4155
5402
  let text;
@@ -4162,11 +5409,11 @@ function renderPostgresTypeIdentity(params) {
4162
5409
  return { text, values: collector.values, returning: "none" };
4163
5410
  }
4164
5411
  // packages/sql/src/dialects/postgres-default-identity.ts
4165
- import { unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
5412
+ import { unsupportedCapability as unsupportedCapability29 } from "@vibeorm/schema";
4166
5413
  function renderPostgresDefaultFunctionIdentity(params) {
4167
5414
  const { dialect, table, column } = params;
4168
5415
  if (dialect.name !== "postgres")
4169
- throw unsupportedCapability18({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
5416
+ throw unsupportedCapability29({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
4170
5417
  const collector = new ParamCollector(dialect);
4171
5418
  const namespace = collector.add(table.namespace);
4172
5419
  const name = collector.add(table.name);
@@ -4190,36 +5437,36 @@ WHERE n.nspname = ${namespace} AND c.relname = ${name} AND a.attname = ${field}
4190
5437
  };
4191
5438
  }
4192
5439
  // packages/sql/src/dialects/constraint-owned-index.ts
4193
- import { unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
5440
+ import { unsupportedCapability as unsupportedCapability30 } from "@vibeorm/schema";
4194
5441
  function renderConstraintOwnedIndexDrop(params) {
4195
5442
  if (params.dialect.name !== "postgres")
4196
- throw unsupportedCapability19({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
5443
+ throw unsupportedCapability30({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
4197
5444
  const quote = (name) => params.dialect.quoteIdent({ name });
4198
5445
  return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
4199
5446
  }
4200
5447
  // packages/sql/src/dialects/mysql-check-probe.ts
4201
- import { unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
5448
+ import { unsupportedCapability as unsupportedCapability31 } from "@vibeorm/schema";
4202
5449
  function renderMysqlCheckProbe(params) {
4203
5450
  if (params.dialect.name !== "mysql")
4204
- throw unsupportedCapability20({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
5451
+ throw unsupportedCapability31({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
4205
5452
  const q = (name) => params.dialect.quoteIdent({ name });
4206
5453
  const probe = q(params.probe);
4207
5454
  switch (params.operation) {
4208
5455
  case "clone":
4209
5456
  if (params.table === undefined)
4210
- 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" });
4211
5458
  return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
4212
5459
  case "add": {
4213
5460
  if (params.expression === undefined || params.definition === undefined)
4214
- throw unsupportedCapability20({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
4215
- 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 });
4216
5463
  return { text: `CREATE TEMPORARY TABLE ${probe} (
4217
5464
  ${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
4218
5465
  `)}
4219
5466
  )${parts.suffix}`, values: [], returning: "none" };
4220
5467
  }
4221
5468
  case "read":
4222
- 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" };
4223
5470
  case "drop":
4224
5471
  return { text: `DROP TEMPORARY TABLE ${probe}`, values: [], returning: "none" };
4225
5472
  }
@@ -4257,31 +5504,31 @@ function sqliteTableConstraints(params) {
4257
5504
  return result;
4258
5505
  }
4259
5506
  // packages/sql/src/dialects/migration-visibility.ts
4260
- import { unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
5507
+ import { unsupportedCapability as unsupportedCapability32 } from "@vibeorm/schema";
4261
5508
  function renderMigrationVisibility(params) {
4262
5509
  if (params.dialect.name !== "mysql")
4263
- throw unsupportedCapability21({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
5510
+ throw unsupportedCapability32({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
4264
5511
  const collector = new ParamCollector(params.dialect);
4265
5512
  const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
4266
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" };
4267
5514
  }
4268
5515
  // packages/sql/src/dialects/migration-inventory.ts
4269
- import { unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
5516
+ import { unsupportedCapability as unsupportedCapability33 } from "@vibeorm/schema";
4270
5517
  function renderSqliteColumnInventory(params) {
4271
5518
  if (params.dialect.name !== "sqlite")
4272
- throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
5519
+ throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
4273
5520
  const collector = new ParamCollector(params.dialect);
4274
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" };
4275
5522
  }
4276
5523
  function renderSqliteForeignKeyInventory(params) {
4277
5524
  if (params.dialect.name !== "sqlite")
4278
- throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
5525
+ throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
4279
5526
  const collector = new ParamCollector(params.dialect);
4280
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" };
4281
5528
  }
4282
5529
  function renderSqliteRebuildObjects(params) {
4283
5530
  if (params.dialect.name !== "sqlite")
4284
- throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
5531
+ throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
4285
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" };
4286
5533
  }
4287
5534
  function renderMigrationInventory(params) {
@@ -4320,7 +5567,7 @@ ORDER BY kind, parent, name`, values: collector.values, returning: "none" };
4320
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" };
4321
5568
  }
4322
5569
  // packages/sql/src/dialects/migration-objects.ts
4323
- import { internalError as internalError3, unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
5570
+ import { internalError as internalError4, unsupportedCapability as unsupportedCapability34 } from "@vibeorm/schema";
4324
5571
  function renderMigrationObjectQuery(params) {
4325
5572
  return objectQuery({ ...params, collector: new ParamCollector(params.dialect) });
4326
5573
  }
@@ -4328,7 +5575,7 @@ function objectQuery(params) {
4328
5575
  const { dialect, identity: object, collector } = params;
4329
5576
  if (object.kind === "namespace") {
4330
5577
  if (dialect.name !== "postgres")
4331
- throw unsupportedCapability23({ dialect: dialect.name, feature: "fixedNamespaces" });
5578
+ throw unsupportedCapability34({ dialect: dialect.name, feature: "fixedNamespaces" });
4332
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" };
4333
5580
  }
4334
5581
  const physical = "table" in object ? object.table : object.name;
@@ -4392,7 +5639,7 @@ function objectQuery(params) {
4392
5639
  break;
4393
5640
  case "constraint":
4394
5641
  if (object.constraintType === "exclusion")
4395
- throw unsupportedCapability23({ dialect: "mysql", feature: "exclusionConstraints" });
5642
+ throw unsupportedCapability34({ dialect: "mysql", feature: "exclusionConstraints" });
4396
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)}`;
4397
5644
  break;
4398
5645
  case "index":
@@ -4402,22 +5649,22 @@ function objectQuery(params) {
4402
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}`;
4403
5650
  break;
4404
5651
  default:
4405
- throw unsupportedCapability23({ dialect: "mysql", feature: `inspect:${object.kind}` });
5652
+ throw unsupportedCapability34({ dialect: "mysql", feature: `inspect:${object.kind}` });
4406
5653
  }
4407
5654
  } else {
4408
5655
  if (physical.namespace !== "main" && physical.namespace !== "temp")
4409
- throw unsupportedCapability23({ dialect: "sqlite", feature: "attachedSchemaInspection" });
5656
+ throw unsupportedCapability34({ dialect: "sqlite", feature: "attachedSchemaInspection" });
4410
5657
  const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
4411
5658
  const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
4412
5659
  if (["function", "sequence", "postgresExtension", "enum"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
4413
- throw unsupportedCapability23({ dialect: "sqlite", feature: `inspect:${object.kind}` });
5660
+ throw unsupportedCapability34({ dialect: "sqlite", feature: `inspect:${object.kind}` });
4414
5661
  const sqliteParams = new ParamCollector(dialect);
4415
5662
  if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
4416
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)}`;
4417
5664
  return { text, values: sqliteParams.values, returning: "none" };
4418
5665
  }
4419
5666
  const objectName = object.kind === "trigger" || object.kind === "index" ? object.name : physical.name;
4420
- 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)}` : ""}`;
4421
5668
  return { text, values: sqliteParams.values, returning: "none" };
4422
5669
  }
4423
5670
  return { text, values: collector.values, returning: "none" };
@@ -4440,7 +5687,7 @@ function renderMigrationDependencies(params) {
4440
5687
  }
4441
5688
  function dependencyQuery(params) {
4442
5689
  if (params.dialect.name !== "postgres")
4443
- throw unsupportedCapability23({ dialect: params.dialect.name, feature: "catalogDependencies" });
5690
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "catalogDependencies" });
4444
5691
  const object = params.identity;
4445
5692
  const collector = params.collector;
4446
5693
  if (object.kind === "namespace") {
@@ -4497,7 +5744,7 @@ function inspectionReferences(params) {
4497
5744
  function renderMigrationObjectBatch(params) {
4498
5745
  const { dialect } = params;
4499
5746
  if (dialect.name !== "postgres")
4500
- throw unsupportedCapability23({ dialect: dialect.name, feature: "batchedMigrationInspection" });
5747
+ throw unsupportedCapability34({ dialect: dialect.name, feature: "batchedMigrationInspection" });
4501
5748
  const groups = new Map;
4502
5749
  for (const [position, identity] of params.identities.entries()) {
4503
5750
  const kind = identity.kind === "constraint" ? `${identity.kind}:${identity.constraintType}` : identity.kind === "generatedColumn" ? "columnDefault" : identity.kind;
@@ -4510,7 +5757,7 @@ function renderMigrationObjectBatch(params) {
4510
5757
  const facts = objectQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
4511
5758
  const dependencies = dependencyQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
4512
5759
  if (JSON.stringify(dependencies.values) !== JSON.stringify(facts.values))
4513
- 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 } });
4514
5761
  const columns = facts.values.map((value, index) => `${dialect.quoteIdent({ name: `p${index + 1}` })} ${Array.isArray(value) ? "text[]" : "text"}`);
4515
5762
  const requests = group.map(({ identity, position }) => ({ position, ...Object.fromEntries(renderMigrationObjectQuery({ dialect, identity }).values.map((value, index) => [`p${index + 1}`, value])) }));
4516
5763
  const collector = new ParamCollector(dialect);
@@ -4530,16 +5777,16 @@ function renderMigrationNamespace(params) {
4530
5777
  }
4531
5778
  function renderCheckProbe(params) {
4532
5779
  if (params.dialect.name !== "postgres")
4533
- throw unsupportedCapability23({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
5780
+ throw unsupportedCapability34({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
4534
5781
  const q = (name) => params.dialect.quoteIdent({ name });
4535
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" };
4536
5783
  }
4537
5784
  // packages/sql/src/dialects/postgres-migration.ts
4538
- import { VibeError as VibeError15, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
5785
+ import { VibeError as VibeError23, unsupportedCapability as unsupportedCapability35 } from "@vibeorm/schema";
4539
5786
  var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
4540
5787
  function requirePostgres2(params) {
4541
5788
  if (params.dialect.name !== "postgres") {
4542
- throw unsupportedCapability24({ dialect: params.dialect.name, feature: params.feature });
5789
+ throw unsupportedCapability35({ dialect: params.dialect.name, feature: params.feature });
4543
5790
  }
4544
5791
  }
4545
5792
  function renderMigrationSessionRead(params) {
@@ -4554,7 +5801,7 @@ function renderMigrationSessionSet(params) {
4554
5801
  requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
4555
5802
  const { milliseconds, setting } = params;
4556
5803
  if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
4557
- throw new VibeError15({
5804
+ throw new VibeError23({
4558
5805
  code: "VIBE_MIGRATION",
4559
5806
  message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
4560
5807
  meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
@@ -4672,6 +5919,7 @@ export {
4672
5919
  SQLITE_SESSION_SQL,
4673
5920
  SQL_DEFAULT_CELL,
4674
5921
  SQL_DIALECTS,
5922
+ assertIndexExpressionVersion,
4675
5923
  assertSqlParamBuilding,
4676
5924
  chainVetoToken,
4677
5925
  cloneTimestampDate,
@@ -4699,6 +5947,9 @@ export {
4699
5947
  escapeGlobPattern,
4700
5948
  escapeLikePattern,
4701
5949
  excludedRef,
5950
+ extractIndexKeys,
5951
+ extractMysqlGeneratedColumn,
5952
+ extractSqliteGeneratedColumn,
4702
5953
  float32Codec,
4703
5954
  getSqlDialect,
4704
5955
  isDbNowMarker,
@@ -4720,6 +5971,8 @@ export {
4720
5971
  isStepColumnRef,
4721
5972
  jsonListParameter,
4722
5973
  jsonParamCell,
5974
+ keyListBindsUntypedRows,
5975
+ localDateCodec,
4723
5976
  mapConflictColumns,
4724
5977
  mysqlDialect,
4725
5978
  nativeEnumArrayParameter,
@@ -4729,7 +5982,9 @@ export {
4729
5982
  parsePgArrayLiteralNested,
4730
5983
  parsePostgresTypeName,
4731
5984
  postgresArrayElementFromText,
5985
+ postgresBulkInsertArrayCastOf,
4732
5986
  postgresDialect,
5987
+ postgresKeyListElementTypeOf,
4733
5988
  projectionOutputName,
4734
5989
  recordSqlBinding,
4735
5990
  recordSqlBindingCheck,
@@ -4748,8 +6003,12 @@ export {
4748
6003
  renderExternalDependents,
4749
6004
  renderExternalOwnershipCatalog,
4750
6005
  renderFragment,
6006
+ renderGeneratedColumnClause,
4751
6007
  renderIndexColumns,
6008
+ renderIndexExpressionProbe,
6009
+ renderIndexKeys,
4752
6010
  renderIndexStateQuery,
6011
+ renderLocalDateCheck,
4753
6012
  renderMigrationDependencies,
4754
6013
  renderMigrationHistoryQuery,
4755
6014
  renderMigrationInventory,
@@ -4764,10 +6023,15 @@ export {
4764
6023
  renderModuleDependencyQuery,
4765
6024
  renderModuleJournal,
4766
6025
  renderMysqlCheckProbe,
6026
+ renderMysqlGeneratedColumnProbe,
4767
6027
  renderMysqlObjectMembers,
4768
6028
  renderPostgresDefaultFunctionIdentity,
6029
+ renderPostgresDropGeneratedExpression,
4769
6030
  renderPostgresExplain,
6031
+ renderPostgresGeneratedColumnProbe,
4770
6032
  renderPostgresIndexKeys,
6033
+ renderPostgresJitRestore,
6034
+ renderPostgresJitSuspend,
4771
6035
  renderPostgresPhysicalCatalog,
4772
6036
  renderPostgresTypeIdentity,
4773
6037
  renderPostgresTypeName,
@@ -4790,6 +6054,8 @@ export {
4790
6054
  renderRlsReadinessQuery,
4791
6055
  renderRlsRole,
4792
6056
  renderRowLockClause,
6057
+ renderRowRule,
6058
+ renderSchemaExpression,
4793
6059
  renderShadowOperation,
4794
6060
  renderSqlFragment,
4795
6061
  renderSqliteColumnInventory,
@@ -4797,6 +6063,7 @@ export {
4797
6063
  renderSqliteFtsUpdateTrigger,
4798
6064
  renderSqliteRebuildObjects,
4799
6065
  renderStatement,
6066
+ resolveIndexKeys,
4800
6067
  resultScalarContract,
4801
6068
  returningColumnName,
4802
6069
  sameResultDecoder,
@@ -4813,6 +6080,7 @@ export {
4813
6080
  sqliteTableConstraints,
4814
6081
  startsWithPattern,
4815
6082
  stepColumnRef,
6083
+ strictMatchCollationPrefilter,
4816
6084
  strictMatchPattern,
4817
6085
  tableDefinitionParts,
4818
6086
  timeOfDayCodec,
@@ -4821,4 +6089,4 @@ export {
4821
6089
  toPgArrayLiteral
4822
6090
  };
4823
6091
 
4824
- //# debugId=71705578A45D011664756E2164756E21
6092
+ //# debugId=75BF4FE272330AE564756E2164756E21