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