@vibeorm/sql 2.5.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ast.d.ts +331 -11
  3. package/dist/codecs.d.ts +48 -0
  4. package/dist/dialect.d.ts +44 -0
  5. package/dist/dialects/postgres-index-keys.d.ts +1 -0
  6. package/dist/dialects/postgres-physical-catalog.d.ts +7 -1
  7. package/dist/dialects/sqlite-fts.d.ts +28 -0
  8. package/dist/index.d.ts +9 -5
  9. package/dist/index.js +757 -193
  10. package/dist/index.js.map +19 -17
  11. package/dist/json-list-parameter.d.ts +37 -0
  12. package/dist/pg-array.d.ts +13 -0
  13. package/dist/render.d.ts +11 -1
  14. package/package.json +5 -3
  15. package/dist/ast.d.ts.map +0 -1
  16. package/dist/capabilities.d.ts.map +0 -1
  17. package/dist/codecs.d.ts.map +0 -1
  18. package/dist/composable.d.ts.map +0 -1
  19. package/dist/dialect.d.ts.map +0 -1
  20. package/dist/dialects/constraint-owned-index.d.ts.map +0 -1
  21. package/dist/dialects/diagnostics.d.ts.map +0 -1
  22. package/dist/dialects/external-dependents.d.ts.map +0 -1
  23. package/dist/dialects/external-ownership.d.ts.map +0 -1
  24. package/dist/dialects/migration-history.d.ts.map +0 -1
  25. package/dist/dialects/migration-inventory.d.ts.map +0 -1
  26. package/dist/dialects/migration-objects.d.ts.map +0 -1
  27. package/dist/dialects/migration-replay-coverage.d.ts.map +0 -1
  28. package/dist/dialects/migration-shadow.d.ts.map +0 -1
  29. package/dist/dialects/migration-visibility.d.ts.map +0 -1
  30. package/dist/dialects/module-dependency.d.ts.map +0 -1
  31. package/dist/dialects/mysql-check-probe.d.ts.map +0 -1
  32. package/dist/dialects/mysql.d.ts.map +0 -1
  33. package/dist/dialects/postgres-default-identity.d.ts.map +0 -1
  34. package/dist/dialects/postgres-diagnostics.d.ts.map +0 -1
  35. package/dist/dialects/postgres-index-keys.d.ts.map +0 -1
  36. package/dist/dialects/postgres-migration.d.ts.map +0 -1
  37. package/dist/dialects/postgres-physical-catalog.d.ts.map +0 -1
  38. package/dist/dialects/postgres-rls-readiness.d.ts.map +0 -1
  39. package/dist/dialects/postgres-rls.d.ts.map +0 -1
  40. package/dist/dialects/postgres-type-identity.d.ts.map +0 -1
  41. package/dist/dialects/postgres.d.ts.map +0 -1
  42. package/dist/dialects/registry.d.ts.map +0 -1
  43. package/dist/dialects/sqlite-constraints.d.ts.map +0 -1
  44. package/dist/dialects/sqlite-session.d.ts.map +0 -1
  45. package/dist/dialects/sqlite-table-definition.d.ts.map +0 -1
  46. package/dist/dialects/sqlite.d.ts.map +0 -1
  47. package/dist/exclusion.d.ts.map +0 -1
  48. package/dist/identifiers.d.ts.map +0 -1
  49. package/dist/index-columns.d.ts.map +0 -1
  50. package/dist/index.d.ts.map +0 -1
  51. package/dist/json-transport.d.ts.map +0 -1
  52. package/dist/like.d.ts.map +0 -1
  53. package/dist/locks.d.ts.map +0 -1
  54. package/dist/module-journal.d.ts.map +0 -1
  55. package/dist/native-enum-parameter.d.ts.map +0 -1
  56. package/dist/params.d.ts.map +0 -1
  57. package/dist/pg-array.d.ts.map +0 -1
  58. package/dist/predicate.d.ts.map +0 -1
  59. package/dist/render.d.ts.map +0 -1
  60. package/dist/result-decoder.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -7,6 +7,14 @@ function nativeEnumArrayParameter(params) {
7
7
  function isNativeEnumArrayParameter(value) {
8
8
  return typeof value === "object" && value !== null && NATIVE_ENUM_ARRAY_PARAMETER in value;
9
9
  }
10
+ // packages/sql/src/json-list-parameter.ts
11
+ var JSON_LIST_PARAMETER = Symbol("vibeorm.jsonListParameter");
12
+ function jsonListParameter(params) {
13
+ return { [JSON_LIST_PARAMETER]: true, value: params.value, storage: params.storage };
14
+ }
15
+ function isJsonListParameter(value) {
16
+ return typeof value === "object" && value !== null && JSON_LIST_PARAMETER in value;
17
+ }
10
18
  // packages/sql/src/dialects/external-ownership.ts
11
19
  import { unsupportedCapability } from "@vibeorm/schema";
12
20
  function renderExternalOwnershipCatalog(params) {
@@ -49,6 +57,13 @@ class ParamCollector {
49
57
  this.values.push(value.value);
50
58
  return `${this.dialect.placeholder({ index: this.values.length })}::${this.dialect.quoteIdent({ name: value.type.namespace })}.${this.dialect.quoteIdent({ name: value.type.name })}[]`;
51
59
  }
60
+ if (isJsonListParameter(value)) {
61
+ if (!this.dialect.capabilities.scalarArrays)
62
+ throw unsupportedCapability2({ dialect: this.dialect.name, feature: "scalarArrays" });
63
+ const whole = typeof value.value !== "string";
64
+ this.values.push(whole ? [...value.value] : value.value);
65
+ return `${this.dialect.placeholder({ index: this.values.length })}::text::${value.storage}${whole ? "[]" : ""}`;
66
+ }
52
67
  this.values.push(value);
53
68
  return this.dialect.placeholder({ index: this.values.length });
54
69
  }
@@ -131,7 +146,7 @@ function renderPostgresPhysicalCatalog(params) {
131
146
  text = `SELECT c.conname AS constraint_name, cl.relname AS table_name, att.attname AS column_name, fn.nspname AS foreign_namespace, fcl.relname AS foreign_table_name, fatt.attname AS foreign_column_name, c.confdeltype AS on_delete_char, c.confupdtype AS on_update_char, c.convalidated AS validated, c.condeferrable AS deferrable, (SELECT bool_and(t.tgenabled IN ('O', 'A')) FROM pg_catalog.pg_trigger t WHERE t.tgconstraint = c.oid) AS triggers_enabled 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 pg_catalog.pg_class fcl ON fcl.oid = c.confrelid JOIN pg_catalog.pg_namespace fn ON fn.oid = fcl.relnamespace JOIN unnest(c.conkey) WITH ORDINALITY AS lk(attnum, ord) ON true JOIN unnest(c.confkey) WITH ORDINALITY AS rk(attnum, ord) ON lk.ord = rk.ord JOIN pg_catalog.pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = lk.attnum JOIN pg_catalog.pg_attribute fatt ON fatt.attrelid = c.confrelid AND fatt.attnum = rk.attnum WHERE c.contype = 'f' AND n.nspname = ${namespace} ORDER BY cl.relname, c.conname, lk.ord`;
132
147
  break;
133
148
  case "enums":
134
- text = `SELECT t.typname AS type_name, n.nspname AS type_namespace, e.enumlabel AS enum_value, e.enumsortorder AS sort_order FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} ORDER BY t.typname, e.enumsortorder`;
149
+ text = `SELECT t.typname AS type_name, n.nspname AS type_namespace, e.enumlabel AS enum_value, e.enumsortorder AS sort_order FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_type'::regclass AND d.objid = t.oid AND d.deptype = 'e') ORDER BY t.typname, e.enumsortorder`;
135
150
  break;
136
151
  case "indexes":
137
152
  text = `SELECT tablename AS table_name, indexname AS index_name, indexdef AS index_def, con.conname AS backing_constraint, con.contype AS backing_constraint_type, health.indisvalid AS valid, health.indisready AS ready, health.indimmediate AS immediate, health.indexprs IS NULL AS plain_columns, health.indnkeyatts AS key_count, health.indnatts AS attribute_count FROM pg_indexes ix JOIN pg_catalog.pg_namespace ns ON ns.nspname = ix.schemaname JOIN pg_catalog.pg_class idx ON idx.relnamespace = ns.oid AND idx.relname = ix.indexname JOIN pg_catalog.pg_index health ON health.indexrelid = idx.oid LEFT JOIN pg_catalog.pg_constraint con ON con.conindid = idx.oid AND con.contype IN ('p', 'u', 'x') WHERE schemaname = ${namespace} ORDER BY tablename, indexname`;
@@ -146,7 +161,7 @@ function renderPostgresIndexKeys(params) {
146
161
  if (dialect.name !== "postgres")
147
162
  throw unsupportedCapability5({ dialect: dialect.name, feature: "indexOperatorClasses" });
148
163
  const collector = new ParamCollector(dialect);
149
- const scope = params.table === undefined ? "n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'" : `n.nspname = ${collector.add(params.table.namespace)} AND c.relname = ${collector.add(params.table.name)}`;
164
+ 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(", ")})`;
150
165
  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`;
151
166
  return {
152
167
  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`,
@@ -334,6 +349,209 @@ function renderModuleJournal(params) {
334
349
  }
335
350
  return { text, values: collector.values };
336
351
  }
352
+ // packages/sql/src/capabilities.ts
353
+ import {
354
+ DIALECT_CAPABILITIES,
355
+ POSTGRES_CAPABILITIES,
356
+ SQLITE_CAPABILITIES,
357
+ MYSQL_CAPABILITIES
358
+ } from "@vibeorm/schema";
359
+
360
+ // packages/sql/src/identifiers.ts
361
+ import { VibeError as VibeError2 } from "@vibeorm/schema";
362
+ function assertNoNul(params) {
363
+ if (params.name.includes("\x00")) {
364
+ throw new VibeError2({
365
+ code: "VIBE_VALIDATION",
366
+ message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
367
+ meta: { name: params.name.replaceAll("\x00", "\\0") }
368
+ });
369
+ }
370
+ return params.name;
371
+ }
372
+
373
+ // packages/sql/src/dialects/sqlite.ts
374
+ var sqliteDialect = {
375
+ name: "sqlite",
376
+ capabilities: SQLITE_CAPABILITIES,
377
+ inArrayStrategy: "expand",
378
+ tupleInStrategy: "rowValue",
379
+ timeOfDayInStrategy: "in",
380
+ upsertForm: "onConflict",
381
+ conflictKeyEquality: {
382
+ foldingScalars: [],
383
+ textIsBinaryByDefault: true,
384
+ duplicatesInOneStatement: "lastWriteWins"
385
+ },
386
+ excludedRowRef: { form: "keyword", name: "excluded" },
387
+ doNothingForm: "onConflictDoNothing",
388
+ insensitiveLike: "lower",
389
+ strictMatchStrategy: "glob",
390
+ greatestForm: "frame",
391
+ orderByNullsMode: "native",
392
+ offsetRequiresLimit: { limitText: "-1" },
393
+ likeEscapeClause: " ESCAPE '\\'",
394
+ jsonPathMode: "sqliteExtract",
395
+ supportedJsonOps: [
396
+ "equals",
397
+ "string_contains",
398
+ "string_starts_with",
399
+ "string_ends_with",
400
+ "array_contains",
401
+ "array_starts_with",
402
+ "array_ends_with"
403
+ ],
404
+ jsonParamCast: "",
405
+ lastInsertIdSql: null,
406
+ rowLockSyntax: null,
407
+ advisoryLockFunctions: null,
408
+ rowDefaultKeyword: false,
409
+ statementChain: null,
410
+ paramBudget: 900,
411
+ booleanLiteral: { true: "1", false: "0" },
412
+ quoteIdent({ name }) {
413
+ return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
414
+ },
415
+ quoteStringLiteral({ value }) {
416
+ return `'${value.replaceAll("'", "''")}'`;
417
+ },
418
+ placeholder() {
419
+ return "?";
420
+ },
421
+ renderDbNow({ offsetSeconds, collector }) {
422
+ if (offsetSeconds === 0)
423
+ return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
424
+ return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
425
+ }
426
+ };
427
+
428
+ // packages/sql/src/dialects/sqlite-table-definition.ts
429
+ import { VibeError as VibeError3 } from "@vibeorm/schema";
430
+ function tableDefinitionParts(params) {
431
+ const sql = params.sql;
432
+ if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql))
433
+ throw unavailable();
434
+ const parts = [];
435
+ let start = -1;
436
+ let depth = 0;
437
+ let quote;
438
+ for (let index = 0;index < sql.length; index++) {
439
+ const char = sql[index];
440
+ if (quote !== undefined) {
441
+ if (char === quote) {
442
+ if (quote !== "]" && sql[index + 1] === quote)
443
+ index++;
444
+ else
445
+ quote = undefined;
446
+ }
447
+ continue;
448
+ }
449
+ if (char === "'" || char === '"' || char === "`") {
450
+ quote = char;
451
+ continue;
452
+ }
453
+ if (char === "[") {
454
+ quote = "]";
455
+ continue;
456
+ }
457
+ if (char === "-" && sql[index + 1] === "-") {
458
+ const end = sql.indexOf(`
459
+ `, index + 2);
460
+ if (end < 0)
461
+ break;
462
+ index = end;
463
+ continue;
464
+ }
465
+ if (char === "/" && sql[index + 1] === "*") {
466
+ const end = sql.indexOf("*/", index + 2);
467
+ if (end < 0)
468
+ break;
469
+ index = end + 1;
470
+ continue;
471
+ }
472
+ if (char === "(") {
473
+ if (depth === 0)
474
+ start = index + 1;
475
+ depth++;
476
+ } else if (char === ")") {
477
+ depth--;
478
+ if (depth === 0 && start >= 0) {
479
+ parts.push(sql.slice(start, index).trim());
480
+ return { clauses: parts, suffix: sql.slice(index + 1) };
481
+ }
482
+ } else if (char === "," && depth === 1) {
483
+ parts.push(sql.slice(start, index).trim());
484
+ start = index + 1;
485
+ }
486
+ }
487
+ throw unavailable();
488
+ }
489
+ function unavailable() {
490
+ return new VibeError3({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
491
+ }
492
+ function sqliteColumnClauses(params) {
493
+ const columns = [];
494
+ for (const part of tableDefinitionParts(params).clauses) {
495
+ const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
496
+ if (token === null)
497
+ throw unavailable();
498
+ if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
499
+ continue;
500
+ const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
501
+ columns.push({ name, sql: part });
502
+ }
503
+ return columns;
504
+ }
505
+
506
+ // packages/sql/src/dialects/sqlite-fts.ts
507
+ function updateStatements(params) {
508
+ const quote = (name) => sqliteDialect.quoteIdent({ name });
509
+ const columns = params.columns.map((column) => quote(column)).join(", ");
510
+ const oldValues = params.columns.map((column) => `old.${quote(column)}`).join(", ");
511
+ const newValues = params.columns.map((column) => `new.${quote(column)}`).join(", ");
512
+ const changes = [
513
+ "old.rowid IS NOT new.rowid",
514
+ ...params.columns.map((column) => `old.${quote(column)} COLLATE BINARY IS NOT new.${quote(column)} COLLATE BINARY`)
515
+ ].join(" OR ");
516
+ const target = `${quote(`${params.ftsTable}_au`)} AFTER UPDATE ON ${quote(params.table)}`;
517
+ const body = `BEGIN
518
+ INSERT INTO ${quote(params.ftsTable)}(${quote(params.ftsTable)}, rowid, ${columns}) VALUES ('delete', old.rowid, ${oldValues});
519
+ INSERT INTO ${quote(params.ftsTable)}(rowid, ${columns}) VALUES (new.rowid, ${newValues});
520
+ END`;
521
+ const tail = `${target} WHEN (${changes}) ${body}`;
522
+ return {
523
+ create: `CREATE TRIGGER IF NOT EXISTS ${tail};`,
524
+ definition: `CREATE TRIGGER ${tail}`,
525
+ legacyDefinition: `CREATE TRIGGER ${target} ${body}`
526
+ };
527
+ }
528
+ function renderSqliteFtsUpdateTrigger(params) {
529
+ const { create, definition } = updateStatements(params);
530
+ return { create, definition };
531
+ }
532
+ function sqliteFtsUpdateUpgrade(params) {
533
+ const quoted = '"(?:[^"]|"")+"';
534
+ const body = new RegExp(`BEGIN\\n INSERT INTO (${quoted})\\((${quoted}), rowid, (${quoted}(?:, ${quoted})*)\\) VALUES \\('delete', old\\.rowid, `, "g");
535
+ for (const match of params.create.matchAll(body)) {
536
+ const ftsTable = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[1]})` })[0].name;
537
+ const columns = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[3]})` }).map((column) => column.name);
538
+ if (params.name !== `${ftsTable}_au`)
539
+ continue;
540
+ const statements = updateStatements({ table: params.table, ftsTable, columns });
541
+ if (params.create !== statements.create)
542
+ continue;
543
+ const installed = params.installed === statements.legacyDefinition ? "legacy" : params.installed === statements.definition ? "current" : undefined;
544
+ if (installed === undefined)
545
+ continue;
546
+ if (params.tableDefinition === undefined)
547
+ return { kind: "tableRequired", ftsTable };
548
+ const tableTail = `${sqliteDialect.quoteIdent({ name: ftsTable })} USING fts5(${columns.map((name) => sqliteDialect.quoteIdent({ name })).join(", ")}, content=${sqliteDialect.quoteStringLiteral({ value: params.table })})`;
549
+ if (params.tableDefinition !== `CREATE VIRTUAL TABLE ${tableTail}` && params.tableDefinition !== `CREATE VIRTUAL TABLE IF NOT EXISTS ${tableTail};`)
550
+ continue;
551
+ return { kind: "recognized", ftsTable, legacyDefinition: statements.legacyDefinition, currentDefinition: statements.definition, drop: `DROP TRIGGER IF EXISTS ${sqliteDialect.quoteIdent({ name: `${ftsTable}_au` })};`, installed };
552
+ }
553
+ return;
554
+ }
337
555
  // packages/sql/src/dialects/sqlite-session.ts
