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
package/dist/index.cjs
CHANGED
|
@@ -14,6 +14,142 @@ function toCondNode(input) {
|
|
|
14
14
|
function wrap(node) {
|
|
15
15
|
return { [CONDITION]: true, node };
|
|
16
16
|
}
|
|
17
|
+
function toExprNode(operand) {
|
|
18
|
+
return isExpression(operand) ? operand.node : { kind: "value", value: operand };
|
|
19
|
+
}
|
|
20
|
+
function assertValueOperands(op, operands) {
|
|
21
|
+
if (operands.some(isExpression)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`The "${op}" operator binds its operands, so it takes values, not expressions.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function isExpression(value) {
|
|
28
|
+
return value instanceof Expression;
|
|
29
|
+
}
|
|
30
|
+
var Expression = class {
|
|
31
|
+
constructor(node) {
|
|
32
|
+
this.node = node;
|
|
33
|
+
}
|
|
34
|
+
node;
|
|
35
|
+
/** Compare this expression against another expression or a bound value. */
|
|
36
|
+
compare(op, operand) {
|
|
37
|
+
return wrap({ kind: "compare", left: this.node, op, right: toExprNode(operand) });
|
|
38
|
+
}
|
|
39
|
+
/** `=` (or `IS NULL` for a null value). */
|
|
40
|
+
eq(operand) {
|
|
41
|
+
return this.compare("eq", operand);
|
|
42
|
+
}
|
|
43
|
+
/** `<>` (or `IS NOT NULL` for a null value). */
|
|
44
|
+
ne(operand) {
|
|
45
|
+
return this.compare("ne", operand);
|
|
46
|
+
}
|
|
47
|
+
/** `>`. */
|
|
48
|
+
gt(operand) {
|
|
49
|
+
return this.compare("gt", operand);
|
|
50
|
+
}
|
|
51
|
+
/** `>=`. */
|
|
52
|
+
gte(operand) {
|
|
53
|
+
return this.compare("gte", operand);
|
|
54
|
+
}
|
|
55
|
+
/** `<`. */
|
|
56
|
+
lt(operand) {
|
|
57
|
+
return this.compare("lt", operand);
|
|
58
|
+
}
|
|
59
|
+
/** `<=`. */
|
|
60
|
+
lte(operand) {
|
|
61
|
+
return this.compare("lte", operand);
|
|
62
|
+
}
|
|
63
|
+
/** `LIKE` — `%` and `_` in the operand are wildcards. */
|
|
64
|
+
like(pattern) {
|
|
65
|
+
return this.compare("like", pattern);
|
|
66
|
+
}
|
|
67
|
+
/** `ILIKE` — case-insensitive **pattern** matching, wildcards included. */
|
|
68
|
+
ilike(pattern) {
|
|
69
|
+
return this.compare("ilike", pattern);
|
|
70
|
+
}
|
|
71
|
+
/** Case-insensitive equality (`lower(a) = lower(b)`), with no wildcards. */
|
|
72
|
+
ieq(operand) {
|
|
73
|
+
return this.compare("ieq", operand);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* `IN (...)` over a list of values.
|
|
77
|
+
*
|
|
78
|
+
* @param values The values to test against.
|
|
79
|
+
* @returns The condition.
|
|
80
|
+
* @throws Error When an entry is an {@link Expression} — a list operand is
|
|
81
|
+
* bound, so an expression there would be serialized as a parameter instead of
|
|
82
|
+
* rendered as SQL.
|
|
83
|
+
*/
|
|
84
|
+
in(values) {
|
|
85
|
+
assertValueOperands("in", values);
|
|
86
|
+
return this.compare("in", values);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* `NOT IN (...)` over a list of values.
|
|
90
|
+
*
|
|
91
|
+
* @param values The values to exclude.
|
|
92
|
+
* @returns The condition.
|
|
93
|
+
* @throws Error When an entry is an {@link Expression} (see {@link Expression.in}).
|
|
94
|
+
*/
|
|
95
|
+
notIn(values) {
|
|
96
|
+
assertValueOperands("notIn", values);
|
|
97
|
+
return this.compare("notIn", values);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* `BETWEEN lo AND hi` (inclusive).
|
|
101
|
+
*
|
|
102
|
+
* @param lo The lower bound.
|
|
103
|
+
* @param hi The upper bound.
|
|
104
|
+
* @returns The condition.
|
|
105
|
+
* @throws Error When a bound is an {@link Expression} (see {@link Expression.in}).
|
|
106
|
+
*/
|
|
107
|
+
between(lo, hi) {
|
|
108
|
+
assertValueOperands("between", [lo, hi]);
|
|
109
|
+
return this.compare("between", [lo, hi]);
|
|
110
|
+
}
|
|
111
|
+
/** `IS NULL` (true) / `IS NOT NULL` (false). */
|
|
112
|
+
isNull(value = true) {
|
|
113
|
+
return this.compare("isNull", value);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
function col(name) {
|
|
117
|
+
return new Expression({ kind: "column", name });
|
|
118
|
+
}
|
|
119
|
+
function val(value) {
|
|
120
|
+
return new Expression({ kind: "value", value });
|
|
121
|
+
}
|
|
122
|
+
var FUNCTION_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
123
|
+
function toArg(arg) {
|
|
124
|
+
return typeof arg === "string" ? { kind: "column", name: arg } : arg.node;
|
|
125
|
+
}
|
|
126
|
+
function call(name, ...args) {
|
|
127
|
+
if (!FUNCTION_NAME.test(name)) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`fn.call() takes a plain SQL function name; got ${JSON.stringify(name)}.`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return new Expression({ kind: "fn", name, args: args.map(toArg) });
|
|
133
|
+
}
|
|
134
|
+
var fn = {
|
|
135
|
+
/** `lower(x)`. */
|
|
136
|
+
lower: (arg) => call("lower", arg),
|
|
137
|
+
/** `upper(x)`. */
|
|
138
|
+
upper: (arg) => call("upper", arg),
|
|
139
|
+
/** `trim(x)`. */
|
|
140
|
+
trim: (arg) => call("trim", arg),
|
|
141
|
+
/** `length(x)`. */
|
|
142
|
+
length: (arg) => call("length", arg),
|
|
143
|
+
/** `abs(x)`. */
|
|
144
|
+
abs: (arg) => call("abs", arg),
|
|
145
|
+
/** `coalesce(a, b, ...)`. */
|
|
146
|
+
coalesce: (...args) => call("coalesce", ...args),
|
|
147
|
+
/**
|
|
148
|
+
* Any other SQL function, by name. Portability is the caller's problem —
|
|
149
|
+
* `date_trunc` is PostgreSQL, `strftime` is SQLite.
|
|
150
|
+
*/
|
|
151
|
+
call
|
|
152
|
+
};
|
|
17
153
|
function and(...inputs) {
|
|
18
154
|
return wrap({
|
|
19
155
|
kind: "and",
|
|
@@ -31,6 +167,9 @@ function not(input) {
|
|
|
31
167
|
}
|
|
32
168
|
|
|
33
169
|
// src/query.ts
|
|
170
|
+
function isSubquery(value) {
|
|
171
|
+
return typeof value === "object" && value !== null && value.node?.kind === "select";
|
|
172
|
+
}
|
|
34
173
|
var OPERATORS = [
|
|
35
174
|
"eq",
|
|
36
175
|
"ne",
|
|
@@ -50,8 +189,8 @@ var OPERATORS = [
|
|
|
50
189
|
"overlaps"
|
|
51
190
|
];
|
|
52
191
|
var Agg = class {
|
|
53
|
-
constructor(
|
|
54
|
-
this.fn =
|
|
192
|
+
constructor(fn2, column2) {
|
|
193
|
+
this.fn = fn2;
|
|
55
194
|
this.column = column2;
|
|
56
195
|
}
|
|
57
196
|
fn;
|
|
@@ -80,12 +219,41 @@ var SelectBuilder = class _SelectBuilder {
|
|
|
80
219
|
node;
|
|
81
220
|
source;
|
|
82
221
|
with(patch) {
|
|
83
|
-
return new _SelectBuilder(
|
|
222
|
+
return new _SelectBuilder(
|
|
223
|
+
{ ...this.node, ...patch },
|
|
224
|
+
this.source
|
|
225
|
+
);
|
|
84
226
|
}
|
|
85
227
|
/** Add a WHERE filter: the object form (keys typed) or an `and`/`or`/`not`. */
|
|
86
228
|
where(input) {
|
|
87
229
|
return this.with({ where: toCondNode(input) });
|
|
88
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Filter by the result of the aggregation (`HAVING`).
|
|
233
|
+
*
|
|
234
|
+
* Only available on a grouped builder — `.having()` before `.aggregate()` is a
|
|
235
|
+
* compile error, not invalid SQL at runtime. Keys are the aggregate aliases you
|
|
236
|
+
* named plus the grouped columns; `WHERE` still filters rows *before* grouping,
|
|
237
|
+
* which is a different question.
|
|
238
|
+
*
|
|
239
|
+
* @param input The condition, keyed by alias or grouped column.
|
|
240
|
+
* @returns A builder carrying the `HAVING` clause.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* select(Outbound)
|
|
245
|
+
* .where({ status: "queued" })
|
|
246
|
+
* .aggregate(["consumer"], { n: count() })
|
|
247
|
+
* .having({ n: { gt: 100 } });
|
|
248
|
+
* // ... GROUP BY "consumer" HAVING COUNT(*) > $2
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
having(input) {
|
|
252
|
+
return new _SelectBuilder(
|
|
253
|
+
{ ...this.node, having: toCondNode(input) },
|
|
254
|
+
this.source
|
|
255
|
+
);
|
|
256
|
+
}
|
|
89
257
|
/** Emit `SELECT DISTINCT` — drop duplicate rows. */
|
|
90
258
|
distinct() {
|
|
91
259
|
return this.with({ distinct: true });
|
|
@@ -117,7 +285,15 @@ var SelectBuilder = class _SelectBuilder {
|
|
|
117
285
|
this.source
|
|
118
286
|
);
|
|
119
287
|
}
|
|
120
|
-
/**
|
|
288
|
+
/**
|
|
289
|
+
* Order by a column of the model, or — on a grouped query — by an aggregate
|
|
290
|
+
* alias. Unlike `HAVING`, every dialect accepts the output alias in `ORDER BY`,
|
|
291
|
+
* so the alias is emitted as written.
|
|
292
|
+
*
|
|
293
|
+
* @param column A model column, or a projected alias.
|
|
294
|
+
* @param direction `"asc"` (default) or `"desc"`.
|
|
295
|
+
* @returns A builder carrying the ordering term.
|
|
296
|
+
*/
|
|
121
297
|
orderBy(column2, direction = "asc") {
|
|
122
298
|
return this.with({
|
|
123
299
|
orderBy: [...this.node.orderBy, { column: column2, direction }]
|
|
@@ -131,6 +307,37 @@ var SelectBuilder = class _SelectBuilder {
|
|
|
131
307
|
offset(n) {
|
|
132
308
|
return this.with({ offset: n });
|
|
133
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Narrow this SELECT to a single column and mark it as a subquery, so it can be
|
|
312
|
+
* the operand of `in` / `notIn`.
|
|
313
|
+
*
|
|
314
|
+
* The whole query — `where`, `orderBy`, `limit`, and a locking clause — is
|
|
315
|
+
* embedded in the outer statement, which is what collapses the claim-a-batch
|
|
316
|
+
* pattern into one round trip instead of selecting ids and sending them back.
|
|
317
|
+
*
|
|
318
|
+
* @param column The single column to project (checked against the model).
|
|
319
|
+
* @returns A subquery carrying that column's type.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```ts
|
|
323
|
+
* update(Outbound)
|
|
324
|
+
* .set({ status: "sending", attempts: sql.raw("attempts + 1") })
|
|
325
|
+
* .where({
|
|
326
|
+
* id: {
|
|
327
|
+
* in: select(Outbound)
|
|
328
|
+
* .where({ status: "queued" })
|
|
329
|
+
* .orderBy("nextAttemptAt")
|
|
330
|
+
* .limit(10)
|
|
331
|
+
* .forUpdate({ skipLocked: true })
|
|
332
|
+
* .asSubquery("id"),
|
|
333
|
+
* },
|
|
334
|
+
* })
|
|
335
|
+
* .returning();
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
asSubquery(column2) {
|
|
339
|
+
return { node: { ...this.node, columns: [column2] } };
|
|
340
|
+
}
|
|
134
341
|
/**
|
|
135
342
|
* Lock the selected rows for update (`SELECT ... FOR UPDATE`), à la
|
|
136
343
|
* SQLAlchemy's `with_for_update()`.
|
|
@@ -284,8 +491,8 @@ function toDict(model, row) {
|
|
|
284
491
|
function toJSON(model, row) {
|
|
285
492
|
const columns = columnsOf(model);
|
|
286
493
|
const out = {};
|
|
287
|
-
for (const [name,
|
|
288
|
-
out[name] = encodeValue(
|
|
494
|
+
for (const [name, col2] of Object.entries(columns)) {
|
|
495
|
+
out[name] = encodeValue(col2, row[name] ?? null);
|
|
289
496
|
}
|
|
290
497
|
return out;
|
|
291
498
|
}
|
|
@@ -296,10 +503,10 @@ function fromDict(model, data) {
|
|
|
296
503
|
const columns = columnsOf(model);
|
|
297
504
|
const out = {};
|
|
298
505
|
const issues = [];
|
|
299
|
-
for (const [name,
|
|
506
|
+
for (const [name, col2] of Object.entries(columns)) {
|
|
300
507
|
const present = name in data && data[name] !== void 0 && data[name] !== null;
|
|
301
508
|
if (!present) {
|
|
302
|
-
const required =
|
|
509
|
+
const required = col2.flags.notNull && !col2.flags.hasDefault;
|
|
303
510
|
if (required) {
|
|
304
511
|
issues.push(`missing required column "${name}"`);
|
|
305
512
|
continue;
|
|
@@ -308,7 +515,7 @@ function fromDict(model, data) {
|
|
|
308
515
|
continue;
|
|
309
516
|
}
|
|
310
517
|
try {
|
|
311
|
-
out[name] = decodeValue(
|
|
518
|
+
out[name] = decodeValue(col2, data[name]);
|
|
312
519
|
} catch (error) {
|
|
313
520
|
issues.push(`column "${name}": ${error.message}`);
|
|
314
521
|
}
|
|
@@ -359,8 +566,8 @@ function mapperFor(model) {
|
|
|
359
566
|
const props = columnPropsOf(model);
|
|
360
567
|
const names = props ? Object.fromEntries(Object.entries(props).map(([db, prop]) => [prop, db])) : null;
|
|
361
568
|
const decoders = /* @__PURE__ */ new Map();
|
|
362
|
-
for (const [prop,
|
|
363
|
-
const decoder = decoderFor(
|
|
569
|
+
for (const [prop, col2] of Object.entries(columnsOf(model))) {
|
|
570
|
+
const decoder = decoderFor(col2.type);
|
|
364
571
|
if (decoder) decoders.set(names?.[prop] ?? prop, decoder);
|
|
365
572
|
}
|
|
366
573
|
const mapper = { props, decoders };
|
|
@@ -391,19 +598,34 @@ function assertWritableValues(model, values, clause) {
|
|
|
391
598
|
const columns = columnsOf(model);
|
|
392
599
|
const issues = [];
|
|
393
600
|
for (const [key, value] of Object.entries(values)) {
|
|
394
|
-
const
|
|
395
|
-
if (!
|
|
601
|
+
const col2 = columns[key];
|
|
602
|
+
if (!col2) {
|
|
396
603
|
issues.push(`${clause}: "${key}" is not a column of ${model.tablename}`);
|
|
397
604
|
continue;
|
|
398
605
|
}
|
|
399
606
|
if (isSqlExpression(value) || isBindableScalar(value)) continue;
|
|
400
|
-
if (typeof value === "object" && STRUCTURED_KINDS.has(
|
|
607
|
+
if (typeof value === "object" && STRUCTURED_KINDS.has(col2.type.kind)) continue;
|
|
401
608
|
issues.push(
|
|
402
|
-
`${clause}: "${key}" got ${describeValue(value)}, which cannot be bound to a ${
|
|
609
|
+
`${clause}: "${key}" got ${describeValue(value)}, which cannot be bound to a ${col2.type.kind} column \u2014 use sql.raw()/sql.expr\`...\` for a SQL expression`
|
|
403
610
|
);
|
|
404
611
|
}
|
|
405
612
|
if (issues.length > 0) throw new ValidationError(model.tablename, issues);
|
|
406
613
|
}
|
|
614
|
+
function assertConsistentRows(model, rows) {
|
|
615
|
+
if (rows.length < 2) return;
|
|
616
|
+
const union = /* @__PURE__ */ new Set();
|
|
617
|
+
for (const row of rows) for (const key of Object.keys(row)) union.add(key);
|
|
618
|
+
const inconsistent = [...union].filter((key) => rows.some((row) => !(key in row)));
|
|
619
|
+
if (inconsistent.length === 0) return;
|
|
620
|
+
const columns = columnsOf(model);
|
|
621
|
+
const defaulted = inconsistent.filter((key) => columns[key]?.flags.hasDefault);
|
|
622
|
+
if (defaulted.length === 0) return;
|
|
623
|
+
const named = defaulted.map((c) => `"${c}"`).join(", ");
|
|
624
|
+
const verb = defaulted.length === 1 ? "has" : "have";
|
|
625
|
+
throw new ValidationError(model.tablename, [
|
|
626
|
+
`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.`
|
|
627
|
+
]);
|
|
628
|
+
}
|
|
407
629
|
function describeValue(value) {
|
|
408
630
|
if (typeof value === "function") return "a function";
|
|
409
631
|
if (Array.isArray(value)) return "an array";
|
|
@@ -426,11 +648,13 @@ var InsertBuilder = class _InsertBuilder {
|
|
|
426
648
|
* @param rows One row, or an array of rows.
|
|
427
649
|
* @returns A builder carrying the rows.
|
|
428
650
|
* @throws ValidationError When a value is not a column value the dialect can
|
|
429
|
-
* bind (see the `sql` helpers for writing an expression instead)
|
|
651
|
+
* bind (see the `sql` helpers for writing an expression instead), or when the
|
|
652
|
+
* rows of a multi-row insert disagree about a column that has a default.
|
|
430
653
|
*/
|
|
431
654
|
values(rows) {
|
|
432
655
|
const list = Array.isArray(rows) ? rows : [rows];
|
|
433
656
|
for (const row of list) assertWritableValues(this.source, row, "values");
|
|
657
|
+
assertConsistentRows(this.source, list);
|
|
434
658
|
return this.with({ values: list });
|
|
435
659
|
}
|
|
436
660
|
/**
|
|
@@ -732,6 +956,18 @@ var Params = class {
|
|
|
732
956
|
return this.placeholder(this.values.length);
|
|
733
957
|
}
|
|
734
958
|
};
|
|
959
|
+
function insertColumns(rows) {
|
|
960
|
+
const columns = [];
|
|
961
|
+
const seen = /* @__PURE__ */ new Set();
|
|
962
|
+
for (const row of rows) {
|
|
963
|
+
for (const key of Object.keys(row)) {
|
|
964
|
+
if (seen.has(key)) continue;
|
|
965
|
+
seen.add(key);
|
|
966
|
+
columns.push(key);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return columns;
|
|
970
|
+
}
|
|
735
971
|
function insertHasExpression(node) {
|
|
736
972
|
for (const row of node.values) {
|
|
737
973
|
for (const value of Object.values(row)) {
|
|
@@ -755,6 +991,15 @@ var BaseDialect = class _BaseDialect {
|
|
|
755
991
|
static insertTemplates = /* @__PURE__ */ new Map();
|
|
756
992
|
/** Quoted-identifier cache (see {@link quoteId}). Shared across dialects. */
|
|
757
993
|
static quotedIds = /* @__PURE__ */ new Map();
|
|
994
|
+
/**
|
|
995
|
+
* Validate a subquery operand before it is rendered, for dialects that restrict
|
|
996
|
+
* what an `IN (SELECT ...)` may contain. The default accepts everything.
|
|
997
|
+
*
|
|
998
|
+
* @param _node The subquery's AST.
|
|
999
|
+
* @throws Error When the dialect cannot execute this subquery.
|
|
1000
|
+
*/
|
|
1001
|
+
checkSubquery(_node) {
|
|
1002
|
+
}
|
|
758
1003
|
/**
|
|
759
1004
|
* The SQL operator for an array containment/overlap test.
|
|
760
1005
|
*
|
|
@@ -878,6 +1123,19 @@ var BaseDialect = class _BaseDialect {
|
|
|
878
1123
|
return ` ${strength}${of}${wait}`;
|
|
879
1124
|
}
|
|
880
1125
|
// ---- statements -------------------------------------------------------
|
|
1126
|
+
/**
|
|
1127
|
+
* Compile a SELECT.
|
|
1128
|
+
*
|
|
1129
|
+
* Two alias rules differ between clauses and are handled here: PostgreSQL does
|
|
1130
|
+
* NOT accept a `SELECT` alias in `HAVING`, so an aggregate key is re-emitted as
|
|
1131
|
+
* its expression (`COUNT(*) > $1`), a form every dialect accepts; `ORDER BY`,
|
|
1132
|
+
* by contrast, accepts the output alias everywhere, so it is emitted as
|
|
1133
|
+
* written.
|
|
1134
|
+
*
|
|
1135
|
+
* @param node The select AST.
|
|
1136
|
+
* @param params The parameter collector.
|
|
1137
|
+
* @returns The SQL text.
|
|
1138
|
+
*/
|
|
881
1139
|
compileSelect(node, params) {
|
|
882
1140
|
const names = node.names;
|
|
883
1141
|
let cols;
|
|
@@ -901,10 +1159,21 @@ var BaseDialect = class _BaseDialect {
|
|
|
901
1159
|
if (node.groupBy.length > 0) {
|
|
902
1160
|
sql2 += ` GROUP BY ${node.groupBy.map((c) => this.columnId(c, names)).join(", ")}`;
|
|
903
1161
|
}
|
|
1162
|
+
const aggByAlias = new Map(node.aggregates.map((a) => [a.alias, a]));
|
|
1163
|
+
if (node.having) {
|
|
1164
|
+
const having = this.compileCondition(node.having, params, (key) => {
|
|
1165
|
+
const agg = aggByAlias.get(key);
|
|
1166
|
+
if (!agg) return this.columnId(key, names);
|
|
1167
|
+
const inner = agg.column === "*" ? "*" : this.columnId(agg.column, names);
|
|
1168
|
+
return `${agg.fn.toUpperCase()}(${inner})`;
|
|
1169
|
+
});
|
|
1170
|
+
if (having) sql2 += ` HAVING ${having}`;
|
|
1171
|
+
}
|
|
904
1172
|
if (node.orderBy.length > 0) {
|
|
905
|
-
const terms = node.orderBy.map(
|
|
906
|
-
(t)
|
|
907
|
-
|
|
1173
|
+
const terms = node.orderBy.map((t) => {
|
|
1174
|
+
const id = aggByAlias.has(t.column) ? this.quoteId(t.column) : this.columnId(t.column, names);
|
|
1175
|
+
return `${id} ${t.direction === "desc" ? "DESC" : "ASC"}`;
|
|
1176
|
+
}).join(", ");
|
|
908
1177
|
sql2 += ` ORDER BY ${terms}`;
|
|
909
1178
|
}
|
|
910
1179
|
if (node.limit !== void 0) sql2 += ` LIMIT ${params.bind(node.limit)}`;
|
|
@@ -928,7 +1197,7 @@ var BaseDialect = class _BaseDialect {
|
|
|
928
1197
|
* SQL order, so placeholder positions stay correct.
|
|
929
1198
|
*/
|
|
930
1199
|
compileInsert(node, params) {
|
|
931
|
-
const columns =
|
|
1200
|
+
const columns = insertColumns(node.values);
|
|
932
1201
|
const conflict = node.onConflict;
|
|
933
1202
|
const cacheable = conflict?.targetWhere === void 0 && conflict?.updateWhere === void 0 && !insertHasExpression(node);
|
|
934
1203
|
if (!cacheable) return this.compileInsertDirect(node, columns, params);
|
|
@@ -1040,7 +1309,7 @@ var BaseDialect = class _BaseDialect {
|
|
|
1040
1309
|
compileUpdate(node, params) {
|
|
1041
1310
|
const names = node.names;
|
|
1042
1311
|
const sets = Object.entries(node.set).map(
|
|
1043
|
-
([
|
|
1312
|
+
([col2, value]) => `${this.columnId(col2, names)} = ${this.renderValue(value, params)}`
|
|
1044
1313
|
).join(", ");
|
|
1045
1314
|
let sql2 = `UPDATE ${this.quoteId(node.table)} SET ${sets}`;
|
|
1046
1315
|
const where = this.compileCondition(
|
|
@@ -1133,6 +1402,83 @@ var BaseDialect = class _BaseDialect {
|
|
|
1133
1402
|
const inner = this.compileCondition(node.part, params, idFor);
|
|
1134
1403
|
return inner ? `NOT (${inner})` : "";
|
|
1135
1404
|
}
|
|
1405
|
+
case "compare": {
|
|
1406
|
+
const left = this.renderExpr(node.left, params, idFor);
|
|
1407
|
+
if (node.right.kind === "value") {
|
|
1408
|
+
return this.compileOperator(left, node.op, node.right.value, params);
|
|
1409
|
+
}
|
|
1410
|
+
return this.compileExprOperator(
|
|
1411
|
+
left,
|
|
1412
|
+
node.op,
|
|
1413
|
+
this.renderExpr(node.right, params, idFor)
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Render one side of a comparison.
|
|
1420
|
+
*
|
|
1421
|
+
* A column reference goes through `idFor`, so an explicit `.name()` mapping and
|
|
1422
|
+
* join qualification apply here exactly as they do in the object form of
|
|
1423
|
+
* `where` — `col()` is not a way around them. Only a `value` node binds.
|
|
1424
|
+
*
|
|
1425
|
+
* @param node The expression AST.
|
|
1426
|
+
* @param params The parameter collector.
|
|
1427
|
+
* @param idFor The identifier resolver for the enclosing statement.
|
|
1428
|
+
* @returns The SQL text of the expression.
|
|
1429
|
+
*/
|
|
1430
|
+
renderExpr(node, params, idFor) {
|
|
1431
|
+
switch (node.kind) {
|
|
1432
|
+
case "column":
|
|
1433
|
+
return idFor(node.name);
|
|
1434
|
+
case "value":
|
|
1435
|
+
return params.bind(node.value);
|
|
1436
|
+
case "fn": {
|
|
1437
|
+
const args = node.args.map((a) => this.renderExpr(a, params, idFor)).join(", ");
|
|
1438
|
+
return `${node.name}(${args})`;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Compile a comparison whose right-hand side is another expression rather than
|
|
1444
|
+
* a bound value (`total > paid`, `lower(a) = lower(b)`).
|
|
1445
|
+
*
|
|
1446
|
+
* The list and null operators are excluded: `IN`, `BETWEEN` and `IS NULL` take
|
|
1447
|
+
* a value operand, and accepting an expression there would silently compile to
|
|
1448
|
+
* something else.
|
|
1449
|
+
*
|
|
1450
|
+
* @param left The rendered left-hand side.
|
|
1451
|
+
* @param op The operator name.
|
|
1452
|
+
* @param right The rendered right-hand side.
|
|
1453
|
+
* @returns The SQL text of the predicate.
|
|
1454
|
+
* @throws Error When the operator needs a value operand.
|
|
1455
|
+
*/
|
|
1456
|
+
compileExprOperator(left, op, right) {
|
|
1457
|
+
switch (op) {
|
|
1458
|
+
case "eq":
|
|
1459
|
+
return `${left} = ${right}`;
|
|
1460
|
+
case "ne":
|
|
1461
|
+
return `${left} <> ${right}`;
|
|
1462
|
+
case "gt":
|
|
1463
|
+
return `${left} > ${right}`;
|
|
1464
|
+
case "gte":
|
|
1465
|
+
return `${left} >= ${right}`;
|
|
1466
|
+
case "lt":
|
|
1467
|
+
return `${left} < ${right}`;
|
|
1468
|
+
case "lte":
|
|
1469
|
+
return `${left} <= ${right}`;
|
|
1470
|
+
case "like":
|
|
1471
|
+
return `${left} LIKE ${right}`;
|
|
1472
|
+
case "ilike":
|
|
1473
|
+
return this.ilike(left, right);
|
|
1474
|
+
case "ieq":
|
|
1475
|
+
return `lower(${left}) = lower(${right})`;
|
|
1476
|
+
case "contains":
|
|
1477
|
+
case "containedBy":
|
|
1478
|
+
case "overlaps":
|
|
1479
|
+
return `${left} ${this.arrayOperator(op)} ${right}`;
|
|
1480
|
+
default:
|
|
1481
|
+
throw new Error(`The "${op}" operator takes a value operand, not an expression.`);
|
|
1136
1482
|
}
|
|
1137
1483
|
}
|
|
1138
1484
|
compileOperator(id, op, operand, params) {
|
|
@@ -1175,12 +1521,33 @@ var BaseDialect = class _BaseDialect {
|
|
|
1175
1521
|
throw new Error(`Unknown operator ${JSON.stringify(op)}`);
|
|
1176
1522
|
}
|
|
1177
1523
|
}
|
|
1178
|
-
|
|
1524
|
+
/**
|
|
1525
|
+
* Compile `IN` / `NOT IN`, whose operand is either a value list or a
|
|
1526
|
+
* single-column subquery.
|
|
1527
|
+
*
|
|
1528
|
+
* The subquery is rendered at the position it appears in the outer statement
|
|
1529
|
+
* and shares the same parameter collector, so its own placeholders land in the
|
|
1530
|
+
* right order — and it keeps its own `names` map, since the inner model may use
|
|
1531
|
+
* a different naming convention than the outer one.
|
|
1532
|
+
*
|
|
1533
|
+
* @param id The quoted column identifier being tested.
|
|
1534
|
+
* @param operand A list of values, or a {@link Subquery}.
|
|
1535
|
+
* @param params The parameter collector for the statement being compiled.
|
|
1536
|
+
* @param negate True for `NOT IN`.
|
|
1537
|
+
* @returns The SQL text of the predicate.
|
|
1538
|
+
*/
|
|
1539
|
+
compileIn(id, operand, params, negate) {
|
|
1540
|
+
const keyword = negate ? "NOT IN" : "IN";
|
|
1541
|
+
if (isSubquery(operand)) {
|
|
1542
|
+
this.checkSubquery(operand.node);
|
|
1543
|
+
return `${id} ${keyword} (${this.compileSelect(operand.node, params)})`;
|
|
1544
|
+
}
|
|
1545
|
+
const values = operand;
|
|
1179
1546
|
if (values.length === 0) {
|
|
1180
1547
|
return negate ? "1 = 1" : "1 = 0";
|
|
1181
1548
|
}
|
|
1182
1549
|
const list = values.map((v) => params.bind(v)).join(", ");
|
|
1183
|
-
return `${id} ${
|
|
1550
|
+
return `${id} ${keyword} (${list})`;
|
|
1184
1551
|
}
|
|
1185
1552
|
};
|
|
1186
1553
|
var SqliteDialect = class extends BaseDialect {
|
|
@@ -1232,6 +1599,19 @@ var MysqlDialect = class extends BaseDialect {
|
|
|
1232
1599
|
mysqlQuotedIds.set(name, quoted);
|
|
1233
1600
|
return quoted;
|
|
1234
1601
|
}
|
|
1602
|
+
/**
|
|
1603
|
+
* MySQL rejects `LIMIT` inside an `IN` subquery with
|
|
1604
|
+
* `ER_NOT_SUPPORTED_YET: This version of MySQL doesn't yet support
|
|
1605
|
+
* 'LIMIT & IN/ALL/ANY/SOME subquery'`. Failing at compile time names the fix
|
|
1606
|
+
* instead of surfacing that error from the driver at runtime.
|
|
1607
|
+
*/
|
|
1608
|
+
checkSubquery(node) {
|
|
1609
|
+
if (node.limit !== void 0 || node.offset !== void 0) {
|
|
1610
|
+
throw new Error(
|
|
1611
|
+
"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."
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1235
1615
|
renderConflict(onConflict, conflictCols, nextValue, names) {
|
|
1236
1616
|
if (onConflict.targetWhere || onConflict.updateWhere) {
|
|
1237
1617
|
throw new Error(
|
|
@@ -1239,16 +1619,25 @@ var MysqlDialect = class extends BaseDialect {
|
|
|
1239
1619
|
);
|
|
1240
1620
|
}
|
|
1241
1621
|
if (onConflict.update === "nothing") {
|
|
1242
|
-
const
|
|
1243
|
-
return ` ON DUPLICATE KEY UPDATE ${
|
|
1622
|
+
const col2 = this.columnId(onConflict.target[0] ?? "id", names);
|
|
1623
|
+
return ` ON DUPLICATE KEY UPDATE ${col2} = ${col2}`;
|
|
1244
1624
|
}
|
|
1245
1625
|
const assignments = conflictCols.map((c) => `${this.columnId(c, names)} = ${nextValue()}`).join(", ");
|
|
1246
1626
|
return ` ON DUPLICATE KEY UPDATE ${assignments}`;
|
|
1247
1627
|
}
|
|
1628
|
+
/**
|
|
1629
|
+
* MySQL has no `RETURNING`, so it cannot be compiled into a statement.
|
|
1630
|
+
*
|
|
1631
|
+
* `session.execute()` still honors `.returning()` on a **single-row INSERT** by
|
|
1632
|
+
* running the insert and reading the row back by key on the same connection —
|
|
1633
|
+
* that is execution, not compilation, so it never reaches here. Compiling a
|
|
1634
|
+
* node with `returning` directly is an error, rather than SQL that silently
|
|
1635
|
+
* returns nothing.
|
|
1636
|
+
*/
|
|
1248
1637
|
compileReturning(returning) {
|
|
1249
1638
|
if (returning === null) return "";
|
|
1250
1639
|
throw new Error(
|
|
1251
|
-
"RETURNING
|
|
1640
|
+
"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."
|
|
1252
1641
|
);
|
|
1253
1642
|
}
|
|
1254
1643
|
};
|
|
@@ -1361,8 +1750,8 @@ var RecordNotFound = class extends Error {
|
|
|
1361
1750
|
}
|
|
1362
1751
|
};
|
|
1363
1752
|
function primaryKeyOf(model) {
|
|
1364
|
-
for (const [name,
|
|
1365
|
-
if (
|
|
1753
|
+
for (const [name, col2] of Object.entries(columnsOf(model))) {
|
|
1754
|
+
if (col2.flags.primaryKey) return name;
|
|
1366
1755
|
}
|
|
1367
1756
|
throw new Error(`${model.tablename} has no primary key`);
|
|
1368
1757
|
}
|
|
@@ -1450,8 +1839,8 @@ var BaseRepository = class {
|
|
|
1450
1839
|
|
|
1451
1840
|
// src/active-record.ts
|
|
1452
1841
|
function primaryKeyOf2(model) {
|
|
1453
|
-
for (const [name,
|
|
1454
|
-
if (
|
|
1842
|
+
for (const [name, col2] of Object.entries(columnsOf(model))) {
|
|
1843
|
+
if (col2.flags.primaryKey) return name;
|
|
1455
1844
|
}
|
|
1456
1845
|
throw new Error(`${model.tablename} has no primary key`);
|
|
1457
1846
|
}
|
|
@@ -1617,10 +2006,18 @@ var NodeSqliteDriver = class _NodeSqliteDriver {
|
|
|
1617
2006
|
constructor(database) {
|
|
1618
2007
|
this.db = database;
|
|
1619
2008
|
}
|
|
1620
|
-
/**
|
|
1621
|
-
|
|
2009
|
+
/**
|
|
2010
|
+
* Open a `node:sqlite` database at the given path (or `:memory:`).
|
|
2011
|
+
*
|
|
2012
|
+
* @param path The database file, or `":memory:"`.
|
|
2013
|
+
* @param options Passed straight to `DatabaseSync` (`readOnly`, `timeout`, …).
|
|
2014
|
+
* @returns A driver over the open handle.
|
|
2015
|
+
*/
|
|
2016
|
+
static open(path, options) {
|
|
1622
2017
|
const { DatabaseSync } = nodeRequire("node:sqlite");
|
|
1623
|
-
return new _NodeSqliteDriver(
|
|
2018
|
+
return new _NodeSqliteDriver(
|
|
2019
|
+
options ? new DatabaseSync(path, { ...options }) : new DatabaseSync(path)
|
|
2020
|
+
);
|
|
1624
2021
|
}
|
|
1625
2022
|
/** Return the cached prepared statement for `sql`, preparing it on first use. */
|
|
1626
2023
|
// biome-ignore lint/suspicious/noExplicitAny: statement type is unavailable here.
|
|
@@ -1787,6 +2184,18 @@ var AsyncResult = class {
|
|
|
1787
2184
|
}
|
|
1788
2185
|
};
|
|
1789
2186
|
var savepointCounter = 0;
|
|
2187
|
+
function needsInsertReadBack(dialect, node) {
|
|
2188
|
+
return dialect.name === "mysql" && node.kind === "insert" && node.returning !== null;
|
|
2189
|
+
}
|
|
2190
|
+
function singlePrimaryKey(model) {
|
|
2191
|
+
const keys = Object.entries(columnsOf(model)).filter(([, column2]) => column2.flags.primaryKey).map(([name]) => name);
|
|
2192
|
+
if (keys.length !== 1) {
|
|
2193
|
+
throw new Error(
|
|
2194
|
+
`${model.tablename} needs exactly one primary key to read an insert back on a dialect without RETURNING; found ${keys.length}.`
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
return keys[0];
|
|
2198
|
+
}
|
|
1790
2199
|
function assertRawParams(params) {
|
|
1791
2200
|
if (!Array.isArray(params)) {
|
|
1792
2201
|
throw new TypeError(
|
|
@@ -1859,10 +2268,10 @@ var SyncSession = class {
|
|
|
1859
2268
|
return new SyncResult(rows, result.changes);
|
|
1860
2269
|
}
|
|
1861
2270
|
/** Run `fn` inside a transaction: commit on success, rollback on throw. */
|
|
1862
|
-
transaction(
|
|
2271
|
+
transaction(fn2) {
|
|
1863
2272
|
this.exec("BEGIN", []);
|
|
1864
2273
|
try {
|
|
1865
|
-
const out =
|
|
2274
|
+
const out = fn2(this);
|
|
1866
2275
|
this.exec("COMMIT", []);
|
|
1867
2276
|
return out;
|
|
1868
2277
|
} catch (error) {
|
|
@@ -1871,12 +2280,12 @@ var SyncSession = class {
|
|
|
1871
2280
|
}
|
|
1872
2281
|
}
|
|
1873
2282
|
/** Run `fn` inside a SAVEPOINT (nested transaction). */
|
|
1874
|
-
beginNested(
|
|
2283
|
+
beginNested(fn2) {
|
|
1875
2284
|
savepointCounter += 1;
|
|
1876
2285
|
const name = `qsp_${savepointCounter}`;
|
|
1877
2286
|
this.exec(`SAVEPOINT ${name}`, []);
|
|
1878
2287
|
try {
|
|
1879
|
-
const out =
|
|
2288
|
+
const out = fn2(this);
|
|
1880
2289
|
this.exec(`RELEASE ${name}`, []);
|
|
1881
2290
|
return out;
|
|
1882
2291
|
} catch (error) {
|
|
@@ -1974,6 +2383,11 @@ var AsyncSession = class _AsyncSession {
|
|
|
1974
2383
|
}
|
|
1975
2384
|
execute(builder) {
|
|
1976
2385
|
const node = builder.node;
|
|
2386
|
+
if (needsInsertReadBack(this.dialect, node)) {
|
|
2387
|
+
return new AsyncResult(
|
|
2388
|
+
this.insertAndReadBack(builder, node)
|
|
2389
|
+
);
|
|
2390
|
+
}
|
|
1977
2391
|
const { sql: sql2, params } = this.dialect.compile(node);
|
|
1978
2392
|
const inner = this.exec(sql2, params).then((result) => {
|
|
1979
2393
|
const rows = mapRows(builder, result.rows);
|
|
@@ -1981,6 +2395,53 @@ var AsyncSession = class _AsyncSession {
|
|
|
1981
2395
|
});
|
|
1982
2396
|
return new AsyncResult(inner);
|
|
1983
2397
|
}
|
|
2398
|
+
/**
|
|
2399
|
+
* Honor `.returning()` on a dialect without `RETURNING`, by inserting and then
|
|
2400
|
+
* reading the row back by key.
|
|
2401
|
+
*
|
|
2402
|
+
* Both statements must run on **one** connection, because `LAST_INSERT_ID()` is
|
|
2403
|
+
* per-connection: outside a transaction the pooled driver is reserved for the
|
|
2404
|
+
* pair; inside one, the session already holds a pinned connection (a reserved
|
|
2405
|
+
* driver exposes no `reserve`), so it runs there directly.
|
|
2406
|
+
*
|
|
2407
|
+
* @param builder The insert builder, for its source model.
|
|
2408
|
+
* @param node The insert AST, whose `returning` drives the read-back.
|
|
2409
|
+
* @returns The result view over the read-back row.
|
|
2410
|
+
* @throws Error When the insert writes more than one row — `LAST_INSERT_ID()`
|
|
2411
|
+
* identifies only the first, and the rest are consecutive only under some
|
|
2412
|
+
* auto-increment lock modes.
|
|
2413
|
+
*/
|
|
2414
|
+
async insertAndReadBack(builder, node) {
|
|
2415
|
+
if (node.values.length !== 1) {
|
|
2416
|
+
throw new Error(
|
|
2417
|
+
`${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().`
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
const model = builder.source;
|
|
2421
|
+
const pk = singlePrimaryKey(model);
|
|
2422
|
+
const supplied = node.values[0][pk];
|
|
2423
|
+
const readBack = select(model).where(
|
|
2424
|
+
supplied === void 0 || supplied === null ? col(pk).eq(fn.call("LAST_INSERT_ID")) : { [pk]: supplied }
|
|
2425
|
+
);
|
|
2426
|
+
const insertSql = this.dialect.compile({ ...node, returning: null });
|
|
2427
|
+
const selectSql = this.dialect.compile(
|
|
2428
|
+
node.returning === "*" || node.returning === null ? readBack.node : { ...readBack.node, columns: node.returning }
|
|
2429
|
+
);
|
|
2430
|
+
const run = async (driver) => {
|
|
2431
|
+
const scoped = new _AsyncSession(driver, this.dialect, this.logger);
|
|
2432
|
+
const written = await scoped.exec(insertSql.sql, insertSql.params);
|
|
2433
|
+
const read = await scoped.exec(selectSql.sql, selectSql.params);
|
|
2434
|
+
const rows = read.rows.map((row) => coerceRow(model, row));
|
|
2435
|
+
return new SyncResult(rows, written.changes);
|
|
2436
|
+
};
|
|
2437
|
+
if (!this.driver.reserve) return run(this.driver);
|
|
2438
|
+
const reserved = await this.driver.reserve();
|
|
2439
|
+
try {
|
|
2440
|
+
return await run(reserved);
|
|
2441
|
+
} finally {
|
|
2442
|
+
await reserved.release();
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
1984
2445
|
/** Lazily iterate result rows. Uses driver streaming when available. */
|
|
1985
2446
|
async *stream(builder) {
|
|
1986
2447
|
const node = builder.node;
|
|
@@ -2001,13 +2462,13 @@ var AsyncSession = class _AsyncSession {
|
|
|
2001
2462
|
yield coerceOne(builder, raw);
|
|
2002
2463
|
}
|
|
2003
2464
|
}
|
|
2004
|
-
async transaction(
|
|
2465
|
+
async transaction(fn2) {
|
|
2005
2466
|
if (this.driver.reserve) {
|
|
2006
2467
|
const reserved = await this.driver.reserve();
|
|
2007
2468
|
const scoped = new _AsyncSession(reserved, this.dialect, this.logger);
|
|
2008
2469
|
try {
|
|
2009
2470
|
await scoped.exec("BEGIN", []);
|
|
2010
|
-
const out = await
|
|
2471
|
+
const out = await fn2(scoped);
|
|
2011
2472
|
await scoped.exec("COMMIT", []);
|
|
2012
2473
|
return out;
|
|
2013
2474
|
} catch (error) {
|
|
@@ -2019,7 +2480,7 @@ var AsyncSession = class _AsyncSession {
|
|
|
2019
2480
|
}
|
|
2020
2481
|
await this.exec("BEGIN", []);
|
|
2021
2482
|
try {
|
|
2022
|
-
const out = await
|
|
2483
|
+
const out = await fn2(this);
|
|
2023
2484
|
await this.exec("COMMIT", []);
|
|
2024
2485
|
return out;
|
|
2025
2486
|
} catch (error) {
|
|
@@ -2035,6 +2496,13 @@ var AsyncSession = class _AsyncSession {
|
|
|
2035
2496
|
await this.close();
|
|
2036
2497
|
}
|
|
2037
2498
|
};
|
|
2499
|
+
function emitNotice(logger, notice) {
|
|
2500
|
+
if (!logger) return;
|
|
2501
|
+
try {
|
|
2502
|
+
logger(notice);
|
|
2503
|
+
} catch {
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2038
2506
|
var SyncEngine = class {
|
|
2039
2507
|
constructor(driver, logger) {
|
|
2040
2508
|
this.driver = driver;
|
|
@@ -2046,8 +2514,8 @@ var SyncEngine = class {
|
|
|
2046
2514
|
session() {
|
|
2047
2515
|
return new SyncSession(this.driver, getDialect("sqlite"), this.logger);
|
|
2048
2516
|
}
|
|
2049
|
-
transaction(
|
|
2050
|
-
return this.session().transaction(
|
|
2517
|
+
transaction(fn2) {
|
|
2518
|
+
return this.session().transaction(fn2);
|
|
2051
2519
|
}
|
|
2052
2520
|
close() {
|
|
2053
2521
|
this.driver.close();
|
|
@@ -2069,8 +2537,8 @@ var AsyncEngine = class {
|
|
|
2069
2537
|
session() {
|
|
2070
2538
|
return new AsyncSession(this.driver, getDialect(this.dialect), this.logger);
|
|
2071
2539
|
}
|
|
2072
|
-
transaction(
|
|
2073
|
-
return this.session().transaction(
|
|
2540
|
+
transaction(fn2) {
|
|
2541
|
+
return this.session().transaction(fn2);
|
|
2074
2542
|
}
|
|
2075
2543
|
async close() {
|
|
2076
2544
|
await this.driver.close();
|
|
@@ -2080,6 +2548,16 @@ var AsyncEngine = class {
|
|
|
2080
2548
|
await this.close();
|
|
2081
2549
|
}
|
|
2082
2550
|
};
|
|
2551
|
+
function toAsyncDriver(driver) {
|
|
2552
|
+
return {
|
|
2553
|
+
async execute(sql2, params) {
|
|
2554
|
+
return await driver.execute(sql2, params);
|
|
2555
|
+
},
|
|
2556
|
+
async close() {
|
|
2557
|
+
await driver.close();
|
|
2558
|
+
}
|
|
2559
|
+
};
|
|
2560
|
+
}
|
|
2083
2561
|
function asAsync(driver) {
|
|
2084
2562
|
const syncIterate = driver.iterate?.bind(driver);
|
|
2085
2563
|
return {
|
|
@@ -2092,8 +2570,8 @@ function asAsync(driver) {
|
|
|
2092
2570
|
} : {}
|
|
2093
2571
|
};
|
|
2094
2572
|
}
|
|
2095
|
-
function openSqliteDriver(path,
|
|
2096
|
-
return NodeSqliteDriver.open(path);
|
|
2573
|
+
function openSqliteDriver(path, options) {
|
|
2574
|
+
return NodeSqliteDriver.open(path, options?.driverOptions);
|
|
2097
2575
|
}
|
|
2098
2576
|
function createSyncEngine(url, options) {
|
|
2099
2577
|
const parsed = parseDatabaseUrl(url);
|
|
@@ -2103,7 +2581,7 @@ function createSyncEngine(url, options) {
|
|
|
2103
2581
|
);
|
|
2104
2582
|
}
|
|
2105
2583
|
return new SyncEngine(
|
|
2106
|
-
openSqliteDriver(parsed.database ?? ":memory:"),
|
|
2584
|
+
openSqliteDriver(parsed.database ?? ":memory:", options),
|
|
2107
2585
|
options?.onQuery
|
|
2108
2586
|
);
|
|
2109
2587
|
}
|
|
@@ -2111,20 +2589,20 @@ function createEngine(url, options) {
|
|
|
2111
2589
|
const parsed = parseDatabaseUrl(url);
|
|
2112
2590
|
if (parsed.dialect === "sqlite") {
|
|
2113
2591
|
return new AsyncEngine(
|
|
2114
|
-
asAsync(openSqliteDriver(parsed.database ?? ":memory:")),
|
|
2592
|
+
asAsync(openSqliteDriver(parsed.database ?? ":memory:", options)),
|
|
2115
2593
|
"sqlite",
|
|
2116
2594
|
options?.onQuery
|
|
2117
2595
|
);
|
|
2118
2596
|
}
|
|
2119
2597
|
if (parsed.dialect === "mysql") {
|
|
2120
2598
|
return new AsyncEngine(
|
|
2121
|
-
createMysqlDriver(parsed.raw, options
|
|
2599
|
+
createMysqlDriver(parsed.raw, options),
|
|
2122
2600
|
"mysql",
|
|
2123
2601
|
options?.onQuery
|
|
2124
2602
|
);
|
|
2125
2603
|
}
|
|
2126
2604
|
return new AsyncEngine(
|
|
2127
|
-
createPostgresDriver(parsed.raw, options
|
|
2605
|
+
createPostgresDriver(parsed.raw, options),
|
|
2128
2606
|
"postgresql",
|
|
2129
2607
|
options?.onQuery
|
|
2130
2608
|
);
|
|
@@ -2144,7 +2622,8 @@ function toMysqlResult(rows) {
|
|
|
2144
2622
|
const header = rows;
|
|
2145
2623
|
return { rows: [], changes: header.affectedRows ?? 0 };
|
|
2146
2624
|
}
|
|
2147
|
-
function createMysqlDriver(url,
|
|
2625
|
+
function createMysqlDriver(url, options) {
|
|
2626
|
+
const pool = options?.pool;
|
|
2148
2627
|
let poolHandle;
|
|
2149
2628
|
const ensure = async () => {
|
|
2150
2629
|
if (poolHandle) return;
|
|
@@ -2157,6 +2636,7 @@ function createMysqlDriver(url, pool) {
|
|
|
2157
2636
|
if (pool?.size !== void 0) opts.connectionLimit = pool.size;
|
|
2158
2637
|
if (pool?.idleTimeoutMs !== void 0) opts.idleTimeout = pool.idleTimeoutMs;
|
|
2159
2638
|
if (pool?.connectTimeoutMs !== void 0) opts.connectTimeout = pool.connectTimeoutMs;
|
|
2639
|
+
Object.assign(opts, options?.driverOptions ?? {});
|
|
2160
2640
|
poolHandle = mod.createPool(opts);
|
|
2161
2641
|
};
|
|
2162
2642
|
const runOn = async (queryable, sql2, params) => {
|
|
@@ -2190,7 +2670,8 @@ function toPostgresResult(rows) {
|
|
|
2190
2670
|
const arr = rows;
|
|
2191
2671
|
return { rows: Array.from(arr), changes: arr.count ?? arr.length };
|
|
2192
2672
|
}
|
|
2193
|
-
function createPostgresDriver(url,
|
|
2673
|
+
function createPostgresDriver(url, options) {
|
|
2674
|
+
const pool = options?.pool;
|
|
2194
2675
|
let client;
|
|
2195
2676
|
const ensure = async () => {
|
|
2196
2677
|
if (client) return;
|
|
@@ -2206,6 +2687,8 @@ function createPostgresDriver(url, pool) {
|
|
|
2206
2687
|
if (pool?.connectTimeoutMs !== void 0) {
|
|
2207
2688
|
opts.connect_timeout = Math.ceil(pool.connectTimeoutMs / 1e3);
|
|
2208
2689
|
}
|
|
2690
|
+
opts.onnotice = (notice) => emitNotice(options?.onNotice, notice);
|
|
2691
|
+
Object.assign(opts, options?.driverOptions ?? {});
|
|
2209
2692
|
client = (mod.default ?? mod)(url, opts);
|
|
2210
2693
|
};
|
|
2211
2694
|
return {
|
|
@@ -2558,8 +3041,8 @@ function columnNamesOf(model) {
|
|
|
2558
3041
|
const map = {};
|
|
2559
3042
|
const seen = /* @__PURE__ */ new Map();
|
|
2560
3043
|
let renamed = false;
|
|
2561
|
-
for (const [prop,
|
|
2562
|
-
const dbName =
|
|
3044
|
+
for (const [prop, col2] of Object.entries(columnsOf(model))) {
|
|
3045
|
+
const dbName = col2.dbName ?? (strategy === "snake_case" ? toSnakeCase(prop) : prop);
|
|
2563
3046
|
const collision = seen.get(dbName);
|
|
2564
3047
|
if (collision !== void 0) {
|
|
2565
3048
|
throw new Error(
|
|
@@ -2600,6 +3083,7 @@ exports.BaseDialect = BaseDialect;
|
|
|
2600
3083
|
exports.BaseRepository = BaseRepository;
|
|
2601
3084
|
exports.Column = Column;
|
|
2602
3085
|
exports.DeleteBuilder = DeleteBuilder;
|
|
3086
|
+
exports.Expression = Expression;
|
|
2603
3087
|
exports.InsertBuilder = InsertBuilder;
|
|
2604
3088
|
exports.InvalidDatabaseUrl = InvalidDatabaseUrl;
|
|
2605
3089
|
exports.JoinBuilder = JoinBuilder;
|
|
@@ -2623,6 +3107,7 @@ exports.activeRecord = activeRecord;
|
|
|
2623
3107
|
exports.and = and;
|
|
2624
3108
|
exports.avg = avg;
|
|
2625
3109
|
exports.belongsTo = belongsTo;
|
|
3110
|
+
exports.col = col;
|
|
2626
3111
|
exports.column = column;
|
|
2627
3112
|
exports.columnNamesOf = columnNamesOf;
|
|
2628
3113
|
exports.columnPropsOf = columnPropsOf;
|
|
@@ -2633,13 +3118,16 @@ exports.createSyncEngine = createSyncEngine;
|
|
|
2633
3118
|
exports.dbColumn = dbColumn;
|
|
2634
3119
|
exports.del = del;
|
|
2635
3120
|
exports.detectDialect = detectDialect;
|
|
3121
|
+
exports.fn = fn;
|
|
2636
3122
|
exports.foreignKey = foreignKey;
|
|
2637
3123
|
exports.fromDict = fromDict;
|
|
2638
3124
|
exports.getDialect = getDialect;
|
|
2639
3125
|
exports.hasMany = hasMany;
|
|
2640
3126
|
exports.insert = insert;
|
|
2641
3127
|
exports.isCondition = isCondition;
|
|
3128
|
+
exports.isExpression = isExpression;
|
|
2642
3129
|
exports.isSqlExpression = isSqlExpression;
|
|
3130
|
+
exports.isSubquery = isSubquery;
|
|
2643
3131
|
exports.join = join;
|
|
2644
3132
|
exports.loadRelations = loadRelations;
|
|
2645
3133
|
exports.max = max;
|
|
@@ -2652,11 +3140,13 @@ exports.select = select;
|
|
|
2652
3140
|
exports.sql = sql;
|
|
2653
3141
|
exports.stringify = stringify;
|
|
2654
3142
|
exports.sum = sum;
|
|
3143
|
+
exports.toAsyncDriver = toAsyncDriver;
|
|
2655
3144
|
exports.toCondNode = toCondNode;
|
|
2656
3145
|
exports.toDict = toDict;
|
|
2657
3146
|
exports.toJSON = toJSON;
|
|
2658
3147
|
exports.toSnakeCase = toSnakeCase;
|
|
2659
3148
|
exports.unique = unique;
|
|
2660
3149
|
exports.update = update;
|
|
3150
|
+
exports.val = val;
|
|
2661
3151
|
//# sourceMappingURL=index.cjs.map
|
|
2662
3152
|
//# sourceMappingURL=index.cjs.map
|