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