tempest-db-js 0.5.0 → 0.7.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 +8 -4
- package/dist/bin.cjs +580 -329
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +3 -3
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-5QQMVTS5.js → chunk-4AWUP7BM.js} +540 -57
- package/dist/chunk-4AWUP7BM.js.map +1 -0
- package/dist/{chunk-EPMLFNFK.js → chunk-SI4CLSF7.js} +109 -59
- package/dist/chunk-SI4CLSF7.js.map +1 -0
- package/dist/index.cjs +544 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +430 -22
- package/dist/index.d.ts +430 -22
- package/dist/index.js +1 -1
- package/dist/migrations/index.cjs +122 -55
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +46 -3
- package/dist/migrations/index.d.ts +46 -3
- package/dist/migrations/index.js +2 -2
- package/package.json +6 -1
- package/dist/chunk-5QQMVTS5.js.map +0 -1
- package/dist/chunk-EPMLFNFK.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",
|
|
@@ -47,8 +186,8 @@ var OPERATORS = [
|
|
|
47
186
|
"overlaps"
|
|
48
187
|
];
|
|
49
188
|
var Agg = class {
|
|
50
|
-
constructor(
|
|
51
|
-
this.fn =
|
|
189
|
+
constructor(fn2, column2) {
|
|
190
|
+
this.fn = fn2;
|
|
52
191
|
this.column = column2;
|
|
53
192
|
}
|
|
54
193
|
fn;
|
|
@@ -77,12 +216,41 @@ var SelectBuilder = class _SelectBuilder {
|
|
|
77
216
|
node;
|
|
78
217
|
source;
|
|
79
218
|
with(patch) {
|
|
80
|
-
return new _SelectBuilder(
|
|
219
|
+
return new _SelectBuilder(
|
|
220
|
+
{ ...this.node, ...patch },
|
|
221
|
+
this.source
|
|
222
|
+
);
|
|
81
223
|
}
|
|
82
224
|
/** Add a WHERE filter: the object form (keys typed) or an `and`/`or`/`not`. */
|
|
83
225
|
where(input) {
|
|
84
226
|
return this.with({ where: toCondNode(input) });
|
|
85
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
|
+
}
|
|
86
254
|
/** Emit `SELECT DISTINCT` — drop duplicate rows. */
|
|
87
255
|
distinct() {
|
|
88
256
|
return this.with({ distinct: true });
|
|
@@ -114,7 +282,15 @@ var SelectBuilder = class _SelectBuilder {
|
|
|
114
282
|
this.source
|
|
115
283
|
);
|
|
116
284
|
}
|
|
117
|
-
/**
|
|
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
|
+
*/
|
|
118
294
|
orderBy(column2, direction = "asc") {
|
|
119
295
|
return this.with({
|
|
120
296
|
orderBy: [...this.node.orderBy, { column: column2, direction }]
|
|
@@ -128,6 +304,37 @@ var SelectBuilder = class _SelectBuilder {
|
|
|
128
304
|
offset(n) {
|
|
129
305
|
return this.with({ offset: n });
|
|
130
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
|
+
}
|
|
131
338
|
/**
|
|
132
339
|
* Lock the selected rows for update (`SELECT ... FOR UPDATE`), à la
|
|
133
340
|
* SQLAlchemy's `with_for_update()`.
|
|
@@ -281,8 +488,8 @@ function toDict(model, row) {
|
|
|
281
488
|
function toJSON(model, row) {
|
|
282
489
|
const columns = columnsOf(model);
|
|
283
490
|
const out = {};
|
|
284
|
-
for (const [name,
|
|
285
|
-
out[name] = encodeValue(
|
|
491
|
+
for (const [name, col2] of Object.entries(columns)) {
|
|
492
|
+
out[name] = encodeValue(col2, row[name] ?? null);
|
|
286
493
|
}
|
|
287
494
|
return out;
|
|
288
495
|
}
|
|
@@ -293,10 +500,10 @@ function fromDict(model, data) {
|
|
|
293
500
|
const columns = columnsOf(model);
|
|
294
501
|
const out = {};
|
|
295
502
|
const issues = [];
|
|
296
|
-
for (const [name,
|
|
503
|
+
for (const [name, col2] of Object.entries(columns)) {
|
|
297
504
|
const present = name in data && data[name] !== void 0 && data[name] !== null;
|
|
298
505
|
if (!present) {
|
|
299
|
-
const required =
|
|
506
|
+
const required = col2.flags.notNull && !col2.flags.hasDefault;
|
|
300
507
|
if (required) {
|
|
301
508
|
issues.push(`missing required column "${name}"`);
|
|
302
509
|
continue;
|
|
@@ -305,7 +512,7 @@ function fromDict(model, data) {
|
|
|
305
512
|
continue;
|
|
306
513
|
}
|
|
307
514
|
try {
|
|
308
|
-
out[name] = decodeValue(
|
|
515
|
+
out[name] = decodeValue(col2, data[name]);
|
|
309
516
|
} catch (error) {
|
|
310
517
|
issues.push(`column "${name}": ${error.message}`);
|
|
311
518
|
}
|
|
@@ -356,8 +563,8 @@ function mapperFor(model) {
|
|
|
356
563
|
const props = columnPropsOf(model);
|
|
357
564
|
const names = props ? Object.fromEntries(Object.entries(props).map(([db, prop]) => [prop, db])) : null;
|
|
358
565
|
const decoders = /* @__PURE__ */ new Map();
|
|
359
|
-
for (const [prop,
|
|
360
|
-
const decoder = decoderFor(
|
|
566
|
+
for (const [prop, col2] of Object.entries(columnsOf(model))) {
|
|
567
|
+
const decoder = decoderFor(col2.type);
|
|
361
568
|
if (decoder) decoders.set(names?.[prop] ?? prop, decoder);
|
|
362
569
|
}
|
|
363
570
|
const mapper = { props, decoders };
|
|
@@ -388,19 +595,34 @@ function assertWritableValues(model, values, clause) {
|
|
|
388
595
|
const columns = columnsOf(model);
|
|
389
596
|
const issues = [];
|
|
390
597
|
for (const [key, value] of Object.entries(values)) {
|
|
391
|
-
const
|
|
392
|
-
if (!
|
|
598
|
+
const col2 = columns[key];
|
|
599
|
+
if (!col2) {
|
|
393
600
|
issues.push(`${clause}: "${key}" is not a column of ${model.tablename}`);
|
|
394
601
|
continue;
|
|
395
602
|
}
|
|
396
603
|
if (isSqlExpression(value) || isBindableScalar(value)) continue;
|
|
397
|
-
if (typeof value === "object" && STRUCTURED_KINDS.has(
|
|
604
|
+
if (typeof value === "object" && STRUCTURED_KINDS.has(col2.type.kind)) continue;
|
|
398
605
|
issues.push(
|
|
399
|
-
`${clause}: "${key}" got ${describeValue(value)}, which cannot be bound to a ${
|
|
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`
|
|
400
607
|
);
|
|
401
608
|
}
|
|
402
609
|
if (issues.length > 0) throw new ValidationError(model.tablename, issues);
|
|
403
610
|
}
|
|
611
|
+
function assertConsistentRows(model, rows) {
|
|
612
|
+
if (rows.length < 2) return;
|
|
613
|
+
const union = /* @__PURE__ */ new Set();
|
|
614
|
+
for (const row of rows) for (const key of Object.keys(row)) union.add(key);
|
|
615
|
+
const inconsistent = [...union].filter((key) => rows.some((row) => !(key in row)));
|
|
616
|
+
if (inconsistent.length === 0) return;
|
|
617
|
+
const columns = columnsOf(model);
|
|
618
|
+
const defaulted = inconsistent.filter((key) => columns[key]?.flags.hasDefault);
|
|
619
|
+
if (defaulted.length === 0) return;
|
|
620
|
+
const named = defaulted.map((c) => `"${c}"`).join(", ");
|
|
621
|
+
const verb = defaulted.length === 1 ? "has" : "have";
|
|
622
|
+
throw new ValidationError(model.tablename, [
|
|
623
|
+
`values: ${named} ${verb} a default but is missing from some rows of this multi-row insert \u2014 every row shares one column list, so the omitting rows would be written as NULL instead of taking the default. Give the column in every row, or insert the rows separately.`
|
|
624
|
+
]);
|
|
625
|
+
}
|
|
404
626
|
function describeValue(value) {
|
|
405
627
|
if (typeof value === "function") return "a function";
|
|
406
628
|
if (Array.isArray(value)) return "an array";
|
|
@@ -423,11 +645,13 @@ var InsertBuilder = class _InsertBuilder {
|
|
|
423
645
|
* @param rows One row, or an array of rows.
|
|
424
646
|
* @returns A builder carrying the rows.
|
|
425
647
|
* @throws ValidationError When a value is not a column value the dialect can
|
|
426
|
-
* bind (see the `sql` helpers for writing an expression instead)
|
|
648
|
+
* bind (see the `sql` helpers for writing an expression instead), or when the
|
|
649
|
+
* rows of a multi-row insert disagree about a column that has a default.
|
|
427
650
|
*/
|
|
428
651
|
values(rows) {
|
|
429
652
|
const list = Array.isArray(rows) ? rows : [rows];
|
|
430
653
|
for (const row of list) assertWritableValues(this.source, row, "values");
|
|
654
|
+
assertConsistentRows(this.source, list);
|
|
431
655
|
return this.with({ values: list });
|
|
432
656
|
}
|
|
433
657
|
/**
|
|
@@ -729,6 +953,18 @@ var Params = class {
|
|
|
729
953
|
return this.placeholder(this.values.length);
|
|
730
954
|
}
|
|
731
955
|
};
|
|
956
|
+
function insertColumns(rows) {
|
|
957
|
+
const columns = [];
|
|
958
|
+
const seen = /* @__PURE__ */ new Set();
|
|
959
|
+
for (const row of rows) {
|
|
960
|
+
for (const key of Object.keys(row)) {
|
|
961
|
+
if (seen.has(key)) continue;
|
|
962
|
+
seen.add(key);
|
|
963
|
+
columns.push(key);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return columns;
|
|
967
|
+
}
|
|
732
968
|
function insertHasExpression(node) {
|
|
733
969
|
for (const row of node.values) {
|
|
734
970
|
for (const value of Object.values(row)) {
|
|
@@ -752,6 +988,15 @@ var BaseDialect = class _BaseDialect {
|
|
|
752
988
|
static insertTemplates = /* @__PURE__ */ new Map();
|
|
753
989
|
/** Quoted-identifier cache (see {@link quoteId}). Shared across dialects. */
|
|
754
990
|
static quotedIds = /* @__PURE__ */ new Map();
|
|
991
|
+
/**
|
|
992
|
+
* Validate a subquery operand before it is rendered, for dialects that restrict
|
|
993
|
+
* what an `IN (SELECT ...)` may contain. The default accepts everything.
|
|
994
|
+
*
|
|
995
|
+
* @param _node The subquery's AST.
|
|
996
|
+
* @throws Error When the dialect cannot execute this subquery.
|
|
997
|
+
*/
|
|
998
|
+
checkSubquery(_node) {
|
|
999
|
+
}
|
|
755
1000
|
/**
|
|
756
1001
|
* The SQL operator for an array containment/overlap test.
|
|
757
1002
|
*
|
|
@@ -875,6 +1120,19 @@ var BaseDialect = class _BaseDialect {
|
|
|
875
1120
|
return ` ${strength}${of}${wait}`;
|
|
876
1121
|
}
|
|
877
1122
|
// ---- statements -------------------------------------------------------
|
|
1123
|
+
/**
|
|
1124
|
+
* Compile a SELECT.
|
|
1125
|
+
*
|
|
1126
|
+
* Two alias rules differ between clauses and are handled here: PostgreSQL does
|
|
1127
|
+
* NOT accept a `SELECT` alias in `HAVING`, so an aggregate key is re-emitted as
|
|
1128
|
+
* its expression (`COUNT(*) > $1`), a form every dialect accepts; `ORDER BY`,
|
|
1129
|
+
* by contrast, accepts the output alias everywhere, so it is emitted as
|
|
1130
|
+
* written.
|
|
1131
|
+
*
|
|
1132
|
+
* @param node The select AST.
|
|
1133
|
+
* @param params The parameter collector.
|
|
1134
|
+
* @returns The SQL text.
|
|
1135
|
+
*/
|
|
878
1136
|
compileSelect(node, params) {
|
|
879
1137
|
const names = node.names;
|
|
880
1138
|
let cols;
|
|
@@ -898,10 +1156,21 @@ var BaseDialect = class _BaseDialect {
|
|
|
898
1156
|
if (node.groupBy.length > 0) {
|
|
899
1157
|
sql2 += ` GROUP BY ${node.groupBy.map((c) => this.columnId(c, names)).join(", ")}`;
|
|
900
1158
|
}
|
|
1159
|
+
const aggByAlias = new Map(node.aggregates.map((a) => [a.alias, a]));
|
|
1160
|
+
if (node.having) {
|
|
1161
|
+
const having = this.compileCondition(node.having, params, (key) => {
|
|
1162
|
+
const agg = aggByAlias.get(key);
|
|
1163
|
+
if (!agg) return this.columnId(key, names);
|
|
1164
|
+
const inner = agg.column === "*" ? "*" : this.columnId(agg.column, names);
|
|
1165
|
+
return `${agg.fn.toUpperCase()}(${inner})`;
|
|
1166
|
+
});
|
|
1167
|
+
if (having) sql2 += ` HAVING ${having}`;
|
|
1168
|
+
}
|
|
901
1169
|
if (node.orderBy.length > 0) {
|
|
902
|
-
const terms = node.orderBy.map(
|
|
903
|
-
(t)
|
|
904
|
-
|
|
1170
|
+
const terms = node.orderBy.map((t) => {
|
|
1171
|
+
const id = aggByAlias.has(t.column) ? this.quoteId(t.column) : this.columnId(t.column, names);
|
|
1172
|
+
return `${id} ${t.direction === "desc" ? "DESC" : "ASC"}`;
|
|
1173
|
+
}).join(", ");
|
|
905
1174
|
sql2 += ` ORDER BY ${terms}`;
|
|
906
1175
|
}
|
|
907
1176
|
if (node.limit !== void 0) sql2 += ` LIMIT ${params.bind(node.limit)}`;
|
|
@@ -925,7 +1194,7 @@ var BaseDialect = class _BaseDialect {
|
|
|
925
1194
|
* SQL order, so placeholder positions stay correct.
|
|
926
1195
|
*/
|
|
927
1196
|
compileInsert(node, params) {
|
|
928
|
-
const columns =
|
|
1197
|
+
const columns = insertColumns(node.values);
|
|
929
1198
|
const conflict = node.onConflict;
|
|
930
1199
|
const cacheable = conflict?.targetWhere === void 0 && conflict?.updateWhere === void 0 && !insertHasExpression(node);
|
|
931
1200
|
if (!cacheable) return this.compileInsertDirect(node, columns, params);
|
|
@@ -1037,7 +1306,7 @@ var BaseDialect = class _BaseDialect {
|
|
|
1037
1306
|
compileUpdate(node, params) {
|
|
1038
1307
|
const names = node.names;
|
|
1039
1308
|
const sets = Object.entries(node.set).map(
|
|
1040
|
-
([
|
|
1309
|
+
([col2, value]) => `${this.columnId(col2, names)} = ${this.renderValue(value, params)}`
|
|
1041
1310
|
).join(", ");
|
|
1042
1311
|
let sql2 = `UPDATE ${this.quoteId(node.table)} SET ${sets}`;
|
|
1043
1312
|
const where = this.compileCondition(
|
|
@@ -1130,6 +1399,83 @@ var BaseDialect = class _BaseDialect {
|
|
|
1130
1399
|
const inner = this.compileCondition(node.part, params, idFor);
|
|
1131
1400
|
return inner ? `NOT (${inner})` : "";
|
|
1132
1401
|
}
|
|
1402
|
+
case "compare": {
|
|
1403
|
+
const left = this.renderExpr(node.left, params, idFor);
|
|
1404
|
+
if (node.right.kind === "value") {
|
|
1405
|
+
return this.compileOperator(left, node.op, node.right.value, params);
|
|
1406
|
+
}
|
|
1407
|
+
return this.compileExprOperator(
|
|
1408
|
+
left,
|
|
1409
|
+
node.op,
|
|
1410
|
+
this.renderExpr(node.right, params, idFor)
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Render one side of a comparison.
|
|
1417
|
+
*
|
|
1418
|
+
* A column reference goes through `idFor`, so an explicit `.name()` mapping and
|
|
1419
|
+
* join qualification apply here exactly as they do in the object form of
|
|
1420
|
+
* `where` — `col()` is not a way around them. Only a `value` node binds.
|
|
1421
|
+
*
|
|
1422
|
+
* @param node The expression AST.
|
|
1423
|
+
* @param params The parameter collector.
|
|
1424
|
+
* @param idFor The identifier resolver for the enclosing statement.
|
|
1425
|
+
* @returns The SQL text of the expression.
|
|
1426
|
+
*/
|
|
1427
|
+
renderExpr(node, params, idFor) {
|
|
1428
|
+
switch (node.kind) {
|
|
1429
|
+
case "column":
|
|
1430
|
+
return idFor(node.name);
|
|
1431
|
+
case "value":
|
|
1432
|
+
return params.bind(node.value);
|
|
1433
|
+
case "fn": {
|
|
1434
|
+
const args = node.args.map((a) => this.renderExpr(a, params, idFor)).join(", ");
|
|
1435
|
+
return `${node.name}(${args})`;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Compile a comparison whose right-hand side is another expression rather than
|
|
1441
|
+
* a bound value (`total > paid`, `lower(a) = lower(b)`).
|
|
1442
|
+
*
|
|
1443
|
+
* The list and null operators are excluded: `IN`, `BETWEEN` and `IS NULL` take
|
|
1444
|
+
* a value operand, and accepting an expression there would silently compile to
|
|
1445
|
+
* something else.
|
|
1446
|
+
*
|
|
1447
|
+
* @param left The rendered left-hand side.
|
|
1448
|
+
* @param op The operator name.
|
|
1449
|
+
* @param right The rendered right-hand side.
|
|
1450
|
+
* @returns The SQL text of the predicate.
|
|
1451
|
+
* @throws Error When the operator needs a value operand.
|
|
1452
|
+
*/
|
|
1453
|
+
compileExprOperator(left, op, right) {
|
|
1454
|
+
switch (op) {
|
|
1455
|
+
case "eq":
|
|
1456
|
+
return `${left} = ${right}`;
|
|
1457
|
+
case "ne":
|
|
1458
|
+
return `${left} <> ${right}`;
|
|
1459
|
+
case "gt":
|
|
1460
|
+
return `${left} > ${right}`;
|
|
1461
|
+
case "gte":
|
|
1462
|
+
return `${left} >= ${right}`;
|
|
1463
|
+
case "lt":
|
|
1464
|
+
return `${left} < ${right}`;
|
|
1465
|
+
case "lte":
|
|
1466
|
+
return `${left} <= ${right}`;
|
|
1467
|
+
case "like":
|
|
1468
|
+
return `${left} LIKE ${right}`;
|
|
1469
|
+
case "ilike":
|
|
1470
|
+
return this.ilike(left, right);
|
|
1471
|
+
case "ieq":
|
|
1472
|
+
return `lower(${left}) = lower(${right})`;
|
|
1473
|
+
case "contains":
|
|
1474
|
+
case "containedBy":
|
|
1475
|
+
case "overlaps":
|
|
1476
|
+
return `${left} ${this.arrayOperator(op)} ${right}`;
|
|
1477
|
+
default:
|
|
1478
|
+
throw new Error(`The "${op}" operator takes a value operand, not an expression.`);
|
|
1133
1479
|
}
|
|
1134
1480
|
}
|
|
1135
1481
|
compileOperator(id, op, operand, params) {
|
|
@@ -1172,12 +1518,33 @@ var BaseDialect = class _BaseDialect {
|
|
|
1172
1518
|
throw new Error(`Unknown operator ${JSON.stringify(op)}`);
|
|
1173
1519
|
}
|
|
1174
1520
|
}
|
|
1175
|
-
|
|
1521
|
+
/**
|
|
1522
|
+
* Compile `IN` / `NOT IN`, whose operand is either a value list or a
|
|
1523
|
+
* single-column subquery.
|
|
1524
|
+
*
|
|
1525
|
+
* The subquery is rendered at the position it appears in the outer statement
|
|
1526
|
+
* and shares the same parameter collector, so its own placeholders land in the
|
|
1527
|
+
* right order — and it keeps its own `names` map, since the inner model may use
|
|
1528
|
+
* a different naming convention than the outer one.
|
|
1529
|
+
*
|
|
1530
|
+
* @param id The quoted column identifier being tested.
|
|
1531
|
+
* @param operand A list of values, or a {@link Subquery}.
|
|
1532
|
+
* @param params The parameter collector for the statement being compiled.
|
|
1533
|
+
* @param negate True for `NOT IN`.
|
|
1534
|
+
* @returns The SQL text of the predicate.
|
|
1535
|
+
*/
|
|
1536
|
+
compileIn(id, operand, params, negate) {
|
|
1537
|
+
const keyword = negate ? "NOT IN" : "IN";
|
|
1538
|
+
if (isSubquery(operand)) {
|
|
1539
|
+
this.checkSubquery(operand.node);
|
|
1540
|
+
return `${id} ${keyword} (${this.compileSelect(operand.node, params)})`;
|
|
1541
|
+
}
|
|
1542
|
+
const values = operand;
|
|
1176
1543
|
if (values.length === 0) {
|
|
1177
1544
|
return negate ? "1 = 1" : "1 = 0";
|
|
1178
1545
|
}
|
|
1179
1546
|
const list = values.map((v) => params.bind(v)).join(", ");
|
|
1180
|
-
return `${id} ${
|
|
1547
|
+
return `${id} ${keyword} (${list})`;
|
|
1181
1548
|
}
|
|
1182
1549
|
};
|
|
1183
1550
|
var SqliteDialect = class extends BaseDialect {
|
|
@@ -1229,6 +1596,19 @@ var MysqlDialect = class extends BaseDialect {
|
|
|
1229
1596
|
mysqlQuotedIds.set(name, quoted);
|
|
1230
1597
|
return quoted;
|
|
1231
1598
|
}
|
|
1599
|
+
/**
|
|
1600
|
+
* MySQL rejects `LIMIT` inside an `IN` subquery with
|
|
1601
|
+
* `ER_NOT_SUPPORTED_YET: This version of MySQL doesn't yet support
|
|
1602
|
+
* 'LIMIT & IN/ALL/ANY/SOME subquery'`. Failing at compile time names the fix
|
|
1603
|
+
* instead of surfacing that error from the driver at runtime.
|
|
1604
|
+
*/
|
|
1605
|
+
checkSubquery(node) {
|
|
1606
|
+
if (node.limit !== void 0 || node.offset !== void 0) {
|
|
1607
|
+
throw new Error(
|
|
1608
|
+
"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."
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1232
1612
|
renderConflict(onConflict, conflictCols, nextValue, names) {
|
|
1233
1613
|
if (onConflict.targetWhere || onConflict.updateWhere) {
|
|
1234
1614
|
throw new Error(
|
|
@@ -1236,16 +1616,25 @@ var MysqlDialect = class extends BaseDialect {
|
|
|
1236
1616
|
);
|
|
1237
1617
|
}
|
|
1238
1618
|
if (onConflict.update === "nothing") {
|
|
1239
|
-
const
|
|
1240
|
-
return ` ON DUPLICATE KEY UPDATE ${
|
|
1619
|
+
const col2 = this.columnId(onConflict.target[0] ?? "id", names);
|
|
1620
|
+
return ` ON DUPLICATE KEY UPDATE ${col2} = ${col2}`;
|
|
1241
1621
|
}
|
|
1242
1622
|
const assignments = conflictCols.map((c) => `${this.columnId(c, names)} = ${nextValue()}`).join(", ");
|
|
1243
1623
|
return ` ON DUPLICATE KEY UPDATE ${assignments}`;
|
|
1244
1624
|
}
|
|
1625
|
+
/**
|
|
1626
|
+
* MySQL has no `RETURNING`, so it cannot be compiled into a statement.
|
|
1627
|
+
*
|
|
1628
|
+
* `session.execute()` still honors `.returning()` on a **single-row INSERT** by
|
|
1629
|
+
* running the insert and reading the row back by key on the same connection —
|
|
1630
|
+
* that is execution, not compilation, so it never reaches here. Compiling a
|
|
1631
|
+
* node with `returning` directly is an error, rather than SQL that silently
|
|
1632
|
+
* returns nothing.
|
|
1633
|
+
*/
|
|
1245
1634
|
compileReturning(returning) {
|
|
1246
1635
|
if (returning === null) return "";
|
|
1247
1636
|
throw new Error(
|
|
1248
|
-
"RETURNING
|
|
1637
|
+
"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."
|
|
1249
1638
|
);
|
|
1250
1639
|
}
|
|
1251
1640
|
};
|
|
@@ -1358,8 +1747,8 @@ var RecordNotFound = class extends Error {
|
|
|
1358
1747
|
}
|
|
1359
1748
|
};
|
|
1360
1749
|
function primaryKeyOf(model) {
|
|
1361
|
-
for (const [name,
|
|
1362
|
-
if (
|
|
1750
|
+
for (const [name, col2] of Object.entries(columnsOf(model))) {
|
|
1751
|
+
if (col2.flags.primaryKey) return name;
|
|
1363
1752
|
}
|
|
1364
1753
|
throw new Error(`${model.tablename} has no primary key`);
|
|
1365
1754
|
}
|
|
@@ -1447,8 +1836,8 @@ var BaseRepository = class {
|
|
|
1447
1836
|
|
|
1448
1837
|
// src/active-record.ts
|
|
1449
1838
|
function primaryKeyOf2(model) {
|
|
1450
|
-
for (const [name,
|
|
1451
|
-
if (
|
|
1839
|
+
for (const [name, col2] of Object.entries(columnsOf(model))) {
|
|
1840
|
+
if (col2.flags.primaryKey) return name;
|
|
1452
1841
|
}
|
|
1453
1842
|
throw new Error(`${model.tablename} has no primary key`);
|
|
1454
1843
|
}
|
|
@@ -1614,10 +2003,18 @@ var NodeSqliteDriver = class _NodeSqliteDriver {
|
|
|
1614
2003
|
constructor(database) {
|
|
1615
2004
|
this.db = database;
|
|
1616
2005
|
}
|
|
1617
|
-
/**
|
|
1618
|
-
|
|
2006
|
+
/**
|
|
2007
|
+
* Open a `node:sqlite` database at the given path (or `:memory:`).
|
|
2008
|
+
*
|
|
2009
|
+
* @param path The database file, or `":memory:"`.
|
|
2010
|
+
* @param options Passed straight to `DatabaseSync` (`readOnly`, `timeout`, …).
|
|
2011
|
+
* @returns A driver over the open handle.
|
|
2012
|
+
*/
|
|
2013
|
+
static open(path, options) {
|
|
1619
2014
|
const { DatabaseSync } = nodeRequire("node:sqlite");
|
|
1620
|
-
return new _NodeSqliteDriver(
|
|
2015
|
+
return new _NodeSqliteDriver(
|
|
2016
|
+
options ? new DatabaseSync(path, { ...options }) : new DatabaseSync(path)
|
|
2017
|
+
);
|
|
1621
2018
|
}
|
|
1622
2019
|
/** Return the cached prepared statement for `sql`, preparing it on first use. */
|
|
1623
2020
|
// biome-ignore lint/suspicious/noExplicitAny: statement type is unavailable here.
|
|
@@ -1784,6 +2181,18 @@ var AsyncResult = class {
|
|
|
1784
2181
|
}
|
|
1785
2182
|
};
|
|
1786
2183
|
var savepointCounter = 0;
|
|
2184
|
+
function needsInsertReadBack(dialect, node) {
|
|
2185
|
+
return dialect.name === "mysql" && node.kind === "insert" && node.returning !== null;
|
|
2186
|
+
}
|
|
2187
|
+
function singlePrimaryKey(model) {
|
|
2188
|
+
const keys = Object.entries(columnsOf(model)).filter(([, column2]) => column2.flags.primaryKey).map(([name]) => name);
|
|
2189
|
+
if (keys.length !== 1) {
|
|
2190
|
+
throw new Error(
|
|
2191
|
+
`${model.tablename} needs exactly one primary key to read an insert back on a dialect without RETURNING; found ${keys.length}.`
|
|
2192
|
+
);
|
|
2193
|
+
}
|
|
2194
|
+
return keys[0];
|
|
2195
|
+
}
|
|
1787
2196
|
function assertRawParams(params) {
|
|
1788
2197
|
if (!Array.isArray(params)) {
|
|
1789
2198
|
throw new TypeError(
|
|
@@ -1856,10 +2265,10 @@ var SyncSession = class {
|
|
|
1856
2265
|
return new SyncResult(rows, result.changes);
|
|
1857
2266
|
}
|
|
1858
2267
|
/** Run `fn` inside a transaction: commit on success, rollback on throw. */
|
|
1859
|
-
transaction(
|
|
2268
|
+
transaction(fn2) {
|
|
1860
2269
|
this.exec("BEGIN", []);
|
|
1861
2270
|
try {
|
|
1862
|
-
const out =
|
|
2271
|
+
const out = fn2(this);
|
|
1863
2272
|
this.exec("COMMIT", []);
|
|
1864
2273
|
return out;
|
|
1865
2274
|
} catch (error) {
|
|
@@ -1868,12 +2277,12 @@ var SyncSession = class {
|
|
|
1868
2277
|
}
|
|
1869
2278
|
}
|
|
1870
2279
|
/** Run `fn` inside a SAVEPOINT (nested transaction). */
|
|
1871
|
-
beginNested(
|
|
2280
|
+
beginNested(fn2) {
|
|
1872
2281
|
savepointCounter += 1;
|
|
1873
2282
|
const name = `qsp_${savepointCounter}`;
|
|
1874
2283
|
this.exec(`SAVEPOINT ${name}`, []);
|
|
1875
2284
|
try {
|
|
1876
|
-
const out =
|
|
2285
|
+
const out = fn2(this);
|
|
1877
2286
|
this.exec(`RELEASE ${name}`, []);
|
|
1878
2287
|
return out;
|
|
1879
2288
|
} catch (error) {
|
|
@@ -1971,6 +2380,11 @@ var AsyncSession = class _AsyncSession {
|
|
|
1971
2380
|
}
|
|
1972
2381
|
execute(builder) {
|
|
1973
2382
|
const node = builder.node;
|
|
2383
|
+
if (needsInsertReadBack(this.dialect, node)) {
|
|
2384
|
+
return new AsyncResult(
|
|
2385
|
+
this.insertAndReadBack(builder, node)
|
|
2386
|
+
);
|
|
2387
|
+
}
|
|
1974
2388
|
const { sql: sql2, params } = this.dialect.compile(node);
|
|
1975
2389
|
const inner = this.exec(sql2, params).then((result) => {
|
|
1976
2390
|
const rows = mapRows(builder, result.rows);
|
|
@@ -1978,6 +2392,53 @@ var AsyncSession = class _AsyncSession {
|
|
|
1978
2392
|
});
|
|
1979
2393
|
return new AsyncResult(inner);
|
|
1980
2394
|
}
|
|
2395
|
+
/**
|
|
2396
|
+
* Honor `.returning()` on a dialect without `RETURNING`, by inserting and then
|
|
2397
|
+
* reading the row back by key.
|
|
2398
|
+
*
|
|
2399
|
+
* Both statements must run on **one** connection, because `LAST_INSERT_ID()` is
|
|
2400
|
+
* per-connection: outside a transaction the pooled driver is reserved for the
|
|
2401
|
+
* pair; inside one, the session already holds a pinned connection (a reserved
|
|
2402
|
+
* driver exposes no `reserve`), so it runs there directly.
|
|
2403
|
+
*
|
|
2404
|
+
* @param builder The insert builder, for its source model.
|
|
2405
|
+
* @param node The insert AST, whose `returning` drives the read-back.
|
|
2406
|
+
* @returns The result view over the read-back row.
|
|
2407
|
+
* @throws Error When the insert writes more than one row — `LAST_INSERT_ID()`
|
|
2408
|
+
* identifies only the first, and the rest are consecutive only under some
|
|
2409
|
+
* auto-increment lock modes.
|
|
2410
|
+
*/
|
|
2411
|
+
async insertAndReadBack(builder, node) {
|
|
2412
|
+
if (node.values.length !== 1) {
|
|
2413
|
+
throw new Error(
|
|
2414
|
+
`${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().`
|
|
2415
|
+
);
|
|
2416
|
+
}
|
|
2417
|
+
const model = builder.source;
|
|
2418
|
+
const pk = singlePrimaryKey(model);
|
|
2419
|
+
const supplied = node.values[0][pk];
|
|
2420
|
+
const readBack = select(model).where(
|
|
2421
|
+
supplied === void 0 || supplied === null ? col(pk).eq(fn.call("LAST_INSERT_ID")) : { [pk]: supplied }
|
|
2422
|
+
);
|
|
2423
|
+
const insertSql = this.dialect.compile({ ...node, returning: null });
|
|
2424
|
+
const selectSql = this.dialect.compile(
|
|
2425
|
+
node.returning === "*" || node.returning === null ? readBack.node : { ...readBack.node, columns: node.returning }
|
|
2426
|
+
);
|
|
2427
|
+
const run = async (driver) => {
|
|
2428
|
+
const scoped = new _AsyncSession(driver, this.dialect, this.logger);
|
|
2429
|
+
const written = await scoped.exec(insertSql.sql, insertSql.params);
|
|
2430
|
+
const read = await scoped.exec(selectSql.sql, selectSql.params);
|
|
2431
|
+
const rows = read.rows.map((row) => coerceRow(model, row));
|
|
2432
|
+
return new SyncResult(rows, written.changes);
|
|
2433
|
+
};
|
|
2434
|
+
if (!this.driver.reserve) return run(this.driver);
|
|
2435
|
+
const reserved = await this.driver.reserve();
|
|
2436
|
+
try {
|
|
2437
|
+
return await run(reserved);
|
|
2438
|
+
} finally {
|
|
2439
|
+
await reserved.release();
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
1981
2442
|
/** Lazily iterate result rows. Uses driver streaming when available. */
|
|
1982
2443
|
async *stream(builder) {
|
|
1983
2444
|
const node = builder.node;
|
|
@@ -1998,13 +2459,13 @@ var AsyncSession = class _AsyncSession {
|
|
|
1998
2459
|
yield coerceOne(builder, raw);
|
|
1999
2460
|
}
|
|
2000
2461
|
}
|
|
2001
|
-
async transaction(
|
|
2462
|
+
async transaction(fn2) {
|
|
2002
2463
|
if (this.driver.reserve) {
|
|
2003
2464
|
const reserved = await this.driver.reserve();
|
|
2004
2465
|
const scoped = new _AsyncSession(reserved, this.dialect, this.logger);
|
|
2005
2466
|
try {
|
|
2006
2467
|
await scoped.exec("BEGIN", []);
|
|
2007
|
-
const out = await
|
|
2468
|
+
const out = await fn2(scoped);
|
|
2008
2469
|
await scoped.exec("COMMIT", []);
|
|
2009
2470
|
return out;
|
|
2010
2471
|
} catch (error) {
|
|
@@ -2016,7 +2477,7 @@ var AsyncSession = class _AsyncSession {
|
|
|
2016
2477
|
}
|
|
2017
2478
|
await this.exec("BEGIN", []);
|
|
2018
2479
|
try {
|
|
2019
|
-
const out = await
|
|
2480
|
+
const out = await fn2(this);
|
|
2020
2481
|
await this.exec("COMMIT", []);
|
|
2021
2482
|
return out;
|
|
2022
2483
|
} catch (error) {
|
|
@@ -2032,6 +2493,13 @@ var AsyncSession = class _AsyncSession {
|
|
|
2032
2493
|
await this.close();
|
|
2033
2494
|
}
|
|
2034
2495
|
};
|
|
2496
|
+
function emitNotice(logger, notice) {
|
|
2497
|
+
if (!logger) return;
|
|
2498
|
+
try {
|
|
2499
|
+
logger(notice);
|
|
2500
|
+
} catch {
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2035
2503
|
var SyncEngine = class {
|
|
2036
2504
|
constructor(driver, logger) {
|
|
2037
2505
|
this.driver = driver;
|
|
@@ -2043,8 +2511,8 @@ var SyncEngine = class {
|
|
|
2043
2511
|
session() {
|
|
2044
2512
|
return new SyncSession(this.driver, getDialect("sqlite"), this.logger);
|
|
2045
2513
|
}
|
|
2046
|
-
transaction(
|
|
2047
|
-
return this.session().transaction(
|
|
2514
|
+
transaction(fn2) {
|
|
2515
|
+
return this.session().transaction(fn2);
|
|
2048
2516
|
}
|
|
2049
2517
|
close() {
|
|
2050
2518
|
this.driver.close();
|
|
@@ -2066,8 +2534,8 @@ var AsyncEngine = class {
|
|
|
2066
2534
|
session() {
|
|
2067
2535
|
return new AsyncSession(this.driver, getDialect(this.dialect), this.logger);
|
|
2068
2536
|
}
|
|
2069
|
-
transaction(
|
|
2070
|
-
return this.session().transaction(
|
|
2537
|
+
transaction(fn2) {
|
|
2538
|
+
return this.session().transaction(fn2);
|
|
2071
2539
|
}
|
|
2072
2540
|
async close() {
|
|
2073
2541
|
await this.driver.close();
|
|
@@ -2077,6 +2545,16 @@ var AsyncEngine = class {
|
|
|
2077
2545
|
await this.close();
|
|
2078
2546
|
}
|
|
2079
2547
|
};
|
|
2548
|
+
function toAsyncDriver(driver) {
|
|
2549
|
+
return {
|
|
2550
|
+
async execute(sql2, params) {
|
|
2551
|
+
return await driver.execute(sql2, params);
|
|
2552
|
+
},
|
|
2553
|
+
async close() {
|
|
2554
|
+
await driver.close();
|
|
2555
|
+
}
|
|
2556
|
+
};
|
|
2557
|
+
}
|
|
2080
2558
|
function asAsync(driver) {
|
|
2081
2559
|
const syncIterate = driver.iterate?.bind(driver);
|
|
2082
2560
|
return {
|
|
@@ -2089,8 +2567,8 @@ function asAsync(driver) {
|
|
|
2089
2567
|
} : {}
|
|
2090
2568
|
};
|
|
2091
2569
|
}
|
|
2092
|
-
function openSqliteDriver(path,
|
|
2093
|
-
return NodeSqliteDriver.open(path);
|
|
2570
|
+
function openSqliteDriver(path, options) {
|
|
2571
|
+
return NodeSqliteDriver.open(path, options?.driverOptions);
|
|
2094
2572
|
}
|
|
2095
2573
|
function createSyncEngine(url, options) {
|
|
2096
2574
|
const parsed = parseDatabaseUrl(url);
|
|
@@ -2100,7 +2578,7 @@ function createSyncEngine(url, options) {
|
|
|
2100
2578
|
);
|
|
2101
2579
|
}
|
|
2102
2580
|
return new SyncEngine(
|
|
2103
|
-
openSqliteDriver(parsed.database ?? ":memory:"),
|
|
2581
|
+
openSqliteDriver(parsed.database ?? ":memory:", options),
|
|
2104
2582
|
options?.onQuery
|
|
2105
2583
|
);
|
|
2106
2584
|
}
|
|
@@ -2108,20 +2586,20 @@ function createEngine(url, options) {
|
|
|
2108
2586
|
const parsed = parseDatabaseUrl(url);
|
|
2109
2587
|
if (parsed.dialect === "sqlite") {
|
|
2110
2588
|
return new AsyncEngine(
|
|
2111
|
-
asAsync(openSqliteDriver(parsed.database ?? ":memory:")),
|
|
2589
|
+
asAsync(openSqliteDriver(parsed.database ?? ":memory:", options)),
|
|
2112
2590
|
"sqlite",
|
|
2113
2591
|
options?.onQuery
|
|
2114
2592
|
);
|
|
2115
2593
|
}
|
|
2116
2594
|
if (parsed.dialect === "mysql") {
|
|
2117
2595
|
return new AsyncEngine(
|
|
2118
|
-
createMysqlDriver(parsed.raw, options
|
|
2596
|
+
createMysqlDriver(parsed.raw, options),
|
|
2119
2597
|
"mysql",
|
|
2120
2598
|
options?.onQuery
|
|
2121
2599
|
);
|
|
2122
2600
|
}
|
|
2123
2601
|
return new AsyncEngine(
|
|
2124
|
-
createPostgresDriver(parsed.raw, options
|
|
2602
|
+
createPostgresDriver(parsed.raw, options),
|
|
2125
2603
|
"postgresql",
|
|
2126
2604
|
options?.onQuery
|
|
2127
2605
|
);
|
|
@@ -2141,7 +2619,8 @@ function toMysqlResult(rows) {
|
|
|
2141
2619
|
const header = rows;
|
|
2142
2620
|
return { rows: [], changes: header.affectedRows ?? 0 };
|
|
2143
2621
|
}
|
|
2144
|
-
function createMysqlDriver(url,
|
|
2622
|
+
function createMysqlDriver(url, options) {
|
|
2623
|
+
const pool = options?.pool;
|
|
2145
2624
|
let poolHandle;
|
|
2146
2625
|
const ensure = async () => {
|
|
2147
2626
|
if (poolHandle) return;
|
|
@@ -2154,6 +2633,7 @@ function createMysqlDriver(url, pool) {
|
|
|
2154
2633
|
if (pool?.size !== void 0) opts.connectionLimit = pool.size;
|
|
2155
2634
|
if (pool?.idleTimeoutMs !== void 0) opts.idleTimeout = pool.idleTimeoutMs;
|
|
2156
2635
|
if (pool?.connectTimeoutMs !== void 0) opts.connectTimeout = pool.connectTimeoutMs;
|
|
2636
|
+
Object.assign(opts, options?.driverOptions ?? {});
|
|
2157
2637
|
poolHandle = mod.createPool(opts);
|
|
2158
2638
|
};
|
|
2159
2639
|
const runOn = async (queryable, sql2, params) => {
|
|
@@ -2187,7 +2667,8 @@ function toPostgresResult(rows) {
|
|
|
2187
2667
|
const arr = rows;
|
|
2188
2668
|
return { rows: Array.from(arr), changes: arr.count ?? arr.length };
|
|
2189
2669
|
}
|
|
2190
|
-
function createPostgresDriver(url,
|
|
2670
|
+
function createPostgresDriver(url, options) {
|
|
2671
|
+
const pool = options?.pool;
|
|
2191
2672
|
let client;
|
|
2192
2673
|
const ensure = async () => {
|
|
2193
2674
|
if (client) return;
|
|
@@ -2203,6 +2684,8 @@ function createPostgresDriver(url, pool) {
|
|
|
2203
2684
|
if (pool?.connectTimeoutMs !== void 0) {
|
|
2204
2685
|
opts.connect_timeout = Math.ceil(pool.connectTimeoutMs / 1e3);
|
|
2205
2686
|
}
|
|
2687
|
+
opts.onnotice = (notice) => emitNotice(options?.onNotice, notice);
|
|
2688
|
+
Object.assign(opts, options?.driverOptions ?? {});
|
|
2206
2689
|
client = (mod.default ?? mod)(url, opts);
|
|
2207
2690
|
};
|
|
2208
2691
|
return {
|
|
@@ -2555,8 +3038,8 @@ function columnNamesOf(model) {
|
|
|
2555
3038
|
const map = {};
|
|
2556
3039
|
const seen = /* @__PURE__ */ new Map();
|
|
2557
3040
|
let renamed = false;
|
|
2558
|
-
for (const [prop,
|
|
2559
|
-
const dbName =
|
|
3041
|
+
for (const [prop, col2] of Object.entries(columnsOf(model))) {
|
|
3042
|
+
const dbName = col2.dbName ?? (strategy === "snake_case" ? toSnakeCase(prop) : prop);
|
|
2560
3043
|
const collision = seen.get(dbName);
|
|
2561
3044
|
if (collision !== void 0) {
|
|
2562
3045
|
throw new Error(
|
|
@@ -2588,6 +3071,6 @@ function dbColumn(names, prop) {
|
|
|
2588
3071
|
return names?.[prop] ?? prop;
|
|
2589
3072
|
}
|
|
2590
3073
|
|
|
2591
|
-
export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, Params, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, activeRecord, and, avg, belongsTo, column, columnNamesOf, columnPropsOf, columnsOf, count, createEngine, createSyncEngine, dbColumn, del, detectDialect, foreignKey, fromDict, getDialect, hasMany, insert, isCondition, isSqlExpression, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, renderPortableToken, select, sql, stringify, sum, toCondNode, toDict, toJSON, toSnakeCase, unique, update };
|
|
2592
|
-
//# sourceMappingURL=chunk-
|
|
2593
|
-
//# sourceMappingURL=chunk-
|
|
3074
|
+
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 };
|
|
3075
|
+
//# sourceMappingURL=chunk-4AWUP7BM.js.map
|
|
3076
|
+
//# sourceMappingURL=chunk-4AWUP7BM.js.map
|