338
556
  var SQLITE_SESSION_SQL = Object.freeze({
339
557
  lockingMode: "PRAGMA main.locking_mode",
@@ -346,10 +564,10 @@ var SQLITE_SESSION_SQL = Object.freeze({
346
564
  foreignKeysOn: "PRAGMA foreign_keys = ON"
347
565
  });
348
566
  // packages/sql/src/composable.ts
349
- import { VibeError as VibeError2 } from "@vibeorm/schema";
567
+ import { VibeError as VibeError4 } from "@vibeorm/schema";
350
568
  var fragments = new WeakMap;
351
569
  function invalid(message) {
352
- throw new VibeError2({ code: "VIBE_VALIDATION", message });
570
+ throw new VibeError4({ code: "VIBE_VALIDATION", message });
353
571
  }
354
572
  function construct(parts) {
355
573
  const snapshot = Object.freeze([...parts]);
@@ -365,6 +583,9 @@ function isSqlFragment(value) {
365
583
  function partsOf(value) {
366
584
  return fragments.get(value) ?? invalid("Expected a constructed SQL fragment");
367
585
  }
586
+ function containsGreatest(fragment) {
587
+ return partsOf(fragment).some((part) => typeof part !== "string" && ("greatest" in part));
588
+ }
368
589
  function identifier(parts) {
369
590
  if (!Array.isArray(parts) || parts.length === 0 || parts.some((part) => typeof part !== "string" || part.length === 0 || part.includes("\x00"))) {
370
591
  invalid("SQL identifiers require non-empty, NUL-free name components");
@@ -427,14 +648,25 @@ var sql = Object.freeze(Object.assign(template, {
427
648
  function renderSqlFragment(params) {
428
649
  const { dialect, collector, conflict } = params;
429
650
  const render = (fragment) => renderSqlFragment({ ...params, fragment });
651
+ const encode = (part) => {
652
+ if (part.value === undefined)
653
+ invalid("SQL parameters cannot be undefined; use null explicitly");
654
+ return part.decoder === undefined ? part.value : part.decoder.encode({ value: part.value, dialect: dialect.name });
655
+ };
656
+ const frameOperand = (fragment) => {
657
+ const parts = partsOf(fragment).filter((part) => part !== "");
658
+ const only = parts.length === 1 ? parts[0] : undefined;
659
+ const value = only !== undefined && typeof only !== "string" && "value" in only ? { encoded: encode(only) } : undefined;
660
+ if (value !== undefined && value.encoded === null)
661
+ return "NULL";
662
+ const text = value === undefined ? render(fragment) : collector.add(value.encoded);
663
+ return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
664
+ };
430
665
  return partsOf(params.fragment).map((part) => {
431
666
  if (typeof part === "string")
432
667
  return part;
433
- if ("value" in part) {
434
- if (part.value === undefined)
435
- invalid("SQL parameters cannot be undefined; use null explicitly");
436
- return collector.add(part.decoder === undefined ? part.value : part.decoder.encode({ value: part.value, dialect: dialect.name }));
437
- }
668
+ if ("value" in part)
669
+ return collector.add(encode(part));
438
670
  if ("ident" in part)
439
671
  return part.ident.map((name) => dialect.quoteIdent({ name })).join(".");
440
672
  if ("row" in part) {
@@ -447,7 +679,14 @@ function renderSqlFragment(params) {
447
679
  const qualifier = part.row === "existing" ? dialect.quoteIdent({ name: conflict.table }) : ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
448
680
  return `${qualifier}.${dialect.quoteIdent({ name })}`;
449
681
  }
450
- return render(part.greatest.reduce((left, right) => sql`(CASE WHEN ${left} IS NULL THEN ${right} WHEN ${right} IS NULL THEN ${left} WHEN ${left} >= ${right} THEN ${left} ELSE ${right} END)`));
682
+ if (dialect.greatestForm === "native")
683
+ return `GREATEST(${part.greatest.map(render).join(", ")})`;
684
+ if (!part.greatest.some(containsGreatest)) {
685
+ return render(part.greatest.reduce((left, right) => sql`(CASE WHEN ${left} IS NULL THEN ${right} WHEN ${right} IS NULL THEN ${left} WHEN ${left} >= ${right} THEN ${left} ELSE ${right} END)`));
686
+ }
687
+ const [left, right] = part.greatest.map(frameOperand);
688
+ const [a, b, frame] = ["a", "b", "g"].map((name) => dialect.quoteIdent({ name }));
689
+ return `(SELECT CASE WHEN ${a} IS NULL THEN ${b} WHEN ${b} IS NULL THEN ${a} WHEN ${a} >= ${b} THEN ${a} ELSE ${b} END FROM (SELECT ${left} AS ${a}, ${right} AS ${b}) AS ${frame})`;
451
690
  }).join("");
452
691
  }
453
692
  function mapConflictColumns(params) {
@@ -474,7 +713,7 @@ function sqlUsesIncoming(params) {
474
713
  return partsOf(params.fragment).some((part) => typeof part !== "string" && (("row" in part) && part.row === "incoming" || ("greatest" in part) && part.greatest.some((fragment) => sqlUsesIncoming({ fragment }))));
475
714
  }
476
715
  // packages/sql/src/result-decoder.ts
477
- import { VibeError as VibeError4 } from "@vibeorm/schema";
716
+ import { VibeError as VibeError6 } from "@vibeorm/schema";
478
717
 
479
718
  // packages/sql/src/pg-array.ts
480
719
  var PG_ARRAY_QUOTE = /^$|^NULL$|[{},"\\\s]/i;
@@ -489,7 +728,7 @@ function toPgArrayLiteral(params) {
489
728
  return `{${parts.join(",")}}`;
490
729
  }
491
730
  function parsePgArrayLiteral(params) {
492
- const { literal } = params;
731
+ const literal = params.literal.startsWith("[") ? params.literal.replace(/^\[-?\d+:-?\d+\]=/, "") : params.literal;
493
732
  if (literal.length < 2 || !literal.startsWith("{") || !literal.endsWith("}"))
494
733
  return null;
495
734
  const inner = literal.slice(1, -1);
@@ -527,9 +766,63 @@ function parsePgArrayLiteral(params) {
527
766
  }
528
767
  return out;
529
768
  }
769
+ function parsePgArrayLiteralNested(params) {
770
+ const text = params.literal.replace(/^(?:\[-?\d+:-?\d+\])+=/, "");
771
+ const cursor = { index: 0 };
772
+ const parsed = parseDimension({ text, cursor });
773
+ return parsed !== null && cursor.index === text.length ? parsed : null;
774
+ }
775
+ function parseDimension(params) {
776
+ const { text, cursor } = params;
777
+ if (text[cursor.index] !== "{")
778
+ return null;
779
+ cursor.index++;
780
+ const out = [];
781
+ if (text[cursor.index] === "}") {
782
+ cursor.index++;
783
+ return out;
784
+ }
785
+ while (cursor.index < text.length) {
786
+ if (text[cursor.index] === "{") {
787
+ const nested = parseDimension({ text, cursor });
788
+ if (nested === null)
789
+ return null;
790
+ out.push(nested);
791
+ } else if (text[cursor.index] === '"') {
792
+ let element = "";
793
+ cursor.index++;
794
+ while (cursor.index < text.length && text[cursor.index] !== '"') {
795
+ if (text[cursor.index] === "\\")
796
+ cursor.index++;
797
+ element += text[cursor.index] ?? "";
798
+ cursor.index++;
799
+ }
800
+ if (text[cursor.index] !== '"')
801
+ return null;
802
+ cursor.index++;
803
+ out.push(element);
804
+ } else {
805
+ let element = "";
806
+ while (cursor.index < text.length && text[cursor.index] !== "," && text[cursor.index] !== "}") {
807
+ element += text[cursor.index];
808
+ cursor.index++;
809
+ }
810
+ out.push(element === "NULL" ? null : element);
811
+ }
812
+ if (text[cursor.index] === ",") {
813
+ cursor.index++;
814
+ continue;
815
+ }
816
+ if (text[cursor.index] !== "}")
817
+ return null;
818
+ cursor.index++;
819
+ return out;
820
+ }
821
+ return null;
822
+ }
530
823
 
531
824
  // packages/sql/src/codecs.ts
532
- import { VibeError as VibeError3, invalidStoredData } from "@vibeorm/schema";
825
+ import { VibeError as VibeError5, invalidStoredData, timeOfDayType } from "@vibeorm/schema";
533
826
  function enumMembershipCodec(params) {
534
827
  const { model, field, enum: enumName } = params;
535
828
  const members = new Set(params.members);
@@ -537,7 +830,7 @@ function enumMembershipCodec(params) {
537
830
  encode: (value) => {
538
831
  if (typeof value === "string" && members.has(value))
539
832
  return value;
540
- throw new VibeError3({
833
+ throw new VibeError5({
541
834
  code: "VIBE_VALIDATION",
542
835
  message: `Invalid value for enum ${enumName} on ${model}.${field}`,
543
836
  meta: { model, field, enum: enumName }
@@ -577,7 +870,7 @@ var bigIntCodec = {
577
870
  return BigInt(value.trim());
578
871
  if (typeof value === "number" && Number.isSafeInteger(value))
579
872
  return BigInt(value);
580
- throw new VibeError3({ code: "VIBE_DATA_INVALID", message: "BigInt requires an integer string or a safe integer", meta: { value } });
873
+ throw new VibeError5({ code: "VIBE_DATA_INVALID", message: "BigInt requires an integer string or a safe integer", meta: { value } });
581
874
  }
582
875
  };
583
876
  var decimalCodec = {
@@ -594,6 +887,7 @@ var mysqlDecimalCodec = {
594
887
  }
595
888
  };
596
889
  var ZONE_LESS_DATETIME = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
890
+ var PG_ZONED_DATETIME = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?)(\.\d{1,9})?([+-]\d{2})(?::?(\d{2}))?$/;
597
891
  function parseDateTimeText(params) {
598
892
  const trimmed = params.text.trim();
599
893
  if (ZONE_LESS_DATETIME.test(trimmed)) {
@@ -601,13 +895,20 @@ function parseDateTimeText(params) {
601
895
  if (!Number.isNaN(asUtc.getTime()))
602
896
  return asUtc;
603
897
  }
898
+ const zoned = PG_ZONED_DATETIME.exec(trimmed);
899
+ if (zoned !== null) {
900
+ const [, date, time, fraction, hours, minutes] = zoned;
901
+ const iso = new Date(`${date}T${time}${(fraction ?? "").slice(0, 4)}${hours}:${minutes ?? "00"}`);
902
+ if (!Number.isNaN(iso.getTime()))
903
+ return iso;
904
+ }
604
905
  const parsed = new Date(trimmed);
605
906
  return Number.isNaN(parsed.getTime()) ? undefined : parsed;
606
907
  }
607
908
  var dateNativeCodec = {
608
909
  encode: (value) => {
609
910
  if (value instanceof Date && !Number.isFinite(value.getTime()) || typeof value === "number" && !Number.isFinite(value) || typeof value === "string" && /^[+-]?infinity$/i.test(value.trim())) {
610
- throw new VibeError3({ code: "VIBE_VALIDATION", message: "DateTime parameters require finite timestamps; use an extended timestamp encoder for infinity" });
911
+ throw new VibeError5({ code: "VIBE_VALIDATION", message: "DateTime parameters require finite timestamps; use an extended timestamp encoder for infinity" });
611
912
  }
612
913
  return value;
613
914
  },
@@ -615,7 +916,7 @@ var dateNativeCodec = {
615
916
  const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
616
917
  if (date !== undefined && Number.isFinite(date.getTime()))
617
918
  return date;
618
- throw new VibeError3({ code: "VIBE_DATA_INVALID", message: "DateTime requires a finite timestamp; use an explicit extended timestamp decoder for infinity" });
919
+ throw new VibeError5({ code: "VIBE_DATA_INVALID", message: "DateTime requires a finite timestamp; use an explicit extended timestamp decoder for infinity" });
619
920
  }
620
921
  };
621
922
  var dateTextCodec = {
@@ -624,7 +925,7 @@ var dateTextCodec = {
624
925
  const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
625
926
  if (date !== undefined && Number.isFinite(date.getTime()))
626
927
  return date.toISOString();
627
- throw new VibeError3({ code: "VIBE_VALIDATION", message: "DateTime parameters require a valid finite timestamp" });
928
+ throw new VibeError5({ code: "VIBE_VALIDATION", message: "DateTime parameters require a valid finite timestamp" });
628
929
  },
629
930
  decode: dateNativeCodec.decode
630
931
  };
@@ -645,6 +946,44 @@ var mysqlDateCodec = {
645
946
  },
646
947
  decode: dateNativeCodec.decode
647
948
  };
949
+ var MILLISECONDS_PER_DAY = 86400000;
950
+ var TIME_OF_DAY_TEXT = /^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(?:([+-])(\d{2})(?::?(\d{2}))?(?::?(\d{2}))?)?$/;
951
+ var pad = (value, width) => String(value).padStart(width, "0");
952
+ var utcMillisecondOfDay = (date) => (date.getTime() % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY;
953
+ var timeOfDayText = (millisecond) => `${pad(Math.floor(millisecond / 3600000), 2)}:${pad(Math.floor(millisecond / 60000) % 60, 2)}:${pad(Math.floor(millisecond / 1000) % 60, 2)}.${pad(millisecond % 1000, 3)}`;
954
+ function makeTimeOfDayCodec(params) {
955
+ const suffix = params.zoned ? "+00" : "";
956
+ return Object.freeze({
957
+ encode: (value) => {
958
+ dateNativeCodec.encode(value);
959
+ const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
960
+ if (date === undefined || !Number.isFinite(date.getTime())) {
961
+ throw new VibeError5({ code: "VIBE_VALIDATION", message: "a time-of-day DateTime parameter requires a valid finite Date", meta: { value } });
962
+ }
963
+ return `${timeOfDayText(utcMillisecondOfDay(date))}${suffix}`;
964
+ },
965
+ decode: (value) => {
966
+ if (value instanceof Date && Number.isFinite(value.getTime()))
967
+ return new Date(utcMillisecondOfDay(value));
968
+ const match = typeof value === "string" ? TIME_OF_DAY_TEXT.exec(value.trim()) : null;
969
+ if (match !== null) {
970
+ const [, hours, minutes, seconds, fraction, sign, offsetHours, offsetMinutes, offsetSeconds] = match;
971
+ const millisecond = Number(hours) * 3600000 + Number(minutes) * 60000 + Number(seconds ?? 0) * 1000 + Number((fraction ?? "").slice(0, 3).padEnd(3, "0"));
972
+ const offset = sign === undefined ? 0 : (sign === "-" ? -1 : 1) * (Number(offsetHours) * 3600000 + Number(offsetMinutes ?? 0) * 60000 + Number(offsetSeconds ?? 0) * 1000);
973
+ if (millisecond <= MILLISECONDS_PER_DAY) {
974
+ return new Date(((millisecond - offset) % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY);
975
+ }
976
+ }
977
+ throw new VibeError5({ code: "VIBE_DATA_INVALID", message: "a time-of-day DateTime column returned a value that is not a time of day", meta: { value } });
978
+ }
979
+ });
980
+ }
981
+ var timeCodec = makeTimeOfDayCodec({ zoned: false });
982
+ var timetzCodec = makeTimeOfDayCodec({ zoned: true });
983
+ function timeOfDayCodec(params) {
984
+ const type = timeOfDayType({ nativeType: params.nativeType });
985
+ return type === "timetz" ? timetzCodec : type === "time" ? timeCodec : undefined;
986
+ }
648
987
  var booleanIntCodec = {
649
988
  encode: (value) => typeof value === "boolean" ? value ? 1 : 0 : value,
650
989
  decode: (value) => typeof value === "number" ? value !== 0 : value
@@ -661,7 +1000,7 @@ var jsonCodec = {
661
1000
  try {
662
1001
  return JSON.parse(value);
663
1002
  } catch (cause) {
664
- throw new VibeError3({
1003
+ throw new VibeError5({
665
1004
  code: "VIBE_INTERNAL",
666
1005
  message: "a Json column returned text that is not valid JSON",
667
1006
  meta: { value },
@@ -670,6 +1009,39 @@ var jsonCodec = {
670
1009
  }
671
1010
  }
672
1011
  };
1012
+ function shortestFloat32(params) {
1013
+ const { value } = params;
1014
+ if (value === 0 || !Number.isFinite(value))
1015
+ return value;
1016
+ const target = Math.fround(value);
1017
+ for (let precision = 1;precision <= 9; precision++) {
1018
+ const candidate = Number(value.toPrecision(precision));
1019
+ if (Math.fround(candidate) === target)
1020
+ return candidate;
1021
+ }
1022
+ return value;
1023
+ }
1024
+ var float32Codec = {
1025
+ encode: identity,
1026
+ decode: (value) => {
1027
+ const number = typeof value === "string" ? Number(value) : value;
1028
+ return typeof number === "number" ? shortestFloat32({ value: number }) : number;
1029
+ }
1030
+ };
1031
+ var booleanFromText = (text) => text === "t" ? true : text === "f" ? false : text;
1032
+ var bytesFromText = (text) => /^\\x(?:[0-9a-fA-F]{2})*$/.test(text) ? new Uint8Array(Buffer.from(text.slice(2), "hex")) : text;
1033
+ function postgresArrayElementFromText(params) {
1034
+ switch (params.scalarType) {
1035
+ case "Boolean":
1036
+ return booleanFromText;
1037
+ case "Bytes":
1038
+ return bytesFromText;
1039
+ case "Json":
1040
+ return jsonCodec.decode;
1041
+ default:
1042
+ return null;
1043
+ }
1044
+ }
673
1045
  var POSTGRES_CODECS = {
674
1046
  String: passthroughCodec,
675
1047
  Boolean: passthroughCodec,
@@ -711,7 +1083,7 @@ var DIALECT_CODECS = {
711
1083
 
712
1084
  // packages/sql/src/result-decoder.ts
713
1085
  function refuse(encoding, message) {
714
- throw new VibeError4({ code: encoding ? "VIBE_VALIDATION" : "VIBE_DATA_INVALID", message });
1086
+ throw new VibeError6({ code: encoding ? "VIBE_VALIDATION" : "VIBE_DATA_INVALID", message });
715
1087
  }
716
1088
  function scalar(type, valid) {
717
1089
  const convert = (params, encoding) => {
@@ -757,7 +1129,7 @@ var extendedDecoder = Object.freeze({
757
1129
  encode: (params) => {
758
1130
  if (params.value === "infinity" || params.value === "-infinity") {
759
1131
  if (params.dialect !== "postgres")
760
- throw new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Timestamp infinity parameters require PostgreSQL" });
1132
+ throw new VibeError6({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Timestamp infinity parameters require PostgreSQL" });
761
1133
  return params.value;
762
1134
  }
763
1135
  return dateDecoder.encode(params);
@@ -815,10 +1187,10 @@ var decode = Object.freeze({
815
1187
  }
816
1188
  });
817
1189
  // packages/sql/src/exclusion.ts
818
- import { VibeError as VibeError6, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
1190
+ import { VibeError as VibeError8, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
819
1191
 
820
1192
  // packages/sql/src/predicate.ts
821
- import { VibeError as VibeError5, fieldTypeIdentity, isEnumType, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
1193
+ import { VibeError as VibeError7, fieldTypeIdentity, isEnumType, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
822
1194
  function renderPredicate(params) {
823
1195
  if (params.usage === "exclusionPredicate" && !params.dialect.capabilities.exclusionConstraints)
824
1196
  throw unsupportedCapability8({ dialect: params.dialect.name, feature: "exclusionConstraints" });
@@ -858,7 +1230,7 @@ var INT64_MIN = -(2n ** 63n);
858
1230
  var INT64_MAX = 2n ** 63n - 1n;
859
1231
  function refuse2(params) {
860
1232
  const { context, rule, message } = params;
861
- throw new VibeError5({
1233
+ throw new VibeError7({
862
1234
  code: "VIBE_SCHEMA_INVALID",
863
1235
  message: `${context.site} on "${context.model.name}": ${message}`,
864
1236
  meta: { rule, model: context.model.name, site: context.site, ...params.meta ?? {} }
@@ -1122,7 +1494,7 @@ function describe(params) {
1122
1494
 
1123
1495
  // packages/sql/src/exclusion.ts
1124
1496
  function invalid2(rule) {
1125
- throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
1497
+ throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
1126
1498
  }
1127
1499
  function compileExclusion(params) {
1128
1500
  const { exclusion: spec, model, dialect } = params;
@@ -1245,7 +1617,7 @@ function isSqlDefaultCell(value) {
1245
1617
  return value === SQL_DEFAULT_CELL;
1246
1618
  }
1247
1619
  function jsonParamCell(params) {
1248
- return { __jsonParam: true, text: params.text };
1620
+ return params.storage === "json" ? { __jsonParam: true, text: params.text, storage: "json" } : { __jsonParam: true, text: params.text };
1249
1621
  }
1250
1622
  function isJsonParamCell(value) {
1251
1623
  return typeof value === "object" && value !== null && value.__jsonParam === true;
@@ -1256,7 +1628,30 @@ function excludedRef(params) {
1256
1628
  function isExcludedRef(value) {
1257
1629
  return typeof value === "object" && value !== null && value.__excluded === true;
1258
1630
  }
1631
+ function returningColumnName(column) {
1632
+ return typeof column === "string" ? column : column.name;
1633
+ }
1259
1634
  var ADVISORY_LOCK_RESULT_COLUMN = "acquired";
1635
+ function stepColumnRef(params) {
1636
+ return { __stepColumn: true, step: params.step, column: params.column };
1637
+ }
1638
+ function isStepColumnRef(value) {
1639
+ return typeof value === "object" && value !== null && value.__stepColumn === true;
1640
+ }
1641
+ var CHAIN_VETO_PREFIX = "__vibe_veto:";
1642
+ var CHAIN_VETO_TOKEN_PATTERN = /^[a-z][a-z0-9_]*$/;
1643
+ function chainVetoToken(params) {
1644
+ const { message, nonce } = params;
1645
+ if (nonce === "")
1646
+ return;
1647
+ for (let start = message.indexOf(CHAIN_VETO_PREFIX);start >= 0; start = message.indexOf(CHAIN_VETO_PREFIX, start + 1)) {
1648
+ const rest = message.slice(start + CHAIN_VETO_PREFIX.length);
1649
+ const match = /^([a-z][a-z0-9_]*):/.exec(rest);
1650
+ if (match !== null && rest.startsWith(`${nonce}:`, match[0].length))
1651
+ return match[1];
1652
+ }
1653
+ return;
1654
+ }
1260
1655
  // packages/sql/src/locks.ts
1261
1656
  import { internalError, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
1262
1657
  var ROW_LOCK_PROSE = Object.freeze({
@@ -1346,33 +1741,13 @@ function projectionOutputName(params) {
1346
1741
  const { column } = params;
1347
1742
  return column.transform === undefined ? column.alias ?? column.name : column.name;
1348
1743
  }
1349
- // packages/sql/src/capabilities.ts
1350
- import {
1351
- DIALECT_CAPABILITIES,
1352
- POSTGRES_CAPABILITIES,
1353
- SQLITE_CAPABILITIES,
1354
- MYSQL_CAPABILITIES
1355
- } from "@vibeorm/schema";
1356
-
1357
- // packages/sql/src/identifiers.ts
1358
- import { VibeError as VibeError7 } from "@vibeorm/schema";
1359
- function assertNoNul(params) {
1360
- if (params.name.includes("\x00")) {
1361
- throw new VibeError7({
1362
- code: "VIBE_VALIDATION",
1363
- message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
1364
- meta: { name: params.name.replaceAll("\x00", "\\0") }
1365
- });
1366
- }
1367
- return params.name;
1368
- }
1369
-
1370
1744
  // packages/sql/src/dialects/postgres.ts
1371
1745
  var postgresDialect = {
1372
1746
  name: "postgres",
1373
1747
  capabilities: POSTGRES_CAPABILITIES,
1374
1748
  inArrayStrategy: "any",
1375
1749
  tupleInStrategy: "rowValue",
1750
+ timeOfDayInStrategy: "in",
1376
1751
  upsertForm: "onConflict",
1377
1752
  conflictKeyEquality: {
1378
1753
  foldingScalars: ["Decimal"],
@@ -1383,6 +1758,7 @@ var postgresDialect = {
1383
1758
  doNothingForm: "onConflictDoNothing",
1384
1759
  insensitiveLike: "ilike",
1385
1760
  strictMatchStrategy: "native",
1761
+ greatestForm: "native",
1386
1762
  orderByNullsMode: "native",
1387
1763
  offsetRequiresLimit: null,
1388
1764
  likeEscapeClause: null,
@@ -1419,6 +1795,7 @@ var postgresDialect = {
1419
1795
  keyCast: Object.freeze({ single: "::bigint", pair: "::int4" })
1420
1796
  },
1421
1797
  rowDefaultKeyword: true,
1798
+ statementChain: "dataModifyingCte",
1422
1799
  paramBudget: 30000,
1423
1800
  booleanLiteral: { true: "true", false: "false" },
1424
1801
  quoteIdent({ name }) {
@@ -1436,63 +1813,13 @@ var postgresDialect = {
1436
1813
  return `(now() + make_interval(secs => ${collector.add(offsetSeconds)}))`;
1437
1814
  }
1438
1815
  };
1439
- // packages/sql/src/dialects/sqlite.ts
1440
- var sqliteDialect = {
1441
- name: "sqlite",
1442
- capabilities: SQLITE_CAPABILITIES,
1443
- inArrayStrategy: "expand",
1444
- tupleInStrategy: "rowValue",
1445
- upsertForm: "onConflict",
1446
- conflictKeyEquality: {
1447
- foldingScalars: [],
1448
- textIsBinaryByDefault: true,
1449
- duplicatesInOneStatement: "lastWriteWins"
1450
- },
1451
- excludedRowRef: { form: "keyword", name: "excluded" },
1452
- doNothingForm: "onConflictDoNothing",
1453
- insensitiveLike: "lower",
1454
- strictMatchStrategy: "glob",
1455
- orderByNullsMode: "native",
1456
- offsetRequiresLimit: { limitText: "-1" },
1457
- likeEscapeClause: " ESCAPE '\\'",
1458
- jsonPathMode: "sqliteExtract",
1459
- supportedJsonOps: [
1460
- "equals",
1461
- "string_contains",
1462
- "string_starts_with",
1463
- "string_ends_with",
1464
- "array_contains",
1465
- "array_starts_with",
1466
- "array_ends_with"
1467
- ],
1468
- jsonParamCast: "",
1469
- lastInsertIdSql: null,
1470
- rowLockSyntax: null,
1471
- advisoryLockFunctions: null,
1472
- rowDefaultKeyword: false,
1473
- paramBudget: 900,
1474
- booleanLiteral: { true: "1", false: "0" },
1475
- quoteIdent({ name }) {
1476
- return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
1477
- },
1478
- quoteStringLiteral({ value }) {
1479
- return `'${value.replaceAll("'", "''")}'`;
1480
- },
1481
- placeholder() {
1482
- return "?";
1483
- },
1484
- renderDbNow({ offsetSeconds, collector }) {
1485
- if (offsetSeconds === 0)
1486
- return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
1487
- return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
1488
- }
1489
- };
1490
1816
  // packages/sql/src/dialects/mysql.ts
1491
1817
  var mysqlDialect = {
1492
1818
  name: "mysql",
1493
1819
  capabilities: MYSQL_CAPABILITIES,
1494
1820
  inArrayStrategy: "expand",
1495
1821
  tupleInStrategy: "rowValue",
1822
+ timeOfDayInStrategy: "equalities",
1496
1823
  upsertForm: "onDuplicateKey",
1497
1824
  conflictKeyEquality: {
1498
1825
  foldingScalars: ["Decimal"],
@@ -1504,6 +1831,7 @@ var mysqlDialect = {
1504
1831
  doNothingForm: "insertIgnore",
1505
1832
  insensitiveLike: "lower",
1506
1833
  strictMatchStrategy: "binary",
1834
+ greatestForm: "wrapped-frame",
1507
1835
  orderByNullsMode: "emulate",
1508
1836
  offsetRequiresLimit: { limitText: "18446744073709551615" },
1509
1837
  likeEscapeClause: null,
@@ -1525,6 +1853,7 @@ var mysqlDialect = {
1525
1853
  },
1526
1854
  advisoryLockFunctions: null,
1527
1855
  rowDefaultKeyword: true,
1856
+ statementChain: null,
1528
1857
  paramBudget: 1e4,
1529
1858
  booleanLiteral: { true: "1", false: "0" },
1530
1859
  quoteIdent({ name }) {
@@ -1551,83 +1880,6 @@ var SQL_DIALECTS = {
1551
1880
  function getSqlDialect(params) {
1552
1881
  return SQL_DIALECTS[params.dialect];
1553
1882
  }
1554
- // packages/sql/src/dialects/sqlite-table-definition.ts
1555
- import { VibeError as VibeError8 } from "@vibeorm/schema";
1556
- function tableDefinitionParts(params) {
1557
- const sql = params.sql;
1558
- if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql))
1559
- throw unavailable();
1560
- const parts = [];
1561
- let start = -1;
1562
- let depth = 0;
1563
- let quote;
1564
- for (let index = 0;index < sql.length; index++) {
1565
- const char = sql[index];
1566
- if (quote !== undefined) {
1567
- if (char === quote) {
1568
- if (quote !== "]" && sql[index + 1] === quote)
1569
- index++;
1570
- else
1571
- quote = undefined;
1572
- }
1573
- continue;
1574
- }
1575
- if (char === "'" || char === '"' || char === "`") {
1576
- quote = char;
1577
- continue;
1578
- }
1579
- if (char === "[") {
1580
- quote = "]";
1581
- continue;
1582
- }
1583
- if (char === "-" && sql[index + 1] === "-") {
1584
- const end = sql.indexOf(`
1585
- `, index + 2);
1586
- if (end < 0)
1587
- break;
1588
- index = end;
1589
- continue;
1590
- }
1591
- if (char === "/" && sql[index + 1] === "*") {
1592
- const end = sql.indexOf("*/", index + 2);
1593
- if (end < 0)
1594
- break;
1595
- index = end + 1;
1596
- continue;
1597
- }
1598
- if (char === "(") {
1599
- if (depth === 0)
1600
- start = index + 1;
1601
- depth++;
1602
- } else if (char === ")") {
1603
- depth--;
1604
- if (depth === 0 && start >= 0) {
1605
- parts.push(sql.slice(start, index).trim());
1606
- return { clauses: parts, suffix: sql.slice(index + 1) };
1607
- }
1608
- } else if (char === "," && depth === 1) {
1609
- parts.push(sql.slice(start, index).trim());
1610
- start = index + 1;
1611
- }
1612
- }
1613
- throw unavailable();
1614
- }
1615
- function unavailable() {
1616
- return new VibeError8({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
1617
- }
1618
- function sqliteColumnClauses(params) {
1619
- const columns = [];
1620
- for (const part of tableDefinitionParts(params).clauses) {
1621
- const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
1622
- if (token === null)
1623
- throw unavailable();
1624
- if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
1625
- continue;
1626
- const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
1627
- columns.push({ name, sql: part });
1628
- }
1629
- return columns;
1630
- }
1631
1883
  // packages/sql/src/dialects/diagnostics.ts
1632
1884
  import { unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
1633
1885
  var QUERIES = Object.freeze({
@@ -2108,6 +2360,8 @@ function renderStatement(params) {
2108
2360
  return renderUpdate({ dialect, statement, collector });
2109
2361
  case "delete":
2110
2362
  return renderDelete({ dialect, statement, collector });
2363
+ case "chain":
2364
+ return renderChain({ dialect, statement, collector });
2111
2365
  case "rlsReadiness":
2112
2366
  return renderRlsReadinessQuery({ dialect, ...statement });
2113
2367
  case "rlsProbeTable":
@@ -2141,8 +2395,29 @@ function quoteCol(dialect, column) {
2141
2395
  }
2142
2396
  return `CAST(${ref} AS REAL)`;
2143
2397
  }
2398
+ if (column.castJsonb !== undefined) {
2399
+ if (dialect.name !== "postgres") {
2400
+ throw internalError2({ message: "ColRef.castJsonb is a postgres json construct" });
2401
+ }
2402
+ return `${ref}::${column.castJsonb}`;
2403
+ }
2144
2404
  return ref;
2145
2405
  }
2406
+ function typedOrderRef(params) {
2407
+ const { statement, column } = params;
2408
+ if (column.table !== undefined || column.castReal === true || column.castJsonb !== undefined)
2409
+ return column;
2410
+ const renamed = statement.columns.some((projected) => (projected.alias ?? projected.name) === column.name && (projected.transform !== undefined || projected.alias !== undefined));
2411
+ return renamed ? { ...column, table: statement.table.alias ?? statement.table.name } : column;
2412
+ }
2413
+ function jsonCellCast(dialect, cell) {
2414
+ if (cell.storage !== "json" || dialect.jsonParamCast === "")
2415
+ return dialect.jsonParamCast;
2416
+ if (dialect.name !== "postgres") {
2417
+ throw internalError2({ message: "JsonParamCell storage json is a postgres construct" });
2418
+ }
2419
+ return "::text::json";
2420
+ }
2146
2421
  function quoteProjection(dialect, column) {
2147
2422
  if (column.transform === undefined) {
2148
2423
  const base = quoteCol(dialect, column);
@@ -2151,7 +2426,7 @@ function quoteProjection(dialect, column) {
2151
2426
  return `${base} AS ${dialect.quoteIdent({ name: column.alias })}`;
2152
2427
  }
2153
2428
  if (dialect.name !== "postgres") {
2154
- throw internalError2({ message: "ColRef.transform is a postgres lateral-join construct" });
2429
+ throw internalError2({ message: "ColRef.transform is a postgres construct" });
2155
2430
  }
2156
2431
  const ref = quoteCol(dialect, column);
2157
2432
  const alias = dialect.quoteIdent({ name: column.name });
@@ -2159,6 +2434,10 @@ function quoteProjection(dialect, column) {
2159
2434
  return `${ref}::text AS ${alias}`;
2160
2435
  return `encode(${ref}, 'base64') AS ${alias}`;
2161
2436
  }
2437
+ function renderProjection(params) {
2438
+ const { dialect, columns } = params;
2439
+ return columns.map((column) => quoteProjection(dialect, column)).join(", ");
2440
+ }
2162
2441
  function renderTable(dialect, table) {
2163
2442
  if (table.namespace !== undefined)
2164
2443
  assertSqlNamespace2({ namespace: table.namespace, dialect: dialect.name });
@@ -2219,7 +2498,8 @@ function renderExpr(params) {
2219
2498
  if (isDbNowMarker(expr.value)) {
2220
2499
  return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
2221
2500
  }
2222
- return `${quoteCol(dialect, expr.column)} ${op} ${collector.add(expr.value)}`;
2501
+ const compared = listComparison({ dialect, column: expr.column, value: expr.value, collector });
2502
+ return `${compared.column} ${op} ${compared.operand}`;
2223
2503
  }
2224
2504
  case "cmpCol": {
2225
2505
  const op = COMPARISON_SQL2[expr.op];
@@ -2235,6 +2515,10 @@ function renderExpr(params) {
2235
2515
  return expr.negate === true ? "1 = 1" : "1 = 0";
2236
2516
  }
2237
2517
  const column = quoteCol(dialect, expr.column);
2518
+ if (expr.timeOfDay === true && dialect.timeOfDayInStrategy === "equalities") {
2519
+ const equalities = collector.addMany(expr.values).map((placeholder) => `${column} = ${placeholder}`);
2520
+ return `${expr.negate === true ? "NOT " : ""}(${equalities.join(" OR ")})`;
2521
+ }
2238
2522
  if (dialect.inArrayStrategy === "any") {
2239
2523
  const base = `${column} = ANY(${collector.add(Array.isArray(expr.values) ? [...expr.values] : expr.values)})`;
2240
2524
  return expr.negate === true ? `NOT (${base})` : base;
@@ -2328,16 +2612,21 @@ function renderExpr(params) {
2328
2612
  }
2329
2613
  case "arrayHas": {
2330
2614
  assertScalarArrays(dialect, 'the scalar-list filter "has"');
2331
- const base = `${collector.add(expr.value)} = ANY(${quoteCol(dialect, expr.column)})`;
2615
+ const compared = listComparison({ dialect, column: expr.column, value: expr.value, collector });
2616
+ const base = `${compared.operand} = ANY(${compared.column})`;
2332
2617
  return expr.negate === true ? `NOT (${base})` : base;
2333
2618
  }
2334
2619
  case "arrayContainsAll": {
2335
2620
  assertScalarArrays(dialect, 'the scalar-list filter "hasEvery"');
2336
- return `${quoteCol(dialect, expr.column)} @> ${collector.add(Array.isArray(expr.values) ? [...expr.values] : expr.values)}`;
2621
+ const values = Array.isArray(expr.values) ? [...expr.values] : expr.values;
2622
+ const compared = listComparison({ dialect, column: expr.column, value: values, collector });
2623
+ return `${compared.column} @> ${compared.operand}`;
2337
2624
  }
2338
2625
  case "arrayOverlaps": {
2339
2626
  assertScalarArrays(dialect, 'the scalar-list filter "hasSome"');
2340
- return `${quoteCol(dialect, expr.column)} && ${collector.add(Array.isArray(expr.values) ? [...expr.values] : expr.values)}`;
2627
+ const values = Array.isArray(expr.values) ? [...expr.values] : expr.values;
2628
+ const compared = listComparison({ dialect, column: expr.column, value: values, collector });
2629
+ return `${compared.column} && ${compared.operand}`;
2341
2630
  }
2342
2631
  case "arrayIsEmpty": {
2343
2632
  assertScalarArrays(dialect, 'the scalar-list filter "isEmpty"');
@@ -2385,6 +2674,16 @@ function jsonPathString(path) {
2385
2674
  }
2386
2675
  return out;
2387
2676
  }
2677
+ function listComparison(params) {
2678
+ const { dialect, column, value, collector } = params;
2679
+ if (isJsonListParameter(value) && value.storage === "json") {
2680
+ return {
2681
+ column: `${quoteCol(dialect, column)}::jsonb[]`,
2682
+ operand: collector.add(jsonListParameter({ value: value.value, storage: "jsonb" }))
2683
+ };
2684
+ }
2685
+ return { column: quoteCol(dialect, column), operand: collector.add(value) };
2686
+ }
2388
2687
  function jsonStringPattern(params) {
2389
2688
  const { expr } = params;
2390
2689
  if (typeof expr.value !== "string") {
@@ -2422,19 +2721,22 @@ function renderJsonFilter(params) {
2422
2721
  if (dialect.jsonPathMode === "operators") {
2423
2722
  const column = quoteCol(dialect, expr.column);
2424
2723
  const pathParam = `${collector.add(expr.path.map(String))}::text[]`;
2724
+ const extracted = expr.storage === "json" ? `(${column} #> ${pathParam})::jsonb` : `(${column} #> ${pathParam})`;
2425
2725
  switch (expr.op) {
2426
2726
  case "equals":
2427
- return `(${column} #> ${pathParam}) ${cmp} ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
2727
+ return `${extracted} ${cmp} ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
2428
2728
  case "string_contains":
2429
2729
  case "string_starts_with":
2430
- case "string_ends_with":
2431
- return renderJsonLike({ dialect, target: `(${column} #>> ${pathParam})`, expr, collector });
2730
+ case "string_ends_with": {
2731
+ const target = expr.storage === "json" ? `(${extracted} #>> '{}')` : `(${column} #>> ${pathParam})`;
2732
+ return renderJsonLike({ dialect, target, expr, collector });
2733
+ }
2432
2734
  case "array_contains":
2433
- return `(${column} #> ${pathParam}) @> ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
2735
+ return `${extracted} @> ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
2434
2736
  case "array_starts_with":
2435
- return `(${column} #> ${pathParam}) -> 0 = ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
2737
+ return `${extracted} -> 0 = ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
2436
2738
  case "array_ends_with":
2437
- return `(${column} #> ${pathParam}) -> -1 = ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
2739
+ return `${extracted} -> -1 = ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
2438
2740
  }
2439
2741
  }
2440
2742
  if (dialect.jsonPathMode === "sqliteExtract") {
@@ -2578,7 +2880,14 @@ function renderLateralJoin(params) {
2578
2880
  const terms = join.select.orderBy;
2579
2881
  const columnTerms = terms.filter((term) => ("column" in term));
2580
2882
  if (columnTerms.length === terms.length && columnTerms.every((term) => projected.has(term.column.name))) {
2581
- const rendered = columnTerms.map((term) => renderOrderTerm({ dialect, term: { ...term, column: { table: "__sub", name: term.column.name } }, collector }));
2883
+ const rendered = columnTerms.map((term) => renderOrderTerm({
2884
+ dialect,
2885
+ term: {
2886
+ ...term,
2887
+ column: { table: "__sub", name: term.column.name, ...term.column.castJsonb === undefined ? {} : { castJsonb: term.column.castJsonb } }
2888
+ },
2889
+ collector
2890
+ }));
2582
2891
  aggOrder = ` ORDER BY ${rendered.join(", ")}`;
2583
2892
  }
2584
2893
  }
@@ -2587,6 +2896,15 @@ function renderLateralJoin(params) {
2587
2896
  const alias = dialect.quoteIdent({ name: join.alias });
2588
2897
  return `LEFT JOIN LATERAL (SELECT ${projection} FROM (${inner.text}) AS ${subAlias}) AS ${alias} ON true`;
2589
2898
  }
2899
+ function renderCountSubquery(params) {
2900
+ const { dialect, subquery, collector } = params;
2901
+ const select = subquery.select;
2902
+ 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) {
2903
+ throw internalError2({ message: "count subquery must be one ungrouped COUNT(*)" });
2904
+ }
2905
+ const inner = renderSelect({ dialect, statement: select, collector });
2906
+ return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
2907
+ }
2590
2908
  function applyReturning(params) {
2591
2909
  const { dialect, requested } = params;
2592
2910
  if (requested === undefined || requested.length === 0) {
@@ -2595,9 +2913,65 @@ function applyReturning(params) {
2595
2913
  if (!dialect.capabilities.returning) {
2596
2914
  return { suffix: "", status: "omitted" };
2597
2915
  }
2598
- const columns = requested.map((name) => dialect.quoteIdent({ name })).join(", ");
2916
+ const columns = requested.map((column) => {
2917
+ if (typeof column === "string")
2918
+ return dialect.quoteIdent({ name: column });
2919
+ if (dialect.name !== "postgres") {
2920
+ throw internalError2({ message: "a castText RETURNING column is a postgres construct" });
2921
+ }
2922
+ const name = dialect.quoteIdent({ name: column.name });
2923
+ return `${name}::text AS ${name}`;
2924
+ }).join(", ");
2599
2925
  return { suffix: ` RETURNING ${columns}`, status: "native" };
2600
2926
  }
2927
+ function renderKeyTableJoin(params) {
2928
+ const { dialect, join, collector } = params;
2929
+ const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
2930
+ const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
2931
+ if (!admitted) {
2932
+ throw unsupportedCapability15({
2933
+ dialect: dialect.name,
2934
+ feature: "relation key table",
2935
+ hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
2936
+ });
2937
+ }
2938
+ if (join.columns.length === 0 || join.columns.length !== join.members.length) {
2939
+ throw internalError2({ message: `key table needs one member per column, got ${join.members.length} for ${join.columns.length}` });
2940
+ }
2941
+ if (join.rows.length === 0)
2942
+ throw internalError2({ message: "key table needs at least one key row" });
2943
+ for (const member of join.members) {
2944
+ if (member.kind === "decimal" && !(Number.isInteger(member.scale) && member.scale >= 0 && member.scale <= 30)) {
2945
+ throw internalError2({ message: `key table decimal scale must be an integer 0-30, got ${String(member.scale)}` });
2946
+ }
2947
+ }
2948
+ const alias = dialect.quoteIdent({ name: join.alias });
2949
+ const memberNames = join.members.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
2950
+ const rows = join.rows.map((row, ordinal) => {
2951
+ if (row.length !== join.members.length) {
2952
+ throw internalError2({ message: `key table row has ${row.length} values for ${join.members.length} members` });
2953
+ }
2954
+ const cells = row.map((value, index) => {
2955
+ const placeholder = collector.add(value);
2956
+ const member = join.members[index];
2957
+ switch (member.kind) {
2958
+ case "integer":
2959
+ return dialect.name === "mysql" ? `CAST(${placeholder} AS SIGNED)` : `CAST(${placeholder} AS bigint)`;
2960
+ case "decimal":
2961
+ return `CAST(${placeholder} AS DECIMAL(65,${member.scale}))`;
2962
+ case "citext":
2963
+ return `CAST(${placeholder} AS citext)`;
2964
+ case "text":
2965
+ return `CAST(${placeholder} AS text)`;
2966
+ default:
2967
+ return placeholder;
2968
+ }
2969
+ });
2970
+ return dialect.name === "mysql" ? `ROW(${cells.join(", ")}, ${ordinal})` : `(${cells.join(", ")}, ${ordinal})`;
2971
+ });
2972
+ const on = join.columns.map((column, index) => `${quoteCol(dialect, column)} = ${alias}.${memberNames[index]}`).join(" AND ");
2973
+ return `INNER JOIN (VALUES ${rows.join(", ")}) AS ${alias} (${[...memberNames, dialect.quoteIdent({ name: join.ordinalAlias })].join(", ")}) ON ${on}`;
2974
+ }
2601
2975
  function renderSelect(params) {
2602
2976
  const { dialect, statement, collector } = params;
2603
2977
  if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
@@ -2615,6 +2989,11 @@ function renderSelect(params) {
2615
2989
  columnList.push(`${renderAggRef(dialect, selection.agg)} AS ${dialect.quoteIdent({ name: selection.alias })}`);
2616
2990
  }
2617
2991
  }
2992
+ if (statement.countSubqueries !== undefined) {
2993
+ for (const subquery of statement.countSubqueries) {
2994
+ columnList.push(renderCountSubquery({ dialect, subquery, collector }));
2995
+ }
2996
+ }
2618
2997
  if (statement.rowNumber !== undefined) {
2619
2998
  const rowNumber = statement.rowNumber;
2620
2999
  if (rowNumber.partitionBy.length === 0) {
@@ -2633,7 +3012,7 @@ function renderSelect(params) {
2633
3012
  hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
2634
3013
  });
2635
3014
  }
2636
- distinctPrefix = `DISTINCT ON (${statement.distinctOn.map((column) => quoteCol(dialect, column)).join(", ")}) `;
3015
+ distinctPrefix = `DISTINCT ON (${statement.distinctOn.map((column) => quoteCol(dialect, typedOrderRef({ statement, column }))).join(", ")}) `;
2637
3016
  }
2638
3017
  const columns = columnList.length === 0 ? "*" : columnList.join(", ");
2639
3018
  let from;
@@ -2656,6 +3035,9 @@ function renderSelect(params) {
2656
3035
  text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
2657
3036
  }
2658
3037
  }
3038
+ if (statement.keyTable !== undefined) {
3039
+ text += ` ${renderKeyTableJoin({ dialect, join: statement.keyTable, collector })}`;
3040
+ }
2659
3041
  if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
2660
3042
  if (!dialect.capabilities.lateralJoin) {
2661
3043
  throw unsupportedCapability15({
@@ -2678,7 +3060,7 @@ function renderSelect(params) {
2678
3060
  text += ` HAVING ${renderExpr({ dialect, expr: statement.having, collector })}`;
2679
3061
  }
2680
3062
  if (statement.orderBy !== undefined && statement.orderBy.length > 0) {
2681
- const terms = statement.orderBy.map((term) => renderOrderTerm({ dialect, term, collector }));
3063
+ const terms = statement.orderBy.map((term) => renderOrderTerm({ dialect, term: "column" in term ? { ...term, column: typedOrderRef({ statement, column: term.column }) } : term, collector }));
2682
3064
  text += ` ORDER BY ${terms.join(", ")}`;
2683
3065
  }
2684
3066
  if (statement.limit !== undefined) {
@@ -2720,7 +3102,7 @@ function renderInsert(params) {
2720
3102
  const columnList = statement.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
2721
3103
  const rowsSql = statement.rows.map((row) => `(${row.map((value) => {
2722
3104
  if (isJsonParamCell(value))
2723
- return `${collector.add(value.text)}${dialect.jsonParamCast}`;
3105
+ return `${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
2724
3106
  if (isDbNowMarker(value)) {
2725
3107
  return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
2726
3108
  }
@@ -2852,7 +3234,7 @@ function renderAssignments(params) {
2852
3234
  return `${column} = ${renderIncomingRowColumn({ dialect, column: value.column })}`;
2853
3235
  }
2854
3236
  if (isJsonParamCell(value)) {
2855
- return `${column} = ${collector.add(value.text)}${dialect.jsonParamCast}`;
3237
+ return `${column} = ${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
2856
3238
  }
2857
3239
  if (isSetOperation(value)) {
2858
3240
  const operand = collector.add(value.value);
@@ -2881,6 +3263,172 @@ function renderDelete(params) {
2881
3263
  const returning = applyReturning({ dialect, requested: statement.returning });
2882
3264
  return { text: text + returning.suffix, values: collector.values, returning: returning.status };
2883
3265
  }
3266
+ var CHAIN_ROWS_ALIAS = "__vibe_rows";
3267
+ var CHAIN_ONE_ALIAS = "__vibe_one";
3268
+ function renderChain(params) {
3269
+ const { dialect, statement, collector } = params;
3270
+ if (dialect.statementChain === null) {
3271
+ throw unsupportedCapability15({
3272
+ dialect: dialect.name,
3273
+ feature: "a statement chain (data-modifying WITH steps)",
3274
+ hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
3275
+ });
3276
+ }
3277
+ if (statement.steps.length === 0)
3278
+ throw internalError2({ message: "a statement chain needs at least one step" });
3279
+ const defined = new Set;
3280
+ const steps = [];
3281
+ for (const step of statement.steps) {
3282
+ if (step.name === CHAIN_ROWS_ALIAS || step.name === CHAIN_ONE_ALIAS || defined.has(step.name)) {
3283
+ throw internalError2({ message: `chain step name "${step.name}" is reserved or repeated` });
3284
+ }
3285
+ const body = renderChainStep({ dialect, statement: step.statement, collector, earlier: defined });
3286
+ steps.push(`${dialect.quoteIdent({ name: step.name })} AS (${body})`);
3287
+ defined.add(step.name);
3288
+ }
3289
+ const result = statement.result;
3290
+ if (result.rowsFrom.length === 0 || result.columns.length === 0) {
3291
+ throw internalError2({ message: "a chain result needs at least one source step and one column" });
3292
+ }
3293
+ for (const name of result.rowsFrom) {
3294
+ if (!defined.has(name))
3295
+ throw internalError2({ message: `chain result reads undefined step "${name}"` });
3296
+ }
3297
+ const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
3298
+ const union = result.rowsFrom.map((name) => `SELECT ${columnList} FROM ${dialect.quoteIdent({ name })}`).join(" UNION ALL ");
3299
+ let select = union;
3300
+ if (result.verdict !== undefined || result.tally !== undefined || result.veto !== undefined && result.veto.length > 0) {
3301
+ const extra = [];
3302
+ for (const alias of [result.verdict?.alias, result.tally?.alias]) {
3303
+ if (alias !== undefined && result.columns.includes(alias)) {
3304
+ throw internalError2({ message: `chain result alias "${alias}" collides with a result column` });
3305
+ }
3306
+ }
3307
+ const rows = dialect.quoteIdent({ name: CHAIN_ROWS_ALIAS });
3308
+ const projected = result.columns.map((name) => `${rows}.${dialect.quoteIdent({ name })}`);
3309
+ if (result.verdict !== undefined) {
3310
+ extra.push(`${renderExpr({ dialect, expr: result.verdict.expr, collector })} AS ${dialect.quoteIdent({ name: result.verdict.alias })}`);
3311
+ }
3312
+ if (result.tally !== undefined || result.veto !== undefined && result.veto.length > 0) {
3313
+ extra.push(renderTallyColumn({ dialect, result, defined, collector }));
3314
+ }
3315
+ select = `SELECT ${[...projected, ...extra].join(", ")} FROM (SELECT 1) AS ${dialect.quoteIdent({ name: CHAIN_ONE_ALIAS })}` + ` LEFT JOIN (${union}) AS ${rows} ON TRUE`;
3316
+ }
3317
+ return { text: `WITH ${steps.join(", ")} ${select}`, values: collector.values, returning: "none" };
3318
+ }
3319
+ function renderChainStep(params) {
3320
+ const { dialect, statement, collector } = params;
3321
+ switch (statement.kind) {
3322
+ case "select":
3323
+ return renderSelect({ dialect, statement, collector }).text;
3324
+ case "insert":
3325
+ return renderInsert({ dialect, statement, collector }).text;
3326
+ case "insertSelect":
3327
+ return renderInsertSelect({ dialect, statement, collector, earlier: params.earlier });
3328
+ case "update":
3329
+ return renderUpdate({ dialect, statement, collector }).text;
3330
+ case "delete":
3331
+ return renderDelete({ dialect, statement, collector }).text;
3332
+ }
3333
+ }
3334
+ function renderSelectCell(params) {
3335
+ const { dialect, value, collector } = params;
3336
+ if (isJsonParamCell(value))
3337
+ return `${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
3338
+ if (isDbNowMarker(value))
3339
+ return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
3340
+ if (isSqlDefaultCell(value)) {
3341
+ throw internalError2({ message: "DEFAULT is not allowed in an INSERT \u2026 SELECT list \u2014 omit the column instead" });
3342
+ }
3343
+ return collector.add(value);
3344
+ }
3345
+ function renderRowSource(params) {
3346
+ const { dialect, source, collector } = params;
3347
+ if (source.columns.length === 0 || source.rows.length === 0) {
3348
+ throw internalError2({ message: "a row source needs at least one column and one row" });
3349
+ }
3350
+ const typedBy = renderTable(dialect, source.typedBy);
3351
+ const columns = source.columns.map((name) => dialect.quoteIdent({ name }));
3352
+ const rows = [`(${columns.map((column) => `(SELECT ${column} FROM ${typedBy} WHERE FALSE)`).join(", ")})`];
3353
+ for (const row of source.rows) {
3354
+ if (row.length !== source.columns.length) {
3355
+ throw internalError2({ message: `row source row has ${row.length} values but ${source.columns.length} columns` });
3356
+ }
3357
+ rows.push(`(${row.map((value) => renderSelectCell({ dialect, value, collector })).join(", ")})`);
3358
+ }
3359
+ const alias = dialect.quoteIdent({ name: source.alias });
3360
+ const columnList = columns.join(", ");
3361
+ return `(SELECT ${columnList} FROM (VALUES ${rows.join(", ")}) AS ${alias} (${columnList}) OFFSET 1) AS ${alias}`;
3362
+ }
3363
+ function renderTallyColumn(params) {
3364
+ const { dialect, result, defined, collector } = params;
3365
+ const tally = result.tally;
3366
+ if (tally === undefined)
3367
+ throw internalError2({ message: "a chain veto needs a tally column to guard" });
3368
+ if (tally.steps.length === 0)
3369
+ throw internalError2({ message: "a chain tally needs at least one step" });
3370
+ const step = (name) => {
3371
+ if (!defined.has(name))
3372
+ throw internalError2({ message: `chain tally or veto reads undefined step "${name}"` });
3373
+ return dialect.quoteIdent({ name });
3374
+ };
3375
+ const count = (name) => `(SELECT count(*) FROM ${step(name)})`;
3376
+ const sum = tally.steps.map(count).join(" + ");
3377
+ const vetoes = result.veto ?? [];
3378
+ if (vetoes.length === 0)
3379
+ return `${sum} AS ${dialect.quoteIdent({ name: tally.alias })}`;
3380
+ const arms = vetoes.map((veto) => {
3381
+ if (!CHAIN_VETO_TOKEN_PATTERN.test(veto.token))
3382
+ throw internalError2({ message: `chain veto token "${veto.token}" is not a lower-case word` });
3383
+ if (typeof veto.nonce !== "string" || veto.nonce === "")
3384
+ throw internalError2({ message: `chain veto "${veto.token}" needs a non-empty nonce` });
3385
+ const minRows = veto.minRows ?? 1;
3386
+ if (!Number.isSafeInteger(minRows) || minRows < 1)
3387
+ throw internalError2({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
3388
+ const when = minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
3389
+ return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
3390
+ });
3391
+ return `CASE ${arms.join(" ")} ELSE ${sum} END AS ${dialect.quoteIdent({ name: tally.alias })}`;
3392
+ }
3393
+ function renderInsertSelect(params) {
3394
+ const { dialect, statement, collector } = params;
3395
+ if (statement.columns.length === 0)
3396
+ throw internalError2({ message: "insert requires at least one column" });
3397
+ if (statement.cells.length !== statement.columns.length) {
3398
+ throw internalError2({
3399
+ message: `insert row has ${statement.cells.length} values but ${statement.columns.length} columns`
3400
+ });
3401
+ }
3402
+ const from = statement.from ?? [];
3403
+ for (const name of from) {
3404
+ if (!params.earlier.has(name))
3405
+ throw internalError2({ message: `INSERT \u2026 SELECT reads step "${name}" before it is defined` });
3406
+ }
3407
+ const source = statement.source;
3408
+ if (source !== undefined && (params.earlier.has(source.alias) || from.includes(source.alias))) {
3409
+ throw internalError2({ message: `row source alias "${source.alias}" collides with a chain step` });
3410
+ }
3411
+ const cells = statement.cells.map((value) => {
3412
+ if (isStepColumnRef(value)) {
3413
+ if (!from.includes(value.step) && value.step !== source?.alias) {
3414
+ throw internalError2({ message: `cell reads step "${value.step}", which the INSERT \u2026 SELECT does not name in FROM` });
3415
+ }
3416
+ return `${dialect.quoteIdent({ name: value.step })}.${dialect.quoteIdent({ name: value.column })}`;
3417
+ }
3418
+ return renderSelectCell({ dialect, value, collector });
3419
+ });
3420
+ const columnList = statement.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
3421
+ let text = `INSERT INTO ${renderTable(dialect, statement.table)} (${columnList}) SELECT ${cells.join(", ")}`;
3422
+ const relations = from.map((name) => dialect.quoteIdent({ name }));
3423
+ if (source !== undefined)
3424
+ relations.push(renderRowSource({ dialect, source, collector }));
3425
+ if (relations.length > 0)
3426
+ text += ` FROM ${relations.join(" CROSS JOIN ")}`;
3427
+ if (statement.where !== undefined) {
3428
+ text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
3429
+ }
3430
+ return text + applyReturning({ dialect, requested: statement.returning }).suffix;
3431
+ }
2884
3432
  // packages/sql/src/dialects/postgres-type-identity.ts
2885
3433
  import { unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
2886
3434
  var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
@@ -3410,6 +3958,8 @@ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${table} AND a.att
3410
3958
  export {
3411
3959
  ADVISORY_KEY_BOUNDS,
3412
3960
  ADVISORY_LOCK_RESULT_COLUMN,
3961
+ CHAIN_VETO_PREFIX,
3962
+ CHAIN_VETO_TOKEN_PATTERN,
3413
3963
  DIALECT_CAPABILITIES,
3414
3964
  DIALECT_CODECS,
3415
3965
  JSON_TRANSPORT,
@@ -3431,6 +3981,7 @@ export {
3431
3981
  SQLITE_SESSION_SQL,
3432
3982
  SQL_DEFAULT_CELL,
3433
3983
  SQL_DIALECTS,
3984
+ chainVetoToken,
3434
3985
  compileExclusion,
3435
3986
  compileSql,
3436
3987
  containsPattern,
@@ -3443,23 +3994,29 @@ export {
3443
3994
  escapeGlobPattern,
3444
3995
  escapeLikePattern,
3445
3996
  excludedRef,
3997
+ float32Codec,
3446
3998
  getSqlDialect,
3447
3999
  isDbNowMarker,
3448
4000
  isExcludedRef,
3449
4001
  isFixedDiagnostic,
4002
+ isJsonListParameter,
3450
4003
  isJsonParamCell,
3451
4004
  isListSetOperation,
3452
4005
  isNativeEnumArrayParameter,
3453
4006
  isSetOperation,
3454
4007
  isSqlDefaultCell,
3455
4008
  isSqlFragment,
4009
+ isStepColumnRef,
4010
+ jsonListParameter,
3456
4011
  jsonParamCell,
3457
4012
  mapConflictColumns,
3458
4013
  mysqlDialect,
3459
4014
  nativeEnumArrayParameter,
3460
4015
  parseDateTimeText,
3461
4016
  parsePgArrayLiteral,
4017
+ parsePgArrayLiteralNested,
3462
4018
  parsePostgresTypeName,
4019
+ postgresArrayElementFromText,
3463
4020
  postgresDialect,
3464
4021
  projectionOutputName,
3465
4022
  renderAdvisoryLock,
@@ -3501,6 +4058,7 @@ export {
3501
4058
  renderPostgresTypeName,
3502
4059
  renderPostgresWorkload,
3503
4060
  renderPredicate,
4061
+ renderProjection,
3504
4062
  renderRelationStateQuery,
3505
4063
  renderReplayCoverageGaps,
3506
4064
  renderRlsColumnType,
@@ -3521,17 +4079,23 @@ export {
3521
4079
  renderSqlFragment,
3522
4080
  renderSqliteColumnInventory,
3523
4081
  renderSqliteForeignKeyInventory,
4082
+ renderSqliteFtsUpdateTrigger,
3524
4083
  renderSqliteRebuildObjects,
3525
4084
  renderStatement,
4085
+ returningColumnName,
4086
+ shortestFloat32,
3526
4087
  sql,
3527
4088
  sqlUsesIncoming,
3528
4089
  sqliteColumnClauses,
3529
4090
  sqliteDialect,
4091
+ sqliteFtsUpdateUpgrade,
3530
4092
  sqliteTableConstraints,
3531
4093
  startsWithPattern,
4094
+ stepColumnRef,
3532
4095
  strictMatchPattern,
3533
4096
  tableDefinitionParts,
4097
+ timeOfDayCodec,
3534
4098
  toPgArrayLiteral
3535
4099
  };
3536
4100
 
3537
- //# debugId=55E18657169AA23764756E2164756E21
4101
+ //# debugId=67799F9417158D9264756E2164756E21