tempest-db-js 0.4.0 → 0.6.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.
@@ -11,6 +11,142 @@ function toCondNode(input) {
11
11
  function wrap(node) {
12
12
  return { [CONDITION]: true, node };
13
13
  }
14
+ function toExprNode(operand) {
15
+ return isExpression(operand) ? operand.node : { kind: "value", value: operand };
16
+ }
17
+ function assertValueOperands(op, operands) {
18
+ if (operands.some(isExpression)) {
19
+ throw new Error(
20
+ `The "${op}" operator binds its operands, so it takes values, not expressions.`
21
+ );
22
+ }
23
+ }
24
+ function isExpression(value) {
25
+ return value instanceof Expression;
26
+ }
27
+ var Expression = class {
28
+ constructor(node) {
29
+ this.node = node;
30
+ }
31
+ node;
32
+ /** Compare this expression against another expression or a bound value. */
33
+ compare(op, operand) {
34
+ return wrap({ kind: "compare", left: this.node, op, right: toExprNode(operand) });
35
+ }
36
+ /** `=` (or `IS NULL` for a null value). */
37
+ eq(operand) {
38
+ return this.compare("eq", operand);
39
+ }
40
+ /** `<>` (or `IS NOT NULL` for a null value). */
41
+ ne(operand) {
42
+ return this.compare("ne", operand);
43
+ }
44
+ /** `>`. */
45
+ gt(operand) {
46
+ return this.compare("gt", operand);
47
+ }
48
+ /** `>=`. */
49
+ gte(operand) {
50
+ return this.compare("gte", operand);
51
+ }
52
+ /** `<`. */
53
+ lt(operand) {
54
+ return this.compare("lt", operand);
55
+ }
56
+ /** `<=`. */
57
+ lte(operand) {
58
+ return this.compare("lte", operand);
59
+ }
60
+ /** `LIKE` — `%` and `_` in the operand are wildcards. */
61
+ like(pattern) {
62
+ return this.compare("like", pattern);
63
+ }
64
+ /** `ILIKE` — case-insensitive **pattern** matching, wildcards included. */
65
+ ilike(pattern) {
66
+ return this.compare("ilike", pattern);
67
+ }
68
+ /** Case-insensitive equality (`lower(a) = lower(b)`), with no wildcards. */
69
+ ieq(operand) {
70
+ return this.compare("ieq", operand);
71
+ }
72
+ /**
73
+ * `IN (...)` over a list of values.
74
+ *
75
+ * @param values The values to test against.
76
+ * @returns The condition.
77
+ * @throws Error When an entry is an {@link Expression} — a list operand is
78
+ * bound, so an expression there would be serialized as a parameter instead of
79
+ * rendered as SQL.
80
+ */
81
+ in(values) {
82
+ assertValueOperands("in", values);
83
+ return this.compare("in", values);
84
+ }
85
+ /**
86
+ * `NOT IN (...)` over a list of values.
87
+ *
88
+ * @param values The values to exclude.
89
+ * @returns The condition.
90
+ * @throws Error When an entry is an {@link Expression} (see {@link Expression.in}).
91
+ */
92
+ notIn(values) {
93
+ assertValueOperands("notIn", values);
94
+ return this.compare("notIn", values);
95
+ }
96
+ /**
97
+ * `BETWEEN lo AND hi` (inclusive).
98
+ *
99
+ * @param lo The lower bound.
100
+ * @param hi The upper bound.
101
+ * @returns The condition.
102
+ * @throws Error When a bound is an {@link Expression} (see {@link Expression.in}).
103
+ */
104
+ between(lo, hi) {
105
+ assertValueOperands("between", [lo, hi]);
106
+ return this.compare("between", [lo, hi]);
107
+ }
108
+ /** `IS NULL` (true) / `IS NOT NULL` (false). */
109
+ isNull(value = true) {
110
+ return this.compare("isNull", value);
111
+ }
112
+ };
113
+ function col(name) {
114
+ return new Expression({ kind: "column", name });
115
+ }
116
+ function val(value) {
117
+ return new Expression({ kind: "value", value });
118
+ }
119
+ var FUNCTION_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
120
+ function toArg(arg) {
121
+ return typeof arg === "string" ? { kind: "column", name: arg } : arg.node;
122
+ }
123
+ function call(name, ...args) {
124
+ if (!FUNCTION_NAME.test(name)) {
125
+ throw new Error(
126
+ `fn.call() takes a plain SQL function name; got ${JSON.stringify(name)}.`
127
+ );
128
+ }
129
+ return new Expression({ kind: "fn", name, args: args.map(toArg) });
130
+ }
131
+ var fn = {
132
+ /** `lower(x)`. */
133
+ lower: (arg) => call("lower", arg),
134
+ /** `upper(x)`. */
135
+ upper: (arg) => call("upper", arg),
136
+ /** `trim(x)`. */
137
+ trim: (arg) => call("trim", arg),
138
+ /** `length(x)`. */
139
+ length: (arg) => call("length", arg),
140
+ /** `abs(x)`. */
141
+ abs: (arg) => call("abs", arg),
142
+ /** `coalesce(a, b, ...)`. */
143
+ coalesce: (...args) => call("coalesce", ...args),
144
+ /**
145
+ * Any other SQL function, by name. Portability is the caller's problem —
146
+ * `date_trunc` is PostgreSQL, `strftime` is SQLite.
147
+ */
148
+ call
149
+ };
14
150
  function and(...inputs) {
15
151
  return wrap({
16
152
  kind: "and",
@@ -28,6 +164,9 @@ function not(input) {
28
164
  }
29
165
 
30
166
  // src/query.ts
167
+ function isSubquery(value) {
168
+ return typeof value === "object" && value !== null && value.node?.kind === "select";
169
+ }
31
170
  var OPERATORS = [
32
171
  "eq",
33
172
  "ne",
@@ -37,14 +176,18 @@ var OPERATORS = [
37
176
  "lte",
38
177
  "like",
39
178
  "ilike",
179
+ "ieq",
40
180
  "in",
41
181
  "notIn",
42
182
  "between",
43
- "isNull"
183
+ "isNull",
184
+ "contains",
185
+ "containedBy",
186
+ "overlaps"
44
187
  ];
45
188
  var Agg = class {
46
- constructor(fn, column2) {
47
- this.fn = fn;
189
+ constructor(fn2, column2) {
190
+ this.fn = fn2;
48
191
  this.column = column2;
49
192
  }
50
193
  fn;
@@ -73,12 +216,41 @@ var SelectBuilder = class _SelectBuilder {
73
216
  node;
74
217
  source;
75
218
  with(patch) {
76
- return new _SelectBuilder({ ...this.node, ...patch }, this.source);
219
+ return new _SelectBuilder(
220
+ { ...this.node, ...patch },
221
+ this.source
222
+ );
77
223
  }
78
224
  /** Add a WHERE filter: the object form (keys typed) or an `and`/`or`/`not`. */
79
225
  where(input) {
80
226
  return this.with({ where: toCondNode(input) });
81
227
  }
228
+ /**
229
+ * Filter by the result of the aggregation (`HAVING`).
230
+ *
231
+ * Only available on a grouped builder — `.having()` before `.aggregate()` is a
232
+ * compile error, not invalid SQL at runtime. Keys are the aggregate aliases you
233
+ * named plus the grouped columns; `WHERE` still filters rows *before* grouping,
234
+ * which is a different question.
235
+ *
236
+ * @param input The condition, keyed by alias or grouped column.
237
+ * @returns A builder carrying the `HAVING` clause.
238
+ *
239
+ * @example
240
+ * ```ts
241
+ * select(Outbound)
242
+ * .where({ status: "queued" })
243
+ * .aggregate(["consumer"], { n: count() })
244
+ * .having({ n: { gt: 100 } });
245
+ * // ... GROUP BY "consumer" HAVING COUNT(*) > $2
246
+ * ```
247
+ */
248
+ having(input) {
249
+ return new _SelectBuilder(
250
+ { ...this.node, having: toCondNode(input) },
251
+ this.source
252
+ );
253
+ }
82
254
  /** Emit `SELECT DISTINCT` — drop duplicate rows. */
83
255
  distinct() {
84
256
  return this.with({ distinct: true });
@@ -110,7 +282,15 @@ var SelectBuilder = class _SelectBuilder {
110
282
  this.source
111
283
  );
112
284
  }
113
- /** Order by a column of `Full`. */
285
+ /**
286
+ * Order by a column of the model, or — on a grouped query — by an aggregate
287
+ * alias. Unlike `HAVING`, every dialect accepts the output alias in `ORDER BY`,
288
+ * so the alias is emitted as written.
289
+ *
290
+ * @param column A model column, or a projected alias.
291
+ * @param direction `"asc"` (default) or `"desc"`.
292
+ * @returns A builder carrying the ordering term.
293
+ */
114
294
  orderBy(column2, direction = "asc") {
115
295
  return this.with({
116
296
  orderBy: [...this.node.orderBy, { column: column2, direction }]
@@ -124,7 +304,87 @@ var SelectBuilder = class _SelectBuilder {
124
304
  offset(n) {
125
305
  return this.with({ offset: n });
126
306
  }
307
+ /**
308
+ * Narrow this SELECT to a single column and mark it as a subquery, so it can be
309
+ * the operand of `in` / `notIn`.
310
+ *
311
+ * The whole query — `where`, `orderBy`, `limit`, and a locking clause — is
312
+ * embedded in the outer statement, which is what collapses the claim-a-batch
313
+ * pattern into one round trip instead of selecting ids and sending them back.
314
+ *
315
+ * @param column The single column to project (checked against the model).
316
+ * @returns A subquery carrying that column's type.
317
+ *
318
+ * @example
319
+ * ```ts
320
+ * update(Outbound)
321
+ * .set({ status: "sending", attempts: sql.raw("attempts + 1") })
322
+ * .where({
323
+ * id: {
324
+ * in: select(Outbound)
325
+ * .where({ status: "queued" })
326
+ * .orderBy("nextAttemptAt")
327
+ * .limit(10)
328
+ * .forUpdate({ skipLocked: true })
329
+ * .asSubquery("id"),
330
+ * },
331
+ * })
332
+ * .returning();
333
+ * ```
334
+ */
335
+ asSubquery(column2) {
336
+ return { node: { ...this.node, columns: [column2] } };
337
+ }
338
+ /**
339
+ * Lock the selected rows for update (`SELECT ... FOR UPDATE`), à la
340
+ * SQLAlchemy's `with_for_update()`.
341
+ *
342
+ * `{ skipLocked: true }` is the job-queue claim: competing workers each take a
343
+ * disjoint batch instead of blocking on — or worse, double-processing — the
344
+ * same rows.
345
+ *
346
+ * PostgreSQL and MySQL 8.0+ only. SQLite has no row-level locking, and its
347
+ * dialect throws rather than emitting a `SELECT` that silently locks nothing —
348
+ * a lock that does not exist only fails under production concurrency.
349
+ *
350
+ * @param options `skipLocked` / `noWait` wait behavior, and `of` to restrict
351
+ * the lock to specific tables.
352
+ * @returns A builder carrying the locking clause.
353
+ * @throws Error When both `skipLocked` and `noWait` are set.
354
+ *
355
+ * @example
356
+ * ```ts
357
+ * const batch = await session.execute(
358
+ * select(Outbound)
359
+ * .where({ status: "queued" })
360
+ * .orderBy("nextAttemptAt")
361
+ * .limit(10)
362
+ * .forUpdate({ skipLocked: true }),
363
+ * ).all();
364
+ * ```
365
+ */
366
+ forUpdate(options) {
367
+ return this.with({ lock: buildLock("update", options) });
368
+ }
369
+ /**
370
+ * Take a shared read lock on the selected rows (`SELECT ... FOR SHARE`), the
371
+ * weaker counterpart of {@link SelectBuilder.forUpdate}.
372
+ *
373
+ * @param options `skipLocked` / `noWait` wait behavior, and `of` tables.
374
+ * @returns A builder carrying the locking clause.
375
+ * @throws Error When both `skipLocked` and `noWait` are set.
376
+ */
377
+ forShare(options) {
378
+ return this.with({ lock: buildLock("share", options) });
379
+ }
127
380
  };
381
+ function buildLock(strength, options) {
382
+ if (options?.skipLocked && options?.noWait) {
383
+ throw new Error("forUpdate/forShare accept skipLocked or noWait, not both.");
384
+ }
385
+ const wait = options?.skipLocked ? "skipLocked" : options?.noWait ? "noWait" : "block";
386
+ return { strength, wait, of: options?.of ?? [] };
387
+ }
128
388
  function select(model, columns) {
129
389
  return new SelectBuilder(
130
390
  {
@@ -137,13 +397,223 @@ function select(model, columns) {
137
397
  where: void 0,
138
398
  orderBy: [],
139
399
  limit: void 0,
140
- offset: void 0
400
+ offset: void 0,
401
+ names: columnNamesOf(model) ?? void 0
141
402
  },
142
403
  model
143
404
  );
144
405
  }
145
406
 
407
+ // src/serialize.ts
408
+ var ValidationError = class extends Error {
409
+ constructor(table, issues) {
410
+ super(`Validation failed for ${table}:
411
+ - ${issues.join("\n - ")}`);
412
+ this.table = table;
413
+ this.issues = issues;
414
+ this.name = "ValidationError";
415
+ }
416
+ table;
417
+ issues;
418
+ };
419
+ function toBase64(bytes) {
420
+ if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
421
+ let binary = "";
422
+ for (const byte of bytes) binary += String.fromCharCode(byte);
423
+ return btoa(binary);
424
+ }
425
+ function fromBase64(value) {
426
+ if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64"));
427
+ const binary = atob(value);
428
+ const bytes = new Uint8Array(binary.length);
429
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
430
+ return bytes;
431
+ }
432
+ function encodeValue(column2, value) {
433
+ if (value === null || value === void 0) return null;
434
+ switch (column2.type.kind) {
435
+ case "bigint":
436
+ return typeof value === "bigint" ? value.toString() : value;
437
+ case "date":
438
+ case "datetime":
439
+ case "timestamp":
440
+ return value instanceof Date ? value.toISOString() : value;
441
+ case "blob":
442
+ return value instanceof Uint8Array ? toBase64(value) : value;
443
+ default:
444
+ return value;
445
+ }
446
+ }
447
+ function decodeValue(column2, value) {
448
+ if (value === null || value === void 0) return null;
449
+ switch (column2.type.kind) {
450
+ case "bigint":
451
+ return typeof value === "bigint" ? value : BigInt(value);
452
+ case "date":
453
+ case "datetime":
454
+ case "timestamp":
455
+ return value instanceof Date ? value : new Date(value);
456
+ case "blob":
457
+ return value instanceof Uint8Array ? value : fromBase64(value);
458
+ case "json":
459
+ return typeof value === "string" ? JSON.parse(value) : value;
460
+ case "array": {
461
+ const element = column2.type.meta.element;
462
+ const items = typeof value === "string" ? JSON.parse(value) : value;
463
+ if (!Array.isArray(items)) return items;
464
+ if (!element) return items;
465
+ return items.map((item) => decodeValue({ type: element }, item));
466
+ }
467
+ case "numeric":
468
+ return typeof value === "string" ? value : String(value);
469
+ case "boolean":
470
+ return typeof value === "boolean" ? value : value === 1 || value === "true";
471
+ case "smallint":
472
+ case "integer":
473
+ case "real":
474
+ case "double":
475
+ return typeof value === "number" ? value : Number(value);
476
+ default:
477
+ return value;
478
+ }
479
+ }
480
+ function toDict(model, row) {
481
+ const columns = columnsOf(model);
482
+ const out = {};
483
+ for (const name of Object.keys(columns)) {
484
+ out[name] = row[name] ?? null;
485
+ }
486
+ return out;
487
+ }
488
+ function toJSON(model, row) {
489
+ const columns = columnsOf(model);
490
+ const out = {};
491
+ for (const [name, col2] of Object.entries(columns)) {
492
+ out[name] = encodeValue(col2, row[name] ?? null);
493
+ }
494
+ return out;
495
+ }
496
+ function stringify(model, row) {
497
+ return JSON.stringify(toJSON(model, row));
498
+ }
499
+ function fromDict(model, data) {
500
+ const columns = columnsOf(model);
501
+ const out = {};
502
+ const issues = [];
503
+ for (const [name, col2] of Object.entries(columns)) {
504
+ const present = name in data && data[name] !== void 0 && data[name] !== null;
505
+ if (!present) {
506
+ const required = col2.flags.notNull && !col2.flags.hasDefault;
507
+ if (required) {
508
+ issues.push(`missing required column "${name}"`);
509
+ continue;
510
+ }
511
+ out[name] = null;
512
+ continue;
513
+ }
514
+ try {
515
+ out[name] = decodeValue(col2, data[name]);
516
+ } catch (error) {
517
+ issues.push(`column "${name}": ${error.message}`);
518
+ }
519
+ }
520
+ if (issues.length > 0) {
521
+ throw new ValidationError(model.tablename, issues);
522
+ }
523
+ return out;
524
+ }
525
+ function parse(model, json) {
526
+ return fromDict(model, JSON.parse(json));
527
+ }
528
+ function decoderFor(type) {
529
+ switch (type.kind) {
530
+ case "bigint":
531
+ return (v) => v == null ? null : typeof v === "bigint" ? v : BigInt(v);
532
+ case "date":
533
+ case "datetime":
534
+ case "timestamp":
535
+ return (v) => v == null ? null : v instanceof Date ? v : new Date(v);
536
+ case "blob":
537
+ return (v) => v == null ? null : v instanceof Uint8Array ? v : fromBase64(v);
538
+ case "json":
539
+ return (v) => v == null ? null : typeof v === "string" ? JSON.parse(v) : v;
540
+ case "array": {
541
+ const element = type.meta.element;
542
+ const inner = element ? decoderFor(element) : null;
543
+ if (!inner) return null;
544
+ return (v) => v == null ? null : Array.isArray(v) ? v.map(inner) : v;
545
+ }
546
+ case "numeric":
547
+ return (v) => v == null ? null : typeof v === "string" ? v : String(v);
548
+ case "boolean":
549
+ return (v) => v == null ? null : typeof v === "boolean" ? v : v === 1 || v === "true";
550
+ case "smallint":
551
+ case "integer":
552
+ case "real":
553
+ case "double":
554
+ return (v) => v == null ? null : typeof v === "number" ? v : Number(v);
555
+ default:
556
+ return null;
557
+ }
558
+ }
559
+ var mapperCache = /* @__PURE__ */ new WeakMap();
560
+ function mapperFor(model) {
561
+ const cached = mapperCache.get(model);
562
+ if (cached) return cached;
563
+ const props = columnPropsOf(model);
564
+ const names = props ? Object.fromEntries(Object.entries(props).map(([db, prop]) => [prop, db])) : null;
565
+ const decoders = /* @__PURE__ */ new Map();
566
+ for (const [prop, col2] of Object.entries(columnsOf(model))) {
567
+ const decoder = decoderFor(col2.type);
568
+ if (decoder) decoders.set(names?.[prop] ?? prop, decoder);
569
+ }
570
+ const mapper = { props, decoders };
571
+ mapperCache.set(model, mapper);
572
+ return mapper;
573
+ }
574
+ function coerceRow(model, raw) {
575
+ const { props, decoders } = mapperFor(model);
576
+ const out = {};
577
+ for (const name of Object.keys(raw)) {
578
+ const decode2 = decoders.get(name);
579
+ out[props?.[name] ?? name] = decode2 ? decode2(raw[name]) : raw[name];
580
+ }
581
+ return out;
582
+ }
583
+
146
584
  // src/mutations.ts
585
+ var STRUCTURED_KINDS = /* @__PURE__ */ new Set(["json", "array", "blob"]);
586
+ function isBindableScalar(value) {
587
+ if (value === null || value === void 0) return true;
588
+ const type = typeof value;
589
+ if (type === "string" || type === "number" || type === "bigint" || type === "boolean") {
590
+ return true;
591
+ }
592
+ return value instanceof Date || value instanceof Uint8Array;
593
+ }
594
+ function assertWritableValues(model, values, clause) {
595
+ const columns = columnsOf(model);
596
+ const issues = [];
597
+ for (const [key, value] of Object.entries(values)) {
598
+ const col2 = columns[key];
599
+ if (!col2) {
600
+ issues.push(`${clause}: "${key}" is not a column of ${model.tablename}`);
601
+ continue;
602
+ }
603
+ if (isSqlExpression(value) || isBindableScalar(value)) continue;
604
+ if (typeof value === "object" && STRUCTURED_KINDS.has(col2.type.kind)) continue;
605
+ issues.push(
606
+ `${clause}: "${key}" got ${describeValue(value)}, which cannot be bound to a ${col2.type.kind} column \u2014 use sql.raw()/sql.expr\`...\` for a SQL expression`
607
+ );
608
+ }
609
+ if (issues.length > 0) throw new ValidationError(model.tablename, issues);
610
+ }
611
+ function describeValue(value) {
612
+ if (typeof value === "function") return "a function";
613
+ if (Array.isArray(value)) return "an array";
614
+ if (typeof value === "symbol") return "a symbol";
615
+ return "an object";
616
+ }
147
617
  var InsertBuilder = class _InsertBuilder {
148
618
  constructor(node, source) {
149
619
  this.node = node;
@@ -154,28 +624,66 @@ var InsertBuilder = class _InsertBuilder {
154
624
  with(patch) {
155
625
  return new _InsertBuilder({ ...this.node, ...patch }, this.source);
156
626
  }
157
- /** Provide one row or many rows to insert, typed by the insert shape. */
627
+ /**
628
+ * Provide one row or many rows to insert, typed by the insert shape.
629
+ *
630
+ * @param rows One row, or an array of rows.
631
+ * @returns A builder carrying the rows.
632
+ * @throws ValidationError When a value is not a column value the dialect can
633
+ * bind (see the `sql` helpers for writing an expression instead).
634
+ */
158
635
  values(rows) {
159
636
  const list = Array.isArray(rows) ? rows : [rows];
637
+ for (const row of list) assertWritableValues(this.source, row, "values");
160
638
  return this.with({ values: list });
161
639
  }
162
640
  /**
163
641
  * On a unique/PK conflict on `target`, do nothing (skip the row).
164
642
  *
165
643
  * @param target The conflicting column(s) — a unique or primary key.
644
+ * @param options Pass `where` to name the predicate of a **partial** unique
645
+ * index, which PostgreSQL requires in order to match it as a conflict target.
646
+ * @returns A builder carrying the conflict clause.
647
+ *
648
+ * @example
649
+ * ```ts
650
+ * insert(Outbound)
651
+ * .values(data)
652
+ * .onConflictDoNothing(["consumer", "idempotencyKey"], {
653
+ * where: { idempotencyKey: { isNull: false } },
654
+ * })
655
+ * .returning();
656
+ * ```
166
657
  */
167
- onConflictDoNothing(target) {
168
- return this.with({ onConflict: { target, update: "nothing" } });
658
+ onConflictDoNothing(target, options) {
659
+ return this.with({
660
+ onConflict: {
661
+ target,
662
+ update: "nothing",
663
+ targetWhere: options?.where ? toCondNode(options.where) : void 0
664
+ }
665
+ });
169
666
  }
170
667
  /**
171
668
  * On a unique/PK conflict on `target`, overwrite the given columns (upsert).
172
669
  *
173
670
  * @param target The conflicting column(s) — a unique or primary key.
174
671
  * @param set The columns to update with new values.
672
+ * @param options `indexWhere` names the predicate of a partial unique index
673
+ * (the conflict target); `updateWhere` further restricts which conflicting
674
+ * rows are rewritten.
675
+ * @returns A builder carrying the conflict clause.
676
+ * @throws ValidationError When a `set` value cannot be bound.
175
677
  */
176
- onConflictDoUpdate(target, set) {
678
+ onConflictDoUpdate(target, set, options) {
679
+ assertWritableValues(this.source, set, "set");
177
680
  return this.with({
178
- onConflict: { target, update: set }
681
+ onConflict: {
682
+ target,
683
+ update: set,
684
+ targetWhere: options?.indexWhere ? toCondNode(options.indexWhere) : void 0,
685
+ updateWhere: options?.updateWhere ? toCondNode(options.updateWhere) : void 0
686
+ }
179
687
  });
180
688
  }
181
689
  returning(columns) {
@@ -188,7 +696,8 @@ function insert(model) {
188
696
  kind: "insert",
189
697
  table: model.tablename,
190
698
  values: [],
191
- returning: null
699
+ returning: null,
700
+ names: columnNamesOf(model) ?? void 0
192
701
  },
193
702
  model
194
703
  );
@@ -203,8 +712,27 @@ var UpdateBuilder = class _UpdateBuilder {
203
712
  with(patch) {
204
713
  return new _UpdateBuilder({ ...this.node, ...patch }, this.source);
205
714
  }
206
- /** The columns to write. Partial — only the given columns change. */
715
+ /**
716
+ * The columns to write. Partial — only the given columns change.
717
+ *
718
+ * A value is bound as a parameter unless it is a {@link sql} expression, which
719
+ * is rendered inline instead — that is how a counter is written without a
720
+ * read-modify-write race.
721
+ *
722
+ * @param values The column → value map.
723
+ * @returns A builder carrying the assignments.
724
+ * @throws ValidationError When a value is not a column value the dialect can
725
+ * bind (a bare object, an array on a scalar column, a function).
726
+ *
727
+ * @example
728
+ * ```ts
729
+ * update(Outbound)
730
+ * .set({ attempts: sql.raw("attempts + 1"), updatedAt: sql.now() })
731
+ * .where({ id });
732
+ * ```
733
+ */
207
734
  set(values) {
735
+ assertWritableValues(this.source, values, "set");
208
736
  return this.with({ set: values });
209
737
  }
210
738
  /** Restrict the rows to update. Marks the builder safe to execute. */
@@ -230,7 +758,8 @@ function update(model) {
230
758
  set: {},
231
759
  where: void 0,
232
760
  guarded: false,
233
- returning: null
761
+ returning: null,
762
+ names: columnNamesOf(model) ?? void 0
234
763
  },
235
764
  model
236
765
  );
@@ -267,7 +796,8 @@ function del(model) {
267
796
  table: model.tablename,
268
797
  where: void 0,
269
798
  guarded: false,
270
- returning: null
799
+ returning: null,
800
+ names: columnNamesOf(model) ?? void 0
271
801
  },
272
802
  model
273
803
  );
@@ -362,173 +892,28 @@ function parseDatabaseUrl(url) {
362
892
  if (!dialect) {
363
893
  throw new InvalidDatabaseUrl(url, `unknown dialect ${JSON.stringify(base)}`);
364
894
  }
365
- const rest = url.slice(schemeEnd + 1);
366
- if (dialect === "sqlite") return parseSqlite(url, driver, rest);
367
- return parseNetworkUrl(url, driver, rest, dialect);
368
- }
369
- function detectDialect(url) {
370
- return parseDatabaseUrl(url).dialect;
371
- }
372
-
373
- // src/serialize.ts
374
- var ValidationError = class extends Error {
375
- constructor(table, issues) {
376
- super(`Validation failed for ${table}:
377
- - ${issues.join("\n - ")}`);
378
- this.table = table;
379
- this.issues = issues;
380
- this.name = "ValidationError";
381
- }
382
- table;
383
- issues;
384
- };
385
- function toBase64(bytes) {
386
- if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
387
- let binary = "";
388
- for (const byte of bytes) binary += String.fromCharCode(byte);
389
- return btoa(binary);
390
- }
391
- function fromBase64(value) {
392
- if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64"));
393
- const binary = atob(value);
394
- const bytes = new Uint8Array(binary.length);
395
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
396
- return bytes;
397
- }
398
- function encodeValue(column2, value) {
399
- if (value === null || value === void 0) return null;
400
- switch (column2.type.kind) {
401
- case "bigint":
402
- return typeof value === "bigint" ? value.toString() : value;
403
- case "date":
404
- case "datetime":
405
- case "timestamp":
406
- return value instanceof Date ? value.toISOString() : value;
407
- case "blob":
408
- return value instanceof Uint8Array ? toBase64(value) : value;
409
- default:
410
- return value;
411
- }
412
- }
413
- function decodeValue(column2, value) {
414
- if (value === null || value === void 0) return null;
415
- switch (column2.type.kind) {
416
- case "bigint":
417
- return typeof value === "bigint" ? value : BigInt(value);
418
- case "date":
419
- case "datetime":
420
- case "timestamp":
421
- return value instanceof Date ? value : new Date(value);
422
- case "blob":
423
- return value instanceof Uint8Array ? value : fromBase64(value);
424
- case "json":
425
- return typeof value === "string" ? JSON.parse(value) : value;
426
- case "numeric":
427
- return typeof value === "string" ? value : String(value);
428
- case "boolean":
429
- return typeof value === "boolean" ? value : value === 1 || value === "true";
430
- case "smallint":
431
- case "integer":
432
- case "real":
433
- case "double":
434
- return typeof value === "number" ? value : Number(value);
435
- default:
436
- return value;
437
- }
438
- }
439
- function toDict(model, row) {
440
- const columns = columnsOf(model);
441
- const out = {};
442
- for (const name of Object.keys(columns)) {
443
- out[name] = row[name] ?? null;
444
- }
445
- return out;
446
- }
447
- function toJSON(model, row) {
448
- const columns = columnsOf(model);
449
- const out = {};
450
- for (const [name, col] of Object.entries(columns)) {
451
- out[name] = encodeValue(col, row[name] ?? null);
452
- }
453
- return out;
454
- }
455
- function stringify(model, row) {
456
- return JSON.stringify(toJSON(model, row));
457
- }
458
- function fromDict(model, data) {
459
- const columns = columnsOf(model);
460
- const out = {};
461
- const issues = [];
462
- for (const [name, col] of Object.entries(columns)) {
463
- const present = name in data && data[name] !== void 0 && data[name] !== null;
464
- if (!present) {
465
- const required = col.flags.notNull && !col.flags.hasDefault;
466
- if (required) {
467
- issues.push(`missing required column "${name}"`);
468
- continue;
469
- }
470
- out[name] = null;
471
- continue;
472
- }
473
- try {
474
- out[name] = decodeValue(col, data[name]);
475
- } catch (error) {
476
- issues.push(`column "${name}": ${error.message}`);
477
- }
478
- }
479
- if (issues.length > 0) {
480
- throw new ValidationError(model.tablename, issues);
481
- }
482
- return out;
483
- }
484
- function parse(model, json) {
485
- return fromDict(model, JSON.parse(json));
486
- }
487
- function decoderForKind(kind) {
488
- switch (kind) {
489
- case "bigint":
490
- return (v) => v == null ? null : typeof v === "bigint" ? v : BigInt(v);
491
- case "date":
492
- case "datetime":
493
- case "timestamp":
494
- return (v) => v == null ? null : v instanceof Date ? v : new Date(v);
495
- case "blob":
496
- return (v) => v == null ? null : v instanceof Uint8Array ? v : fromBase64(v);
497
- case "json":
498
- return (v) => v == null ? null : typeof v === "string" ? JSON.parse(v) : v;
499
- case "numeric":
500
- return (v) => v == null ? null : typeof v === "string" ? v : String(v);
501
- case "boolean":
502
- return (v) => v == null ? null : typeof v === "boolean" ? v : v === 1 || v === "true";
503
- case "smallint":
504
- case "integer":
505
- case "real":
506
- case "double":
507
- return (v) => v == null ? null : typeof v === "number" ? v : Number(v);
508
- default:
509
- return null;
510
- }
511
- }
512
- var decoderCache = /* @__PURE__ */ new WeakMap();
513
- function decodersFor(model) {
514
- const cached = decoderCache.get(model);
515
- if (cached) return cached;
516
- const map = /* @__PURE__ */ new Map();
517
- for (const [name, col] of Object.entries(columnsOf(model))) {
518
- const decoder = decoderForKind(col.type.kind);
519
- if (decoder) map.set(name, decoder);
520
- }
521
- decoderCache.set(model, map);
522
- return map;
895
+ const rest = url.slice(schemeEnd + 1);
896
+ if (dialect === "sqlite") return parseSqlite(url, driver, rest);
897
+ return parseNetworkUrl(url, driver, rest, dialect);
523
898
  }
524
- function coerceRow(model, raw) {
525
- const decoders = decodersFor(model);
526
- const out = {};
527
- for (const name of Object.keys(raw)) {
528
- const decode2 = decoders.get(name);
529
- out[name] = decode2 ? decode2(raw[name]) : raw[name];
899
+ function detectDialect(url) {
900
+ return parseDatabaseUrl(url).dialect;
901
+ }
902
+
903
+ // src/expressions.ts
904
+ function renderPortableToken(token, dialect) {
905
+ switch (token) {
906
+ case "now":
907
+ return dialect === "postgresql" ? "now()" : "CURRENT_TIMESTAMP";
908
+ case "current_date":
909
+ return "CURRENT_DATE";
910
+ case "current_time":
911
+ return "CURRENT_TIME";
912
+ case "uuidv4":
913
+ if (dialect === "postgresql") return "gen_random_uuid()";
914
+ if (dialect === "mysql") return "(UUID())";
915
+ return "(lower(hex(randomblob(16))))";
530
916
  }
531
- return out;
532
917
  }
533
918
 
534
919
  // src/dialect.ts
@@ -551,6 +936,20 @@ var Params = class {
551
936
  return this.placeholder(this.values.length);
552
937
  }
553
938
  };
939
+ function insertHasExpression(node) {
940
+ for (const row of node.values) {
941
+ for (const value of Object.values(row)) {
942
+ if (isSqlExpression(value)) return true;
943
+ }
944
+ }
945
+ const update2 = node.onConflict?.update;
946
+ if (update2 && update2 !== "nothing") {
947
+ for (const value of Object.values(update2)) {
948
+ if (isSqlExpression(value)) return true;
949
+ }
950
+ }
951
+ return false;
952
+ }
554
953
  var BaseDialect = class _BaseDialect {
555
954
  /**
556
955
  * INSERT SQL templates keyed by structure (dialect|table|columns|rowCount|
@@ -560,6 +959,30 @@ var BaseDialect = class _BaseDialect {
560
959
  static insertTemplates = /* @__PURE__ */ new Map();
561
960
  /** Quoted-identifier cache (see {@link quoteId}). Shared across dialects. */
562
961
  static quotedIds = /* @__PURE__ */ new Map();
962
+ /**
963
+ * Validate a subquery operand before it is rendered, for dialects that restrict
964
+ * what an `IN (SELECT ...)` may contain. The default accepts everything.
965
+ *
966
+ * @param _node The subquery's AST.
967
+ * @throws Error When the dialect cannot execute this subquery.
968
+ */
969
+ checkSubquery(_node) {
970
+ }
971
+ /**
972
+ * The SQL operator for an array containment/overlap test.
973
+ *
974
+ * Only PostgreSQL has native arrays; the other dialects throw rather than
975
+ * emitting an operator that means something else there.
976
+ *
977
+ * @param op The array operator name.
978
+ * @returns The SQL operator text.
979
+ * @throws Error On a dialect without native array support.
980
+ */
981
+ arrayOperator(op) {
982
+ throw new Error(
983
+ `The "${op}" operator needs native array support, which ${this.name} does not have.`
984
+ );
985
+ }
563
986
  /**
564
987
  * Quote an identifier (column/table) for the active dialect.
565
988
  *
@@ -598,51 +1021,199 @@ var BaseDialect = class _BaseDialect {
598
1021
  }
599
1022
  return { sql: sql2, params: params.values };
600
1023
  }
601
- /** Render a qualified `alias.column` ref as `"alias"."column"`. */
602
- qualify(ref) {
1024
+ /**
1025
+ * Render a qualified `alias.column` ref as `"alias"."column"`, translating the
1026
+ * property name to the real column name for that alias's model.
1027
+ *
1028
+ * @param ref The `alias.property` reference (a bare name is left unqualified).
1029
+ * @param names The node's per-alias name maps, if any source renames columns.
1030
+ * @returns The quoted, qualified identifier.
1031
+ */
1032
+ qualify(ref, names) {
603
1033
  const dot = ref.indexOf(".");
604
1034
  if (dot === -1) return this.quoteId(ref);
605
- return `${this.quoteId(ref.slice(0, dot))}.${this.quoteId(ref.slice(dot + 1))}`;
1035
+ const alias = ref.slice(0, dot);
1036
+ const prop = ref.slice(dot + 1);
1037
+ return `${this.quoteId(alias)}.${this.columnId(prop, names?.[alias])}`;
1038
+ }
1039
+ /**
1040
+ * Quote a column identifier, translating the model property name to the real
1041
+ * database column name first.
1042
+ *
1043
+ * `names` is `undefined` for a model that renames nothing — the overwhelmingly
1044
+ * common case — so this stays a single lookup plus the memoized quote.
1045
+ *
1046
+ * @param prop The model property name as written in the builder.
1047
+ * @param names The node's property → column map, if any.
1048
+ * @returns The quoted database identifier.
1049
+ */
1050
+ columnId(prop, names) {
1051
+ return this.quoteId(names?.[prop] ?? prop);
1052
+ }
1053
+ /**
1054
+ * Render a {@link SqlExpression} inline, binding the parameters it carries.
1055
+ *
1056
+ * This is what keeps `set({ attempts: sql.raw("attempts + 1") })` an expression
1057
+ * instead of a bound object: the fragment goes into the statement text, and
1058
+ * only a `sql.expr` template's interpolations become parameters.
1059
+ *
1060
+ * @param expr The branded expression.
1061
+ * @param params The parameter collector for the statement being compiled.
1062
+ * @returns The SQL text of the expression.
1063
+ */
1064
+ renderExpression(expr, params) {
1065
+ const token = expr.expression;
1066
+ if (typeof token === "string") return renderPortableToken(token, this.name);
1067
+ if ("raw" in token) return token.raw;
1068
+ const parts = token.parts;
1069
+ let sql2 = parts[0] ?? "";
1070
+ for (let i = 1; i < parts.length; i++) {
1071
+ sql2 += `${params.bind(expr.params[i - 1])}${parts[i]}`;
1072
+ }
1073
+ return sql2;
1074
+ }
1075
+ /** Render one write value: a SQL expression inline, anything else as a parameter. */
1076
+ renderValue(value, params) {
1077
+ return isSqlExpression(value) ? this.renderExpression(value, params) : params.bind(value);
1078
+ }
1079
+ /**
1080
+ * Render a row-level locking clause (`FOR UPDATE ...`).
1081
+ *
1082
+ * Standard on PostgreSQL and MySQL 8.0+; SQLite overrides it to throw.
1083
+ *
1084
+ * @param lock The locking clause from the node.
1085
+ * @returns The SQL text, leading space included.
1086
+ */
1087
+ renderLock(lock) {
1088
+ const strength = lock.strength === "update" ? "FOR UPDATE" : "FOR SHARE";
1089
+ const of = lock.of.length > 0 ? ` OF ${lock.of.map((t) => this.quoteId(t)).join(", ")}` : "";
1090
+ const wait = lock.wait === "skipLocked" ? " SKIP LOCKED" : lock.wait === "noWait" ? " NOWAIT" : "";
1091
+ return ` ${strength}${of}${wait}`;
606
1092
  }
607
1093
  // ---- statements -------------------------------------------------------
1094
+ /**
1095
+ * Compile a SELECT.
1096
+ *
1097
+ * Two alias rules differ between clauses and are handled here: PostgreSQL does
1098
+ * NOT accept a `SELECT` alias in `HAVING`, so an aggregate key is re-emitted as
1099
+ * its expression (`COUNT(*) > $1`), a form every dialect accepts; `ORDER BY`,
1100
+ * by contrast, accepts the output alias everywhere, so it is emitted as
1101
+ * written.
1102
+ *
1103
+ * @param node The select AST.
1104
+ * @param params The parameter collector.
1105
+ * @returns The SQL text.
1106
+ */
608
1107
  compileSelect(node, params) {
1108
+ const names = node.names;
609
1109
  let cols;
610
1110
  if (node.aggregates.length > 0) {
611
- const groupSel = node.groupBy.map((c) => this.quoteId(c));
1111
+ const groupSel = node.groupBy.map((c) => this.columnId(c, names));
612
1112
  const aggSel = node.aggregates.map((a) => {
613
- const inner = a.column === "*" ? "*" : this.quoteId(a.column);
1113
+ const inner = a.column === "*" ? "*" : this.columnId(a.column, names);
614
1114
  return `${a.fn.toUpperCase()}(${inner}) AS ${this.quoteId(a.alias)}`;
615
1115
  });
616
1116
  cols = [...groupSel, ...aggSel].join(", ");
617
1117
  } else {
618
- cols = node.columns === "*" ? "*" : node.columns.map((c) => this.quoteId(c)).join(", ");
1118
+ cols = node.columns === "*" ? "*" : node.columns.map((c) => this.columnId(c, names)).join(", ");
619
1119
  }
620
1120
  let sql2 = `SELECT ${node.distinct ? "DISTINCT " : ""}${cols} FROM ${this.quoteId(node.table)}`;
621
- const where = this.compileCondition(node.where, params, (k) => this.quoteId(k));
1121
+ const where = this.compileCondition(
1122
+ node.where,
1123
+ params,
1124
+ (k) => this.columnId(k, names)
1125
+ );
622
1126
  if (where) sql2 += ` WHERE ${where}`;
623
1127
  if (node.groupBy.length > 0) {
624
- sql2 += ` GROUP BY ${node.groupBy.map((c) => this.quoteId(c)).join(", ")}`;
1128
+ sql2 += ` GROUP BY ${node.groupBy.map((c) => this.columnId(c, names)).join(", ")}`;
1129
+ }
1130
+ const aggByAlias = new Map(node.aggregates.map((a) => [a.alias, a]));
1131
+ if (node.having) {
1132
+ const having = this.compileCondition(node.having, params, (key) => {
1133
+ const agg = aggByAlias.get(key);
1134
+ if (!agg) return this.columnId(key, names);
1135
+ const inner = agg.column === "*" ? "*" : this.columnId(agg.column, names);
1136
+ return `${agg.fn.toUpperCase()}(${inner})`;
1137
+ });
1138
+ if (having) sql2 += ` HAVING ${having}`;
625
1139
  }
626
1140
  if (node.orderBy.length > 0) {
627
- const terms = node.orderBy.map(
628
- (t) => `${this.quoteId(t.column)} ${t.direction === "desc" ? "DESC" : "ASC"}`
629
- ).join(", ");
1141
+ const terms = node.orderBy.map((t) => {
1142
+ const id = aggByAlias.has(t.column) ? this.quoteId(t.column) : this.columnId(t.column, names);
1143
+ return `${id} ${t.direction === "desc" ? "DESC" : "ASC"}`;
1144
+ }).join(", ");
630
1145
  sql2 += ` ORDER BY ${terms}`;
631
1146
  }
632
1147
  if (node.limit !== void 0) sql2 += ` LIMIT ${params.bind(node.limit)}`;
633
1148
  if (node.offset !== void 0) sql2 += ` OFFSET ${params.bind(node.offset)}`;
1149
+ if (node.lock) {
1150
+ if (node.distinct || node.groupBy.length > 0 || node.aggregates.length > 0) {
1151
+ throw new Error(
1152
+ "FOR UPDATE / FOR SHARE cannot be combined with DISTINCT or an aggregate query \u2014 lock the underlying rows in a separate SELECT."
1153
+ );
1154
+ }
1155
+ sql2 += this.renderLock(node.lock);
1156
+ }
634
1157
  return sql2;
635
1158
  }
1159
+ /**
1160
+ * Compile an INSERT.
1161
+ *
1162
+ * Takes the cached fast path only when the statement text is a pure function of
1163
+ * its structure. A SQL expression among the values, or a conflict predicate,
1164
+ * makes the text depend on the values themselves — those compile uncached, in
1165
+ * SQL order, so placeholder positions stay correct.
1166
+ */
636
1167
  compileInsert(node, params) {
637
1168
  const columns = node.values.length > 0 ? Object.keys(node.values[0]) : [];
1169
+ const conflict = node.onConflict;
1170
+ const cacheable = conflict?.targetWhere === void 0 && conflict?.updateWhere === void 0 && !insertHasExpression(node);
1171
+ if (!cacheable) return this.compileInsertDirect(node, columns, params);
638
1172
  for (const row of node.values) {
639
1173
  for (const c of columns) params.bind(row[c] ?? null);
640
1174
  }
641
- const conflictCols = node.onConflict && node.onConflict.update !== "nothing" ? Object.keys(node.onConflict.update) : [];
1175
+ const conflictCols = conflict && conflict.update !== "nothing" ? Object.keys(conflict.update) : [];
642
1176
  for (const c of conflictCols) {
643
- params.bind((node.onConflict?.update)[c]);
1177
+ params.bind((conflict?.update)[c]);
1178
+ }
1179
+ return this.insertTemplate(node, columns, conflictCols, params);
1180
+ }
1181
+ /**
1182
+ * Compile an INSERT without the template cache, rendering clauses in statement
1183
+ * order so every parameter is bound at the position it appears.
1184
+ *
1185
+ * @param node The insert node.
1186
+ * @param columns The column keys shared by every row.
1187
+ * @param params The parameter collector.
1188
+ * @returns The SQL text.
1189
+ */
1190
+ compileInsertDirect(node, columns, params) {
1191
+ const names = node.names;
1192
+ const colSql = columns.map((c) => this.columnId(c, names)).join(", ");
1193
+ const rowsSql = node.values.map((row) => {
1194
+ const cells = columns.map(
1195
+ (c) => this.renderValue(row[c] ?? null, params)
1196
+ );
1197
+ return `(${cells.join(", ")})`;
1198
+ }).join(", ");
1199
+ let sql2 = `INSERT INTO ${this.quoteId(node.table)} (${colSql}) VALUES ${rowsSql}`;
1200
+ if (node.onConflict) {
1201
+ const update2 = node.onConflict.update;
1202
+ const conflictCols = update2 === "nothing" ? [] : Object.keys(update2);
1203
+ let cursor = 0;
1204
+ sql2 += this.renderConflict(
1205
+ node.onConflict,
1206
+ conflictCols,
1207
+ () => {
1208
+ const key = conflictCols[cursor++];
1209
+ return this.renderValue(update2[key], params);
1210
+ },
1211
+ names,
1212
+ params
1213
+ );
644
1214
  }
645
- return this.insertTemplate(node, columns, conflictCols);
1215
+ sql2 += this.compileReturning(node.returning, names);
1216
+ return sql2;
646
1217
  }
647
1218
  /**
648
1219
  * The INSERT SQL template for a given structure, cached across calls.
@@ -652,13 +1223,14 @@ var BaseDialect = class _BaseDialect {
652
1223
  * deterministic from the counts (a fresh statement always starts binding at 1).
653
1224
  * So a per-row insert loop compiles the string once and reuses it every row.
654
1225
  */
655
- insertTemplate(node, columns, conflictCols) {
1226
+ insertTemplate(node, columns, conflictCols, params) {
656
1227
  const returningKey = node.returning === null ? "" : node.returning === "*" ? "*" : node.returning.join(",");
657
1228
  const conflictKey = node.onConflict ? `${node.onConflict.target.join(",")}>${node.onConflict.update === "nothing" ? "nothing" : conflictCols.join(",")}` : "";
658
1229
  const key = `${this.name}|${node.table}|${columns.join(",")}|${node.values.length}|${returningKey}|${conflictKey}`;
659
1230
  const cached = _BaseDialect.insertTemplates.get(key);
660
1231
  if (cached !== void 0) return cached;
661
- const colSql = columns.map((c) => this.quoteId(c)).join(", ");
1232
+ const names = node.names;
1233
+ const colSql = columns.map((c) => this.columnId(c, names)).join(", ");
662
1234
  let position = 0;
663
1235
  const rowsSql = node.values.map(() => `(${columns.map(() => this.placeholder(++position)).join(", ")})`).join(", ");
664
1236
  let sql2 = `INSERT INTO ${this.quoteId(node.table)} (${colSql}) VALUES ${rowsSql}`;
@@ -666,57 +1238,91 @@ var BaseDialect = class _BaseDialect {
666
1238
  sql2 += this.renderConflict(
667
1239
  node.onConflict,
668
1240
  conflictCols,
669
- () => this.placeholder(++position)
1241
+ () => this.placeholder(++position),
1242
+ names,
1243
+ params
670
1244
  );
671
1245
  }
672
- sql2 += this.compileReturning(node.returning);
1246
+ sql2 += this.compileReturning(node.returning, names);
673
1247
  _BaseDialect.insertTemplates.set(key, sql2);
674
1248
  return sql2;
675
1249
  }
676
1250
  /**
677
1251
  * Render the conflict-handling clause. Standard SQL (SQLite/PostgreSQL) uses
678
- * `ON CONFLICT (...) DO NOTHING | DO UPDATE SET ...`; MySQL overrides this.
1252
+ * `ON CONFLICT (...) [WHERE predicate] DO NOTHING | DO UPDATE SET ... [WHERE ...]`;
1253
+ * MySQL overrides this.
1254
+ *
1255
+ * The index predicate is rendered before the `DO UPDATE` assignments because
1256
+ * that is where it sits in the statement, so its parameters bind first.
679
1257
  *
680
1258
  * @param onConflict The conflict clause from the node.
681
1259
  * @param conflictCols The columns to overwrite on `DO UPDATE` (empty for nothing).
682
- * @param nextPlaceholder Yields the next positional placeholder (advances the count).
1260
+ * @param nextValue Yields the SQL for the next `DO UPDATE` assignment value.
1261
+ * @param names The node's property → column map, if any.
1262
+ * @param params The parameter collector, for the predicates.
1263
+ * @returns The SQL text, leading space included.
683
1264
  */
684
- renderConflict(onConflict, conflictCols, nextPlaceholder) {
685
- const target = onConflict.target.map((c) => this.quoteId(c)).join(", ");
686
- if (onConflict.update === "nothing") return ` ON CONFLICT (${target}) DO NOTHING`;
687
- const assignments = conflictCols.map((c) => `${this.quoteId(c)} = ${nextPlaceholder()}`).join(", ");
688
- return ` ON CONFLICT (${target}) DO UPDATE SET ${assignments}`;
1265
+ renderConflict(onConflict, conflictCols, nextValue, names, params) {
1266
+ const idFor = (key) => this.columnId(key, names);
1267
+ const target = onConflict.target.map(idFor).join(", ");
1268
+ const indexWhere = this.compileCondition(onConflict.targetWhere, params, idFor);
1269
+ const targetSql = indexWhere ? `(${target}) WHERE ${indexWhere}` : `(${target})`;
1270
+ if (onConflict.update === "nothing") return ` ON CONFLICT ${targetSql} DO NOTHING`;
1271
+ const assignments = conflictCols.map((c) => `${idFor(c)} = ${nextValue()}`).join(", ");
1272
+ let sql2 = ` ON CONFLICT ${targetSql} DO UPDATE SET ${assignments}`;
1273
+ const updateWhere = this.compileCondition(onConflict.updateWhere, params, idFor);
1274
+ if (updateWhere) sql2 += ` WHERE ${updateWhere}`;
1275
+ return sql2;
689
1276
  }
690
1277
  compileUpdate(node, params) {
691
- const sets = Object.entries(node.set).map(([col, value]) => `${this.quoteId(col)} = ${params.bind(value)}`).join(", ");
1278
+ const names = node.names;
1279
+ const sets = Object.entries(node.set).map(
1280
+ ([col2, value]) => `${this.columnId(col2, names)} = ${this.renderValue(value, params)}`
1281
+ ).join(", ");
692
1282
  let sql2 = `UPDATE ${this.quoteId(node.table)} SET ${sets}`;
693
- const where = this.compileCondition(node.where, params, (k) => this.quoteId(k));
1283
+ const where = this.compileCondition(
1284
+ node.where,
1285
+ params,
1286
+ (k) => this.columnId(k, names)
1287
+ );
694
1288
  if (where) sql2 += ` WHERE ${where}`;
695
- sql2 += this.compileReturning(node.returning);
1289
+ sql2 += this.compileReturning(node.returning, names);
696
1290
  return sql2;
697
1291
  }
698
1292
  compileDelete(node, params) {
1293
+ const names = node.names;
699
1294
  let sql2 = `DELETE FROM ${this.quoteId(node.table)}`;
700
- const where = this.compileCondition(node.where, params, (k) => this.quoteId(k));
1295
+ const where = this.compileCondition(
1296
+ node.where,
1297
+ params,
1298
+ (k) => this.columnId(k, names)
1299
+ );
701
1300
  if (where) sql2 += ` WHERE ${where}`;
702
- sql2 += this.compileReturning(node.returning);
1301
+ sql2 += this.compileReturning(node.returning, names);
703
1302
  return sql2;
704
1303
  }
705
1304
  compileJoin(node, params) {
1305
+ const names = node.names;
706
1306
  const cols = node.selections.map((s) => {
707
1307
  const ref = `${s.alias}.${s.column}`;
708
- return `${this.qualify(ref)} AS ${this.quoteId(ref)}`;
1308
+ return `${this.qualify(ref, names)} AS ${this.quoteId(ref)}`;
709
1309
  }).join(", ");
710
1310
  let sql2 = `SELECT ${cols} FROM ${this.quoteId(node.base.table)} AS ${this.quoteId(node.base.alias)}`;
711
1311
  for (const j of node.joins) {
712
1312
  const kw = j.kind === "left" ? "LEFT JOIN" : "INNER JOIN";
713
- const on = j.on.map(([l, r]) => `${this.qualify(l)} = ${this.qualify(r)}`).join(" AND ");
1313
+ const on = j.on.map(([l, r]) => `${this.qualify(l, names)} = ${this.qualify(r, names)}`).join(" AND ");
714
1314
  sql2 += ` ${kw} ${this.quoteId(j.table)} AS ${this.quoteId(j.alias)} ON ${on}`;
715
1315
  }
716
- const where = this.compileCondition(node.where, params, (k) => this.qualify(k));
1316
+ const where = this.compileCondition(
1317
+ node.where,
1318
+ params,
1319
+ (k) => this.qualify(k, names)
1320
+ );
717
1321
  if (where) sql2 += ` WHERE ${where}`;
718
1322
  if (node.orderBy.length > 0) {
719
- const terms = node.orderBy.map((t) => `${this.qualify(t.ref)} ${t.direction === "desc" ? "DESC" : "ASC"}`).join(", ");
1323
+ const terms = node.orderBy.map(
1324
+ (t) => `${this.qualify(t.ref, names)} ${t.direction === "desc" ? "DESC" : "ASC"}`
1325
+ ).join(", ");
720
1326
  sql2 += ` ORDER BY ${terms}`;
721
1327
  }
722
1328
  if (node.limit !== void 0) sql2 += ` LIMIT ${params.bind(node.limit)}`;
@@ -724,10 +1330,10 @@ var BaseDialect = class _BaseDialect {
724
1330
  return sql2;
725
1331
  }
726
1332
  // ---- clauses ----------------------------------------------------------
727
- compileReturning(returning) {
1333
+ compileReturning(returning, names) {
728
1334
  if (returning === null) return "";
729
1335
  if (returning === "*") return " RETURNING *";
730
- return ` RETURNING ${returning.map((c) => this.quoteId(c)).join(", ")}`;
1336
+ return ` RETURNING ${returning.map((c) => this.columnId(c, names)).join(", ")}`;
731
1337
  }
732
1338
  /**
733
1339
  * Compile a condition tree (fields / and / or / not) to SQL. `idFor` renders a
@@ -764,6 +1370,83 @@ var BaseDialect = class _BaseDialect {
764
1370
  const inner = this.compileCondition(node.part, params, idFor);
765
1371
  return inner ? `NOT (${inner})` : "";
766
1372
  }
1373
+ case "compare": {
1374
+ const left = this.renderExpr(node.left, params, idFor);
1375
+ if (node.right.kind === "value") {
1376
+ return this.compileOperator(left, node.op, node.right.value, params);
1377
+ }
1378
+ return this.compileExprOperator(
1379
+ left,
1380
+ node.op,
1381
+ this.renderExpr(node.right, params, idFor)
1382
+ );
1383
+ }
1384
+ }
1385
+ }
1386
+ /**
1387
+ * Render one side of a comparison.
1388
+ *
1389
+ * A column reference goes through `idFor`, so an explicit `.name()` mapping and
1390
+ * join qualification apply here exactly as they do in the object form of
1391
+ * `where` — `col()` is not a way around them. Only a `value` node binds.
1392
+ *
1393
+ * @param node The expression AST.
1394
+ * @param params The parameter collector.
1395
+ * @param idFor The identifier resolver for the enclosing statement.
1396
+ * @returns The SQL text of the expression.
1397
+ */
1398
+ renderExpr(node, params, idFor) {
1399
+ switch (node.kind) {
1400
+ case "column":
1401
+ return idFor(node.name);
1402
+ case "value":
1403
+ return params.bind(node.value);
1404
+ case "fn": {
1405
+ const args = node.args.map((a) => this.renderExpr(a, params, idFor)).join(", ");
1406
+ return `${node.name}(${args})`;
1407
+ }
1408
+ }
1409
+ }
1410
+ /**
1411
+ * Compile a comparison whose right-hand side is another expression rather than
1412
+ * a bound value (`total > paid`, `lower(a) = lower(b)`).
1413
+ *
1414
+ * The list and null operators are excluded: `IN`, `BETWEEN` and `IS NULL` take
1415
+ * a value operand, and accepting an expression there would silently compile to
1416
+ * something else.
1417
+ *
1418
+ * @param left The rendered left-hand side.
1419
+ * @param op The operator name.
1420
+ * @param right The rendered right-hand side.
1421
+ * @returns The SQL text of the predicate.
1422
+ * @throws Error When the operator needs a value operand.
1423
+ */
1424
+ compileExprOperator(left, op, right) {
1425
+ switch (op) {
1426
+ case "eq":
1427
+ return `${left} = ${right}`;
1428
+ case "ne":
1429
+ return `${left} <> ${right}`;
1430
+ case "gt":
1431
+ return `${left} > ${right}`;
1432
+ case "gte":
1433
+ return `${left} >= ${right}`;
1434
+ case "lt":
1435
+ return `${left} < ${right}`;
1436
+ case "lte":
1437
+ return `${left} <= ${right}`;
1438
+ case "like":
1439
+ return `${left} LIKE ${right}`;
1440
+ case "ilike":
1441
+ return this.ilike(left, right);
1442
+ case "ieq":
1443
+ return `lower(${left}) = lower(${right})`;
1444
+ case "contains":
1445
+ case "containedBy":
1446
+ case "overlaps":
1447
+ return `${left} ${this.arrayOperator(op)} ${right}`;
1448
+ default:
1449
+ throw new Error(`The "${op}" operator takes a value operand, not an expression.`);
767
1450
  }
768
1451
  }
769
1452
  compileOperator(id, op, operand, params) {
@@ -784,6 +1467,14 @@ var BaseDialect = class _BaseDialect {
784
1467
  return `${id} LIKE ${params.bind(operand)}`;
785
1468
  case "ilike":
786
1469
  return this.ilike(id, params.bind(operand));
1470
+ case "ieq":
1471
+ return operand === null ? `${id} IS NULL` : `lower(${id}) = lower(${params.bind(operand)})`;
1472
+ case "contains":
1473
+ return `${id} ${this.arrayOperator("contains")} ${params.bind(operand)}`;
1474
+ case "containedBy":
1475
+ return `${id} ${this.arrayOperator("containedBy")} ${params.bind(operand)}`;
1476
+ case "overlaps":
1477
+ return `${id} ${this.arrayOperator("overlaps")} ${params.bind(operand)}`;
787
1478
  case "in":
788
1479
  return this.compileIn(id, operand, params, false);
789
1480
  case "notIn":
@@ -798,12 +1489,33 @@ var BaseDialect = class _BaseDialect {
798
1489
  throw new Error(`Unknown operator ${JSON.stringify(op)}`);
799
1490
  }
800
1491
  }
801
- compileIn(id, values, params, negate) {
1492
+ /**
1493
+ * Compile `IN` / `NOT IN`, whose operand is either a value list or a
1494
+ * single-column subquery.
1495
+ *
1496
+ * The subquery is rendered at the position it appears in the outer statement
1497
+ * and shares the same parameter collector, so its own placeholders land in the
1498
+ * right order — and it keeps its own `names` map, since the inner model may use
1499
+ * a different naming convention than the outer one.
1500
+ *
1501
+ * @param id The quoted column identifier being tested.
1502
+ * @param operand A list of values, or a {@link Subquery}.
1503
+ * @param params The parameter collector for the statement being compiled.
1504
+ * @param negate True for `NOT IN`.
1505
+ * @returns The SQL text of the predicate.
1506
+ */
1507
+ compileIn(id, operand, params, negate) {
1508
+ const keyword = negate ? "NOT IN" : "IN";
1509
+ if (isSubquery(operand)) {
1510
+ this.checkSubquery(operand.node);
1511
+ return `${id} ${keyword} (${this.compileSelect(operand.node, params)})`;
1512
+ }
1513
+ const values = operand;
802
1514
  if (values.length === 0) {
803
1515
  return negate ? "1 = 1" : "1 = 0";
804
1516
  }
805
1517
  const list = values.map((v) => params.bind(v)).join(", ");
806
- return `${id} ${negate ? "NOT IN" : "IN"} (${list})`;
1518
+ return `${id} ${keyword} (${list})`;
807
1519
  }
808
1520
  };
809
1521
  var SqliteDialect = class extends BaseDialect {
@@ -814,6 +1526,16 @@ var SqliteDialect = class extends BaseDialect {
814
1526
  ilike(column2, param) {
815
1527
  return `${column2} LIKE ${param}`;
816
1528
  }
1529
+ /**
1530
+ * SQLite has no row-level locking, so a lock request is an error rather than a
1531
+ * silently unlocked `SELECT` — a lock that does not exist only shows up as
1532
+ * duplicated work under production concurrency.
1533
+ */
1534
+ renderLock() {
1535
+ throw new Error(
1536
+ "SQLite has no row-level locking \u2014 FOR UPDATE / FOR SHARE is unsupported. Serialize the claim inside a transaction instead."
1537
+ );
1538
+ }
817
1539
  };
818
1540
  var PostgresDialect = class extends BaseDialect {
819
1541
  name = "postgresql";
@@ -823,6 +1545,11 @@ var PostgresDialect = class extends BaseDialect {
823
1545
  ilike(column2, param) {
824
1546
  return `${column2} ILIKE ${param}`;
825
1547
  }
1548
+ arrayOperator(op) {
1549
+ if (op === "contains") return "@>";
1550
+ if (op === "containedBy") return "<@";
1551
+ return "&&";
1552
+ }
826
1553
  };
827
1554
  var mysqlQuotedIds = /* @__PURE__ */ new Map();
828
1555
  var MysqlDialect = class extends BaseDialect {
@@ -840,18 +1567,45 @@ var MysqlDialect = class extends BaseDialect {
840
1567
  mysqlQuotedIds.set(name, quoted);
841
1568
  return quoted;
842
1569
  }
843
- renderConflict(onConflict, conflictCols, nextPlaceholder) {
1570
+ /**
1571
+ * MySQL rejects `LIMIT` inside an `IN` subquery with
1572
+ * `ER_NOT_SUPPORTED_YET: This version of MySQL doesn't yet support
1573
+ * 'LIMIT & IN/ALL/ANY/SOME subquery'`. Failing at compile time names the fix
1574
+ * instead of surfacing that error from the driver at runtime.
1575
+ */
1576
+ checkSubquery(node) {
1577
+ if (node.limit !== void 0 || node.offset !== void 0) {
1578
+ throw new Error(
1579
+ "MySQL does not support LIMIT/OFFSET inside an IN subquery. Select the ids first and pass them as a list, or wrap the subquery in a derived table."
1580
+ );
1581
+ }
1582
+ }
1583
+ renderConflict(onConflict, conflictCols, nextValue, names) {
1584
+ if (onConflict.targetWhere || onConflict.updateWhere) {
1585
+ throw new Error(
1586
+ "MySQL's ON DUPLICATE KEY UPDATE has no conflict-target predicate \u2014 a partial unique index is PostgreSQL/SQLite only."
1587
+ );
1588
+ }
844
1589
  if (onConflict.update === "nothing") {
845
- const col = this.quoteId(onConflict.target[0] ?? "id");
846
- return ` ON DUPLICATE KEY UPDATE ${col} = ${col}`;
1590
+ const col2 = this.columnId(onConflict.target[0] ?? "id", names);
1591
+ return ` ON DUPLICATE KEY UPDATE ${col2} = ${col2}`;
847
1592
  }
848
- const assignments = conflictCols.map((c) => `${this.quoteId(c)} = ${nextPlaceholder()}`).join(", ");
1593
+ const assignments = conflictCols.map((c) => `${this.columnId(c, names)} = ${nextValue()}`).join(", ");
849
1594
  return ` ON DUPLICATE KEY UPDATE ${assignments}`;
850
1595
  }
1596
+ /**
1597
+ * MySQL has no `RETURNING`, so it cannot be compiled into a statement.
1598
+ *
1599
+ * `session.execute()` still honors `.returning()` on a **single-row INSERT** by
1600
+ * running the insert and reading the row back by key on the same connection —
1601
+ * that is execution, not compilation, so it never reaches here. Compiling a
1602
+ * node with `returning` directly is an error, rather than SQL that silently
1603
+ * returns nothing.
1604
+ */
851
1605
  compileReturning(returning) {
852
1606
  if (returning === null) return "";
853
1607
  throw new Error(
854
- "RETURNING is not supported on MySQL \u2014 insert, then SELECT by key (e.g. LAST_INSERT_ID())."
1608
+ "RETURNING cannot be compiled for MySQL. session.execute() reads a single-row INSERT back by key (LAST_INSERT_ID()); UPDATE/DELETE have no equivalent \u2014 run a SELECT yourself."
855
1609
  );
856
1610
  }
857
1611
  };
@@ -870,6 +1624,11 @@ function getDialect(name) {
870
1624
  function selectionsFor(alias, model) {
871
1625
  return Object.keys(columnsOf(model)).map((column2) => ({ alias, column: column2 }));
872
1626
  }
1627
+ function withAliasNames(current, alias, model) {
1628
+ const names = columnNamesOf(model);
1629
+ if (!names) return current;
1630
+ return { ...current ?? {}, [alias]: names };
1631
+ }
873
1632
  var JoinBuilder = class _JoinBuilder {
874
1633
  constructor(node, sources) {
875
1634
  this.node = node;
@@ -882,7 +1641,8 @@ var JoinBuilder = class _JoinBuilder {
882
1641
  {
883
1642
  ...this.node,
884
1643
  joins: [...this.node.joins, clause],
885
- selections: [...this.node.selections, ...selectionsFor(clause.alias, model)]
1644
+ selections: [...this.node.selections, ...selectionsFor(clause.alias, model)],
1645
+ names: withAliasNames(this.node.names, clause.alias, model)
886
1646
  },
887
1647
  { ...this.sources, [clause.alias]: model }
888
1648
  );
@@ -943,7 +1703,8 @@ function join(model, alias) {
943
1703
  where: void 0,
944
1704
  orderBy: [],
945
1705
  limit: void 0,
946
- offset: void 0
1706
+ offset: void 0,
1707
+ names: withAliasNames(void 0, alias, model)
947
1708
  },
948
1709
  { [alias]: model }
949
1710
  );
@@ -957,8 +1718,8 @@ var RecordNotFound = class extends Error {
957
1718
  }
958
1719
  };
959
1720
  function primaryKeyOf(model) {
960
- for (const [name, col] of Object.entries(columnsOf(model))) {
961
- if (col.flags.primaryKey) return name;
1721
+ for (const [name, col2] of Object.entries(columnsOf(model))) {
1722
+ if (col2.flags.primaryKey) return name;
962
1723
  }
963
1724
  throw new Error(`${model.tablename} has no primary key`);
964
1725
  }
@@ -1046,8 +1807,8 @@ var BaseRepository = class {
1046
1807
 
1047
1808
  // src/active-record.ts
1048
1809
  function primaryKeyOf2(model) {
1049
- for (const [name, col] of Object.entries(columnsOf(model))) {
1050
- if (col.flags.primaryKey) return name;
1810
+ for (const [name, col2] of Object.entries(columnsOf(model))) {
1811
+ if (col2.flags.primaryKey) return name;
1051
1812
  }
1052
1813
  throw new Error(`${model.tablename} has no primary key`);
1053
1814
  }
@@ -1256,11 +2017,12 @@ function splitJoinRow(node, sources, raw) {
1256
2017
  const out = {};
1257
2018
  for (const [alias, model] of Object.entries(sources)) {
1258
2019
  const sub = {};
2020
+ const names = columnNamesOf(model);
1259
2021
  let allNull = true;
1260
2022
  for (const colName of Object.keys(columnsOf(model))) {
1261
2023
  const value = raw[`${alias}.${colName}`];
1262
2024
  if (value !== null && value !== void 0) allNull = false;
1263
- sub[colName] = value;
2025
+ sub[names?.[colName] ?? colName] = value;
1264
2026
  }
1265
2027
  out[alias] = leftAliases.has(alias) && allNull ? null : coerceRow(model, sub);
1266
2028
  }
@@ -1382,6 +2144,25 @@ var AsyncResult = class {
1382
2144
  }
1383
2145
  };
1384
2146
  var savepointCounter = 0;
2147
+ function needsInsertReadBack(dialect, node) {
2148
+ return dialect.name === "mysql" && node.kind === "insert" && node.returning !== null;
2149
+ }
2150
+ function singlePrimaryKey(model) {
2151
+ const keys = Object.entries(columnsOf(model)).filter(([, column2]) => column2.flags.primaryKey).map(([name]) => name);
2152
+ if (keys.length !== 1) {
2153
+ throw new Error(
2154
+ `${model.tablename} needs exactly one primary key to read an insert back on a dialect without RETURNING; found ${keys.length}.`
2155
+ );
2156
+ }
2157
+ return keys[0];
2158
+ }
2159
+ function assertRawParams(params) {
2160
+ if (!Array.isArray(params)) {
2161
+ throw new TypeError(
2162
+ "session.raw(sql, params) takes an array of bound parameters \u2014 never interpolate values into the SQL string."
2163
+ );
2164
+ }
2165
+ }
1385
2166
  var SyncSession = class {
1386
2167
  constructor(driver, dialect, logger) {
1387
2168
  this.driver = driver;
@@ -1400,6 +2181,44 @@ var SyncSession = class {
1400
2181
  throw new QueryExecutionError(error, sql2, params);
1401
2182
  }
1402
2183
  }
2184
+ /**
2185
+ * Run a raw, parameterized SQL statement (synchronous) — the runtime counterpart of the
2186
+ * migrations' `Op.execute`.
2187
+ *
2188
+ * A query builder never covers all of SQL, and without an escape hatch a single
2189
+ * unsupported query forces a whole second database stack alongside this one. Use
2190
+ * it for what the builder cannot yet express, and keep everything else typed.
2191
+ *
2192
+ * The statement goes through the same path as a compiled one: it is logged via
2193
+ * `onQuery`, wrapped in {@link QueryExecutionError} on failure, and runs on the
2194
+ * reserved connection inside `transaction()`.
2195
+ *
2196
+ * @param sql The statement text. Placeholders only (`$1` / `?` per dialect) —
2197
+ * never interpolate a value into this string.
2198
+ * @param params The bound parameters, in placeholder order.
2199
+ * @param options Pass `as` to coerce the returned rows with a model's column
2200
+ * types (and its column-name mapping).
2201
+ * @returns The result view over the returned rows.
2202
+ * @throws Error When `params` is not an array — the guard against calling this
2203
+ * with an interpolated string and no parameters by mistake.
2204
+ *
2205
+ * @example
2206
+ * ```ts
2207
+ * const claimed = await session.raw<OutboundRow>(
2208
+ * `UPDATE outbound_messages SET status = 'sending'
2209
+ * WHERE id = ANY($1) RETURNING *`,
2210
+ * [ids],
2211
+ * { as: Outbound },
2212
+ * ).all();
2213
+ * ```
2214
+ */
2215
+ raw(sql2, params = [], options) {
2216
+ assertRawParams(params);
2217
+ const result = this.exec(sql2, params);
2218
+ const model = options?.as;
2219
+ const rows = model ? result.rows.map((row) => coerceRow(model, row)) : result.rows;
2220
+ return new SyncResult(rows, result.changes);
2221
+ }
1403
2222
  /** Compile, run, and coerce a builder into a result. */
1404
2223
  execute(builder) {
1405
2224
  const node = builder.node;
@@ -1409,10 +2228,10 @@ var SyncSession = class {
1409
2228
  return new SyncResult(rows, result.changes);
1410
2229
  }
1411
2230
  /** Run `fn` inside a transaction: commit on success, rollback on throw. */
1412
- transaction(fn) {
2231
+ transaction(fn2) {
1413
2232
  this.exec("BEGIN", []);
1414
2233
  try {
1415
- const out = fn(this);
2234
+ const out = fn2(this);
1416
2235
  this.exec("COMMIT", []);
1417
2236
  return out;
1418
2237
  } catch (error) {
@@ -1421,12 +2240,12 @@ var SyncSession = class {
1421
2240
  }
1422
2241
  }
1423
2242
  /** Run `fn` inside a SAVEPOINT (nested transaction). */
1424
- beginNested(fn) {
2243
+ beginNested(fn2) {
1425
2244
  savepointCounter += 1;
1426
2245
  const name = `qsp_${savepointCounter}`;
1427
2246
  this.exec(`SAVEPOINT ${name}`, []);
1428
2247
  try {
1429
- const out = fn(this);
2248
+ const out = fn2(this);
1430
2249
  this.exec(`RELEASE ${name}`, []);
1431
2250
  return out;
1432
2251
  } catch (error) {
@@ -1482,8 +2301,53 @@ var AsyncSession = class _AsyncSession {
1482
2301
  throw new QueryExecutionError(error, sql2, params);
1483
2302
  }
1484
2303
  }
2304
+ /**
2305
+ * Run a raw, parameterized SQL statement — the runtime counterpart of the
2306
+ * migrations' `Op.execute`.
2307
+ *
2308
+ * A query builder never covers all of SQL, and without an escape hatch a single
2309
+ * unsupported query forces a whole second database stack alongside this one. Use
2310
+ * it for what the builder cannot yet express, and keep everything else typed.
2311
+ *
2312
+ * The statement goes through the same path as a compiled one: it is logged via
2313
+ * `onQuery`, wrapped in {@link QueryExecutionError} on failure, and runs on the
2314
+ * reserved connection inside `transaction()`.
2315
+ *
2316
+ * @param sql The statement text. Placeholders only (`$1` / `?` per dialect) —
2317
+ * never interpolate a value into this string.
2318
+ * @param params The bound parameters, in placeholder order.
2319
+ * @param options Pass `as` to coerce the returned rows with a model's column
2320
+ * types (and its column-name mapping).
2321
+ * @returns The result view over the returned rows.
2322
+ * @throws Error When `params` is not an array — the guard against calling this
2323
+ * with an interpolated string and no parameters by mistake.
2324
+ *
2325
+ * @example
2326
+ * ```ts
2327
+ * const claimed = await session.raw<OutboundRow>(
2328
+ * `UPDATE outbound_messages SET status = 'sending'
2329
+ * WHERE id = ANY($1) RETURNING *`,
2330
+ * [ids],
2331
+ * { as: Outbound },
2332
+ * ).all();
2333
+ * ```
2334
+ */
2335
+ raw(sql2, params = [], options) {
2336
+ assertRawParams(params);
2337
+ const model = options?.as;
2338
+ const inner = this.exec(sql2, params).then((result) => {
2339
+ const rows = model ? result.rows.map((row) => coerceRow(model, row)) : result.rows;
2340
+ return new SyncResult(rows, result.changes);
2341
+ });
2342
+ return new AsyncResult(inner);
2343
+ }
1485
2344
  execute(builder) {
1486
2345
  const node = builder.node;
2346
+ if (needsInsertReadBack(this.dialect, node)) {
2347
+ return new AsyncResult(
2348
+ this.insertAndReadBack(builder, node)
2349
+ );
2350
+ }
1487
2351
  const { sql: sql2, params } = this.dialect.compile(node);
1488
2352
  const inner = this.exec(sql2, params).then((result) => {
1489
2353
  const rows = mapRows(builder, result.rows);
@@ -1491,6 +2355,53 @@ var AsyncSession = class _AsyncSession {
1491
2355
  });
1492
2356
  return new AsyncResult(inner);
1493
2357
  }
2358
+ /**
2359
+ * Honor `.returning()` on a dialect without `RETURNING`, by inserting and then
2360
+ * reading the row back by key.
2361
+ *
2362
+ * Both statements must run on **one** connection, because `LAST_INSERT_ID()` is
2363
+ * per-connection: outside a transaction the pooled driver is reserved for the
2364
+ * pair; inside one, the session already holds a pinned connection (a reserved
2365
+ * driver exposes no `reserve`), so it runs there directly.
2366
+ *
2367
+ * @param builder The insert builder, for its source model.
2368
+ * @param node The insert AST, whose `returning` drives the read-back.
2369
+ * @returns The result view over the read-back row.
2370
+ * @throws Error When the insert writes more than one row — `LAST_INSERT_ID()`
2371
+ * identifies only the first, and the rest are consecutive only under some
2372
+ * auto-increment lock modes.
2373
+ */
2374
+ async insertAndReadBack(builder, node) {
2375
+ if (node.values.length !== 1) {
2376
+ throw new Error(
2377
+ `${this.dialect.name} has no RETURNING, and reading back a multi-row insert is not reliable \u2014 insert one row at a time, or drop .returning().`
2378
+ );
2379
+ }
2380
+ const model = builder.source;
2381
+ const pk = singlePrimaryKey(model);
2382
+ const supplied = node.values[0][pk];
2383
+ const readBack = select(model).where(
2384
+ supplied === void 0 || supplied === null ? col(pk).eq(fn.call("LAST_INSERT_ID")) : { [pk]: supplied }
2385
+ );
2386
+ const insertSql = this.dialect.compile({ ...node, returning: null });
2387
+ const selectSql = this.dialect.compile(
2388
+ node.returning === "*" || node.returning === null ? readBack.node : { ...readBack.node, columns: node.returning }
2389
+ );
2390
+ const run = async (driver) => {
2391
+ const scoped = new _AsyncSession(driver, this.dialect, this.logger);
2392
+ const written = await scoped.exec(insertSql.sql, insertSql.params);
2393
+ const read = await scoped.exec(selectSql.sql, selectSql.params);
2394
+ const rows = read.rows.map((row) => coerceRow(model, row));
2395
+ return new SyncResult(rows, written.changes);
2396
+ };
2397
+ if (!this.driver.reserve) return run(this.driver);
2398
+ const reserved = await this.driver.reserve();
2399
+ try {
2400
+ return await run(reserved);
2401
+ } finally {
2402
+ await reserved.release();
2403
+ }
2404
+ }
1494
2405
  /** Lazily iterate result rows. Uses driver streaming when available. */
1495
2406
  async *stream(builder) {
1496
2407
  const node = builder.node;
@@ -1511,13 +2422,13 @@ var AsyncSession = class _AsyncSession {
1511
2422
  yield coerceOne(builder, raw);
1512
2423
  }
1513
2424
  }
1514
- async transaction(fn) {
2425
+ async transaction(fn2) {
1515
2426
  if (this.driver.reserve) {
1516
2427
  const reserved = await this.driver.reserve();
1517
2428
  const scoped = new _AsyncSession(reserved, this.dialect, this.logger);
1518
2429
  try {
1519
2430
  await scoped.exec("BEGIN", []);
1520
- const out = await fn(scoped);
2431
+ const out = await fn2(scoped);
1521
2432
  await scoped.exec("COMMIT", []);
1522
2433
  return out;
1523
2434
  } catch (error) {
@@ -1529,7 +2440,7 @@ var AsyncSession = class _AsyncSession {
1529
2440
  }
1530
2441
  await this.exec("BEGIN", []);
1531
2442
  try {
1532
- const out = await fn(this);
2443
+ const out = await fn2(this);
1533
2444
  await this.exec("COMMIT", []);
1534
2445
  return out;
1535
2446
  } catch (error) {
@@ -1556,8 +2467,8 @@ var SyncEngine = class {
1556
2467
  session() {
1557
2468
  return new SyncSession(this.driver, getDialect("sqlite"), this.logger);
1558
2469
  }
1559
- transaction(fn) {
1560
- return this.session().transaction(fn);
2470
+ transaction(fn2) {
2471
+ return this.session().transaction(fn2);
1561
2472
  }
1562
2473
  close() {
1563
2474
  this.driver.close();
@@ -1579,8 +2490,8 @@ var AsyncEngine = class {
1579
2490
  session() {
1580
2491
  return new AsyncSession(this.driver, getDialect(this.dialect), this.logger);
1581
2492
  }
1582
- transaction(fn) {
1583
- return this.session().transaction(fn);
2493
+ transaction(fn2) {
2494
+ return this.session().transaction(fn2);
1584
2495
  }
1585
2496
  async close() {
1586
2497
  await this.driver.close();
@@ -1590,6 +2501,16 @@ var AsyncEngine = class {
1590
2501
  await this.close();
1591
2502
  }
1592
2503
  };
2504
+ function toAsyncDriver(driver) {
2505
+ return {
2506
+ async execute(sql2, params) {
2507
+ return await driver.execute(sql2, params);
2508
+ },
2509
+ async close() {
2510
+ await driver.close();
2511
+ }
2512
+ };
2513
+ }
1593
2514
  function asAsync(driver) {
1594
2515
  const syncIterate = driver.iterate?.bind(driver);
1595
2516
  return {
@@ -1751,24 +2672,58 @@ var DEFAULT_FLAGS = {
1751
2672
  hasDefault: false,
1752
2673
  unique: false
1753
2674
  };
2675
+ var EXPRESSION = /* @__PURE__ */ Symbol.for("tempest-db-js.expression");
2676
+ function expression(token, params = []) {
2677
+ return { [EXPRESSION]: true, kind: "expression", expression: token, params };
2678
+ }
2679
+ function isSqlExpression(value) {
2680
+ return typeof value === "object" && value !== null && value[EXPRESSION] === true;
2681
+ }
1754
2682
  var sql = {
1755
2683
  /** Current timestamp at insert (`CURRENT_TIMESTAMP` / `now()`). */
1756
- now: () => ({ kind: "expression", expression: "now" }),
2684
+ now: () => expression("now"),
1757
2685
  /** Current date. */
1758
- currentDate: () => ({ kind: "expression", expression: "current_date" }),
2686
+ currentDate: () => expression("current_date"),
1759
2687
  /** Current time. */
1760
- currentTime: () => ({ kind: "expression", expression: "current_time" }),
2688
+ currentTime: () => expression("current_time"),
1761
2689
  /** A freshly generated UUID v4 (`gen_random_uuid()` / portable fallback). */
1762
- uuidv4: () => ({ kind: "expression", expression: "uuidv4" }),
1763
- /** Escape hatch: a verbatim SQL expression rendered as-is. */
1764
- raw: (expression) => ({
1765
- kind: "expression",
1766
- expression: { raw: expression }
1767
- })
2690
+ uuidv4: () => expression("uuidv4"),
2691
+ /**
2692
+ * Escape hatch: a verbatim SQL expression rendered as-is, with no parameters.
2693
+ *
2694
+ * The fragment is interpolated into the statement untouched, so it must never
2695
+ * carry user input — use {@link sql.expr} when a value has to be bound.
2696
+ *
2697
+ * @param fragment The SQL text (e.g. `"attempts + 1"`).
2698
+ * @returns The expression, usable as a default and as a write value.
2699
+ */
2700
+ raw: (fragment) => expression({ raw: fragment }),
2701
+ /**
2702
+ * A parameterized SQL expression, written as a tagged template. Static text is
2703
+ * SQL; every `${...}` interpolation becomes a bound parameter, so the fragment
2704
+ * is injection-safe by construction.
2705
+ *
2706
+ * Cannot be used as a column default — a `DEFAULT` clause has nowhere to bind
2707
+ * parameters; use {@link sql.raw} there.
2708
+ *
2709
+ * @param parts The static SQL segments supplied by the template tag.
2710
+ * @param values The interpolated values, bound in order.
2711
+ * @returns The expression, usable as a write value.
2712
+ *
2713
+ * @example
2714
+ * ```ts
2715
+ * update(Account).set({ balance: sql.expr`balance - ${amount}` }).where({ id });
2716
+ * // UPDATE "accounts" SET "balance" = balance - $1 WHERE "id" = $2
2717
+ * ```
2718
+ */
2719
+ expr: (parts, ...values) => expression({ parts: Array.from(parts) }, values)
1768
2720
  };
1769
2721
  function isDefaultValue(value) {
1770
2722
  return typeof value === "object" && value !== null && "kind" in value && (value.kind === "literal" || value.kind === "expression");
1771
2723
  }
2724
+ function bindsParameters(value) {
2725
+ return value.kind === "expression" && typeof value.expression === "object" && "parts" in value.expression;
2726
+ }
1772
2727
  function parseReference(ref, options) {
1773
2728
  const dot = ref.lastIndexOf(".");
1774
2729
  if (dot <= 0 || dot === ref.length - 1) {
@@ -1782,48 +2737,71 @@ function parseReference(ref, options) {
1782
2737
  };
1783
2738
  }
1784
2739
  var Column = class _Column {
1785
- constructor(type, flags, defaultValue = null, onUpdateValue = null, reference = null) {
2740
+ constructor(type, flags, defaultValue = null, onUpdateValue = null, reference = null, dbName = null) {
1786
2741
  this.type = type;
1787
2742
  this.flags = flags;
1788
2743
  this.defaultValue = defaultValue;
1789
2744
  this.onUpdateValue = onUpdateValue;
1790
2745
  this.reference = reference;
2746
+ this.dbName = dbName;
1791
2747
  }
1792
2748
  type;
1793
2749
  flags;
1794
2750
  defaultValue;
1795
2751
  onUpdateValue;
1796
2752
  reference;
1797
- primaryKey() {
2753
+ dbName;
2754
+ /** Clone this column with one facet replaced, carrying every other over. */
2755
+ derive(patch) {
1798
2756
  return new _Column(
1799
2757
  this.type,
1800
- { ...this.flags, primaryKey: true, hasDefault: true },
1801
- this.defaultValue,
1802
- this.onUpdateValue,
1803
- this.reference
2758
+ patch.flags ?? this.flags,
2759
+ patch.defaultValue !== void 0 ? patch.defaultValue : this.defaultValue,
2760
+ patch.onUpdateValue !== void 0 ? patch.onUpdateValue : this.onUpdateValue,
2761
+ patch.reference !== void 0 ? patch.reference : this.reference,
2762
+ patch.dbName !== void 0 ? patch.dbName : this.dbName
1804
2763
  );
1805
2764
  }
2765
+ primaryKey() {
2766
+ return this.derive({ flags: { ...this.flags, primaryKey: true, hasDefault: true } });
2767
+ }
1806
2768
  notNull() {
1807
- return new _Column(
1808
- this.type,
1809
- { ...this.flags, notNull: true },
1810
- this.defaultValue,
1811
- this.onUpdateValue,
1812
- this.reference
1813
- );
2769
+ return this.derive({ flags: { ...this.flags, notNull: true } });
1814
2770
  }
1815
2771
  /**
1816
2772
  * Add a `UNIQUE` constraint to the column (mirrors SQLAlchemy's
1817
2773
  * `mapped_column(unique=True)`). DDL-only — does not change the inferred type.
1818
2774
  */
1819
2775
  unique() {
1820
- return new _Column(
1821
- this.type,
1822
- { ...this.flags, unique: true },
1823
- this.defaultValue,
1824
- this.onUpdateValue,
1825
- this.reference
1826
- );
2776
+ return this.derive({ flags: { ...this.flags, unique: true } });
2777
+ }
2778
+ /**
2779
+ * Map this property to a differently-named database column, à la SQLAlchemy's
2780
+ * `mapped_column("consumer_name")` (Django's `db_column`, Prisma's `@map`).
2781
+ *
2782
+ * The override applies everywhere the name reaches SQL — select, insert,
2783
+ * update, delete, where, order by, group by, returning, conflict targets, the
2784
+ * migration IR and the drift check — while the TypeScript row keeps the
2785
+ * property name. Use it to keep a `snake_case` schema behind a `camelCase`
2786
+ * model; {@link Model.naming} does the same for a whole table at once.
2787
+ *
2788
+ * @param dbName The real column name in the database.
2789
+ * @returns A new column bound to that name.
2790
+ * @throws Error When `dbName` is empty.
2791
+ *
2792
+ * @example
2793
+ * ```ts
2794
+ * class ApiKey extends Model {
2795
+ * static tablename = "api_keys";
2796
+ * consumerName = column.text().name("consumer_name").notNull();
2797
+ * }
2798
+ * ```
2799
+ */
2800
+ name(dbName) {
2801
+ if (dbName.length === 0) {
2802
+ throw new Error("column.name() requires a non-empty database column name.");
2803
+ }
2804
+ return this.derive({ dbName });
1827
2805
  }
1828
2806
  /**
1829
2807
  * Declare a foreign-key reference to another table's column, à la SQLAlchemy's
@@ -1836,35 +2814,45 @@ var Column = class _Column {
1836
2814
  * @throws Error When `ref` is not a valid `"table.column"` string.
1837
2815
  */
1838
2816
  references(ref, options) {
1839
- return new _Column(
1840
- this.type,
1841
- this.flags,
1842
- this.defaultValue,
1843
- this.onUpdateValue,
1844
- parseReference(ref, options)
1845
- );
2817
+ return this.derive({ reference: parseReference(ref, options) });
1846
2818
  }
1847
2819
  /**
1848
2820
  * Set the insert-time default: a constant value of type `T`, or a portable
1849
2821
  * server-side expression from {@link sql} (e.g. `sql.now()`, `sql.uuidv4()`).
2822
+ *
2823
+ * @param value The literal default, or a {@link sql} expression.
2824
+ * @returns A new column carrying the default.
2825
+ * @throws Error When given a `sql.expr` fragment — a `DEFAULT` clause has
2826
+ * nowhere to bind parameters; use `sql.raw()` for a verbatim expression.
1850
2827
  */
1851
2828
  default(value) {
1852
2829
  const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
1853
- return new _Column(
1854
- this.type,
1855
- { ...this.flags, hasDefault: true },
1856
- resolved,
1857
- this.onUpdateValue,
1858
- this.reference
1859
- );
2830
+ if (bindsParameters(resolved)) {
2831
+ throw new Error(
2832
+ "sql.expr`...` binds parameters and cannot be a column default \u2014 use sql.raw() for a verbatim DEFAULT expression."
2833
+ );
2834
+ }
2835
+ return this.derive({
2836
+ flags: { ...this.flags, hasDefault: true },
2837
+ defaultValue: resolved
2838
+ });
1860
2839
  }
1861
2840
  /**
1862
2841
  * Re-apply a value whenever the row is updated (e.g. an `updated_at` column
1863
2842
  * with `sql.now()`). Mirrors SQLAlchemy's `onupdate`.
2843
+ *
2844
+ * @param value The literal value, or a {@link sql} expression.
2845
+ * @returns A new column carrying the on-update value.
2846
+ * @throws Error When given a `sql.expr` fragment (see {@link Column.default}).
1864
2847
  */
1865
2848
  onUpdate(value) {
1866
2849
  const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
1867
- return new _Column(this.type, this.flags, this.defaultValue, resolved, this.reference);
2850
+ if (bindsParameters(resolved)) {
2851
+ throw new Error(
2852
+ "sql.expr`...` binds parameters and cannot be an onUpdate default \u2014 use sql.raw() for a verbatim expression."
2853
+ );
2854
+ }
2855
+ return this.derive({ onUpdateValue: resolved });
1868
2856
  }
1869
2857
  };
1870
2858
  function makeColumn(kind, meta = {}) {
@@ -1916,7 +2904,27 @@ var column = {
1916
2904
  /** `UUID` → `string`. */
1917
2905
  uuid: () => makeColumn("uuid"),
1918
2906
  /** `ENUM(...values)` → a string-literal union of the given values. */
1919
- enum: (...values) => makeColumn("enum", { values })
2907
+ enum: (...values) => makeColumn("enum", { values }),
2908
+ /**
2909
+ * A PostgreSQL array column (`text[]`, `integer[]`) → `T[]`.
2910
+ *
2911
+ * PostgreSQL only: SQLite and MySQL have no native array type, and rendering
2912
+ * one as JSON there would give the same model different semantics per dialect
2913
+ * (`@>` and `&&` work on one and not the other), so the DDL renderer throws
2914
+ * for those dialects instead of falling back silently.
2915
+ *
2916
+ * @param element The element column (its type, not its flags, is what is used).
2917
+ * @returns A column whose inferred type is an array of the element's type.
2918
+ *
2919
+ * @example
2920
+ * ```ts
2921
+ * class ApiKey extends Model {
2922
+ * static tablename = "api_keys";
2923
+ * scopes = column.array(column.text()).notNull().default(["send"]);
2924
+ * }
2925
+ * ```
2926
+ */
2927
+ array: (element) => makeColumn("array", { element: element.type })
1920
2928
  };
1921
2929
  function unique(...columns) {
1922
2930
  if (columns.length === 0) {
@@ -1940,6 +2948,9 @@ function foreignKey(columns, refTable, refColumns, options) {
1940
2948
  onUpdate: options?.onUpdate
1941
2949
  };
1942
2950
  }
2951
+ function toSnakeCase(name) {
2952
+ return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
2953
+ }
1943
2954
  var Model = class {
1944
2955
  static tablename;
1945
2956
  /**
@@ -1948,6 +2959,12 @@ var Model = class {
1948
2959
  * `__table_args__`.
1949
2960
  */
1950
2961
  static tableArgs;
2962
+ /**
2963
+ * How to derive column names from property names (default `"preserve"`). Set
2964
+ * `"snake_case"` to keep a `snake_case` schema behind a `camelCase` model
2965
+ * without annotating every column; {@link Column.name} overrides it per column.
2966
+ */
2967
+ static naming;
1951
2968
  };
1952
2969
  var columnsCache = /* @__PURE__ */ new WeakMap();
1953
2970
  function columnsOf(model) {
@@ -1963,7 +2980,48 @@ function columnsOf(model) {
1963
2980
  columnsCache.set(model, out);
1964
2981
  return out;
1965
2982
  }
2983
+ var nameMapCache = /* @__PURE__ */ new WeakMap();
2984
+ var propMapCache = /* @__PURE__ */ new WeakMap();
2985
+ function columnNamesOf(model) {
2986
+ const cached = nameMapCache.get(model);
2987
+ if (cached !== void 0) return cached;
2988
+ const strategy = model.naming ?? "preserve";
2989
+ const map = {};
2990
+ const seen = /* @__PURE__ */ new Map();
2991
+ let renamed = false;
2992
+ for (const [prop, col2] of Object.entries(columnsOf(model))) {
2993
+ const dbName = col2.dbName ?? (strategy === "snake_case" ? toSnakeCase(prop) : prop);
2994
+ const collision = seen.get(dbName);
2995
+ if (collision !== void 0) {
2996
+ throw new Error(
2997
+ `${model.tablename}: properties "${collision}" and "${prop}" both map to column "${dbName}".`
2998
+ );
2999
+ }
3000
+ seen.set(dbName, prop);
3001
+ map[prop] = dbName;
3002
+ if (dbName !== prop) renamed = true;
3003
+ }
3004
+ const result = renamed ? map : null;
3005
+ nameMapCache.set(model, result);
3006
+ return result;
3007
+ }
3008
+ function columnPropsOf(model) {
3009
+ const cached = propMapCache.get(model);
3010
+ if (cached !== void 0) return cached;
3011
+ const forward = columnNamesOf(model);
3012
+ let result = null;
3013
+ if (forward) {
3014
+ const inverse = {};
3015
+ for (const [prop, dbName] of Object.entries(forward)) inverse[dbName] = prop;
3016
+ result = inverse;
3017
+ }
3018
+ propMapCache.set(model, result);
3019
+ return result;
3020
+ }
3021
+ function dbColumn(names, prop) {
3022
+ return names?.[prop] ?? prop;
3023
+ }
1966
3024
 
1967
- export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, foreignKey, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, unique, update };
1968
- //# sourceMappingURL=chunk-JR4MLFQN.js.map
1969
- //# sourceMappingURL=chunk-JR4MLFQN.js.map
3025
+ export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, Expression, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, Params, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, activeRecord, and, avg, belongsTo, col, column, columnNamesOf, columnPropsOf, columnsOf, count, createEngine, createSyncEngine, dbColumn, del, detectDialect, fn, foreignKey, fromDict, getDialect, hasMany, insert, isCondition, isExpression, isSqlExpression, isSubquery, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, renderPortableToken, select, sql, stringify, sum, toAsyncDriver, toCondNode, toDict, toJSON, toSnakeCase, unique, update, val };
3026
+ //# sourceMappingURL=chunk-G7O5DCCC.js.map
3027
+ //# sourceMappingURL=chunk-G7O5DCCC.js.map