kern-orm 0.2.0 → 0.4.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/dist/{expression-tYmWRuKR.d.mts → index--0tyG7sU.d.mts} +637 -174
- package/dist/index.d.mts +2 -3
- package/dist/index.mjs +1487 -875
- package/dist/testing.d.mts +1 -1
- package/dist/{text-4Xs09pIf.mjs → text-BTFErHIi.mjs} +87 -13
- package/dist/types.d.mts +47 -73
- package/dist/types.mjs +2 -8
- package/package.json +5 -1
- package/dist/index-pj2AhTRY.d.mts +0 -134
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as trueNode, C as toNode, D as isType, E as defineType, F as createNode, I as createSelectNode, L as falseNode, M as valuesQueryNode, N as defineGenericOperator, O as Column, P as createList, R as noFilters, S as toFunctionOperandNode, T as getBooleanType, _ as isOperandSource, a as stringifyJson, b as toCastValueNode, c as textOperators, d as orderingOperators, f as compare, g as isExpression, h as getType, j as isEqualityCompatible, k as equalityOperators, m as createIsNullNode, n as text, p as compareNodes, s as encodeBytea, v as operandNode, w as toValueNode, x as toExpression, y as toBooleanExpression, z as oneNode } from "./text-BTFErHIi.mjs";
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
3
|
// src/ast/boolean.ts
|
|
4
4
|
/** Combines predicate nodes with `AND` or `OR`, flattening nested groups of the same kind. */
|
|
@@ -33,13 +33,21 @@ function combineFilters(filters) {
|
|
|
33
33
|
return combineNodes("and", operands);
|
|
34
34
|
}
|
|
35
35
|
// src/expression/predicates.ts
|
|
36
|
-
/** Converts a condition operand into a node
|
|
36
|
+
/** Converts a condition operand into a node: a query nests in it, and an object of column values stays unresolved until `where`; it rejects any other value. */
|
|
37
37
|
function toConditionNode(operand) {
|
|
38
|
-
|
|
38
|
+
if (isOperandSource(operand)) return operand[operandNode]();
|
|
39
|
+
if (!isColumnValues(operand)) throw new TypeError("A condition must be an expression, such as TRUE or FALSE, a query of one Boolean value, or an object of column values");
|
|
40
|
+
return createNode({
|
|
39
41
|
kind: "values",
|
|
40
42
|
values: operand
|
|
41
43
|
});
|
|
42
44
|
}
|
|
45
|
+
/** Tells whether `value` can be an object of column values: a plain object, of this or another realm, or an object that has keys. */
|
|
46
|
+
function isColumnValues(value) {
|
|
47
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
48
|
+
const prototype = Object.getPrototypeOf(value);
|
|
49
|
+
return prototype === null || Object.getPrototypeOf(prototype) === null || Object.keys(value).length > 0;
|
|
50
|
+
}
|
|
43
51
|
function and(...operands) {
|
|
44
52
|
return combineConditions("and", operands);
|
|
45
53
|
}
|
|
@@ -54,6 +62,244 @@ function combineConditions(kind, operands) {
|
|
|
54
62
|
for (const operand of operands) nodes.push(toConditionNode(operand));
|
|
55
63
|
return toBooleanExpression(combineNodes(kind, nodes));
|
|
56
64
|
}
|
|
65
|
+
// src/expression/constants.ts
|
|
66
|
+
/** Condition that always holds, such as for a branch of `and` built in code: `and(check, isAdmin ? TRUE : other)`. */
|
|
67
|
+
const TRUE = /* @__PURE__ */ toExpression(trueNode, /* @__PURE__ */ getBooleanType());
|
|
68
|
+
/** Condition that never holds, such as for a branch of `or` built in code: `or(check, isAdmin ? other : FALSE)`. */
|
|
69
|
+
const FALSE = /* @__PURE__ */ toExpression(falseNode, /* @__PURE__ */ getBooleanType());
|
|
70
|
+
// src/query/literal.ts
|
|
71
|
+
/**
|
|
72
|
+
* Field of a selection whose value is `T`, made by `literal`.
|
|
73
|
+
*
|
|
74
|
+
* It is not an expression: it has no SQL, so only a selection takes it.
|
|
75
|
+
*/
|
|
76
|
+
var Literal = class {
|
|
77
|
+
literal;
|
|
78
|
+
constructor(literal) {
|
|
79
|
+
this.literal = literal;
|
|
80
|
+
Object.freeze(this);
|
|
81
|
+
}
|
|
82
|
+
/** Rejects the literal as an operand of an expression: a statement has no SQL of it. */
|
|
83
|
+
[operandNode]() {
|
|
84
|
+
throw new TypeError("A literal is only a selected field and has no SQL; pass a value to an expression through param");
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Creates a field of a selection with `value` in every record, without selecting anything in SQL: the query returns the value itself, not a copy of it, whatever it is.
|
|
89
|
+
*
|
|
90
|
+
* It lets a branch of a selection skip a column or a nested query, such as `withHint ? field.hint : literal(null)`.
|
|
91
|
+
* To use a value in SQL, such as in a condition, pass it through `param` instead.
|
|
92
|
+
*/
|
|
93
|
+
function literal(value) {
|
|
94
|
+
return new Literal(value);
|
|
95
|
+
}
|
|
96
|
+
// src/query/exists.ts
|
|
97
|
+
let existsProjection;
|
|
98
|
+
/** Returns the projection of whether a query has a row, the only projection of a query made by `exists`. */
|
|
99
|
+
function getExistsProjection() {
|
|
100
|
+
existsProjection ??= Object.freeze({
|
|
101
|
+
key: "exists",
|
|
102
|
+
expression: trueNode,
|
|
103
|
+
type: getBooleanType()
|
|
104
|
+
});
|
|
105
|
+
return existsProjection;
|
|
106
|
+
}
|
|
107
|
+
/** Tells whether `node` is a query of `exists`, whose value is whether the rows that its filters, limit, and offset choose exist. */
|
|
108
|
+
function isExistsQuery(node) {
|
|
109
|
+
return node.value && isExistsProjection(node.projections[0]);
|
|
110
|
+
}
|
|
111
|
+
/** Tells whether a selected field is the projection of `exists`, which only `exists` selects. */
|
|
112
|
+
function isExistsProjection(field) {
|
|
113
|
+
return field === existsProjection;
|
|
114
|
+
}
|
|
115
|
+
// src/query/aggregate.ts
|
|
116
|
+
/** Type of a count: a `bigint` of PostgreSQL decoded as a number, rejecting a count that a number cannot hold exactly. */
|
|
117
|
+
const countType = /* @__PURE__ */ defineType({ sqlType: "bigint" }).transform({
|
|
118
|
+
decode: (text) => decodeSafeInteger(text, "Count"),
|
|
119
|
+
encode: (value) => String(value)
|
|
120
|
+
}).withOperators(orderingOperators);
|
|
121
|
+
/** Types of the sums of the values of `smallint` and `integer` types, by the type of the values, created once for each. */
|
|
122
|
+
const sumTypes = /* @__PURE__ */ new WeakMap();
|
|
123
|
+
const noFallbacks = /* @__PURE__ */ Object.freeze([]);
|
|
124
|
+
/** Definitions of the aggregate methods that take a column or an expression. */
|
|
125
|
+
const aggregateDefinitions = /* @__PURE__ */ Object.freeze({
|
|
126
|
+
count: {
|
|
127
|
+
name: "count",
|
|
128
|
+
distinct: false,
|
|
129
|
+
ordered: false,
|
|
130
|
+
type: toCountType
|
|
131
|
+
},
|
|
132
|
+
countDistinct: {
|
|
133
|
+
name: "count",
|
|
134
|
+
distinct: true,
|
|
135
|
+
ordered: false,
|
|
136
|
+
type: toCountType
|
|
137
|
+
},
|
|
138
|
+
min: {
|
|
139
|
+
name: "min",
|
|
140
|
+
distinct: false,
|
|
141
|
+
ordered: false,
|
|
142
|
+
type: toArgumentType
|
|
143
|
+
},
|
|
144
|
+
max: {
|
|
145
|
+
name: "max",
|
|
146
|
+
distinct: false,
|
|
147
|
+
ordered: false,
|
|
148
|
+
type: toArgumentType
|
|
149
|
+
},
|
|
150
|
+
sum: {
|
|
151
|
+
name: "sum",
|
|
152
|
+
distinct: false,
|
|
153
|
+
ordered: false,
|
|
154
|
+
type: toSumType
|
|
155
|
+
},
|
|
156
|
+
avg: {
|
|
157
|
+
name: "avg",
|
|
158
|
+
distinct: false,
|
|
159
|
+
ordered: false,
|
|
160
|
+
type: toAverageType
|
|
161
|
+
},
|
|
162
|
+
every: {
|
|
163
|
+
name: "bool_and",
|
|
164
|
+
distinct: false,
|
|
165
|
+
ordered: false,
|
|
166
|
+
type: toArgumentType
|
|
167
|
+
},
|
|
168
|
+
some: {
|
|
169
|
+
name: "bool_or",
|
|
170
|
+
distinct: false,
|
|
171
|
+
ordered: false,
|
|
172
|
+
type: toArgumentType
|
|
173
|
+
},
|
|
174
|
+
stringAgg: {
|
|
175
|
+
name: "string_agg",
|
|
176
|
+
distinct: false,
|
|
177
|
+
ordered: true,
|
|
178
|
+
type: toArgumentType
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
/** Projection of `count(*)`, which `count` without an argument selects; its value does not depend on the values or the order of the rows. */
|
|
182
|
+
const countProjection = /* @__PURE__ */ Object.freeze({
|
|
183
|
+
key: "count",
|
|
184
|
+
expression: /* @__PURE__ */ createNode({
|
|
185
|
+
kind: "aggregate",
|
|
186
|
+
name: "count",
|
|
187
|
+
distinct: false,
|
|
188
|
+
arguments: [/* @__PURE__ */ createNode({
|
|
189
|
+
kind: "raw",
|
|
190
|
+
fragments: ["*"],
|
|
191
|
+
values: []
|
|
192
|
+
})],
|
|
193
|
+
orderBy: []
|
|
194
|
+
}),
|
|
195
|
+
type: countType,
|
|
196
|
+
ordered: false,
|
|
197
|
+
fallbacks: noFallbacks
|
|
198
|
+
});
|
|
199
|
+
/**
|
|
200
|
+
* Creates the projection of the aggregate of `method` over the values of `argument`, the projection of a column or an expression of each row.
|
|
201
|
+
*
|
|
202
|
+
* `delimiter` is the delimiter of `stringAgg`, sent as a parameter.
|
|
203
|
+
*/
|
|
204
|
+
function projectAggregate(method, argument, delimiter) {
|
|
205
|
+
const definition = aggregateDefinitions[method];
|
|
206
|
+
const args = [argument.expression];
|
|
207
|
+
if (delimiter !== void 0) args.push(createNode({
|
|
208
|
+
kind: "param",
|
|
209
|
+
value: delimiter,
|
|
210
|
+
type: void 0
|
|
211
|
+
}));
|
|
212
|
+
return Object.freeze({
|
|
213
|
+
key: method,
|
|
214
|
+
expression: createNode({
|
|
215
|
+
kind: "aggregate",
|
|
216
|
+
name: definition.name,
|
|
217
|
+
distinct: definition.distinct,
|
|
218
|
+
arguments: args,
|
|
219
|
+
orderBy: []
|
|
220
|
+
}),
|
|
221
|
+
type: definition.type(argument.type),
|
|
222
|
+
ordered: definition.ordered,
|
|
223
|
+
fallbacks: noFallbacks
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/** Creates the projection of the aggregate of `field` whose `NULL` `fallback` replaces after its earlier fallbacks. */
|
|
227
|
+
function addFallback(field, fallback) {
|
|
228
|
+
const fallbacks = field.fallbacks.slice();
|
|
229
|
+
fallbacks.push(fallback);
|
|
230
|
+
return Object.freeze({
|
|
231
|
+
key: field.key,
|
|
232
|
+
expression: field.expression,
|
|
233
|
+
type: field.type,
|
|
234
|
+
ordered: field.ordered,
|
|
235
|
+
fallbacks
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** Tells whether `node` is an aggregate query, whose value is the aggregate over the rows that its filters, limit, and offset choose. */
|
|
239
|
+
function isAggregateQuery(node) {
|
|
240
|
+
return node.value && isAggregateProjection(node.projections[0]);
|
|
241
|
+
}
|
|
242
|
+
/** Tells whether `node`, a nested query, is selected as its value rather than as packed rows: an aggregate query as a scalar subquery of its statement, and a query of `exists` as `EXISTS` of its rows. */
|
|
243
|
+
function isSelectedAsValue(node) {
|
|
244
|
+
return isAggregateQuery(node) || isExistsQuery(node);
|
|
245
|
+
}
|
|
246
|
+
/** Tells whether a selected field is the projection of an aggregate, which only the aggregate methods select. */
|
|
247
|
+
function isAggregateProjection(field) {
|
|
248
|
+
return "ordered" in field;
|
|
249
|
+
}
|
|
250
|
+
function toCountType() {
|
|
251
|
+
return countType;
|
|
252
|
+
}
|
|
253
|
+
function toArgumentType(argument) {
|
|
254
|
+
return argument;
|
|
255
|
+
}
|
|
256
|
+
/** Returns the type of the sum of the values of type `argument`: for `smallint` and `integer` values, a `bigint` with the operators of `argument` that rejects a sum that a number cannot hold exactly, and `argument` for others. */
|
|
257
|
+
function toSumType(argument) {
|
|
258
|
+
if (argument.castType !== "smallint" && argument.castType !== "integer") return argument;
|
|
259
|
+
let type = sumTypes.get(argument);
|
|
260
|
+
if (type === void 0) {
|
|
261
|
+
type = defineType({ sqlType: "bigint" }).transform({
|
|
262
|
+
decode: (text) => {
|
|
263
|
+
decodeSafeInteger(text, "Sum");
|
|
264
|
+
return argument.decode(text);
|
|
265
|
+
},
|
|
266
|
+
encode: (value) => argument.encode(value)
|
|
267
|
+
}).withOperators(argument.operators);
|
|
268
|
+
sumTypes.set(argument, type);
|
|
269
|
+
}
|
|
270
|
+
return type;
|
|
271
|
+
}
|
|
272
|
+
/** Returns the type of the average of the values of type `argument`, which decodes a fractional value where PostgreSQL gives one, such as `Number` of `integer`; rejects a type of the SQL type `bigint`. */
|
|
273
|
+
function toAverageType(argument) {
|
|
274
|
+
if (argument.castType === "bigint") throw new TypeError("avg of bigint values requires a type that can represent fractional values; average a sql fragment of the column with t.doublePrecision or t.numeric");
|
|
275
|
+
return argument;
|
|
276
|
+
}
|
|
277
|
+
/** Decodes `text` of a `bigint` as a number, rejecting one that a number cannot hold exactly; `subject` names it in the error. */
|
|
278
|
+
function decodeSafeInteger(text, subject) {
|
|
279
|
+
const number = Number(text);
|
|
280
|
+
if (!Number.isSafeInteger(number)) throw new RangeError(`${subject} ${text} is outside the safe integer range of numbers`);
|
|
281
|
+
return number;
|
|
282
|
+
}
|
|
283
|
+
// src/query/column-refs.ts
|
|
284
|
+
/** Context of a query whose table has no relations, such as one of `from(table)`. */
|
|
285
|
+
const noContext = /* @__PURE__ */ Object.freeze({ relations: /* @__PURE__ */ Object.freeze({}) });
|
|
286
|
+
/** Creates expressions of the columns of `source`, each with the type of its column. */
|
|
287
|
+
function createColumnRefs(source) {
|
|
288
|
+
return addColumns({}, source);
|
|
289
|
+
}
|
|
290
|
+
/** Creates the references of the callbacks of `select`, `where`, and `orderBy` over `source`: its columns, and the relations of `context`. */
|
|
291
|
+
function createSelectionRefs(source, context) {
|
|
292
|
+
return Object.defineProperties(addColumns({}, source), context.relations);
|
|
293
|
+
}
|
|
294
|
+
function addColumns(refs, source) {
|
|
295
|
+
const { columns } = source.table;
|
|
296
|
+
for (const key of Object.keys(columns)) refs[key] = toExpression(createNode({
|
|
297
|
+
kind: "column",
|
|
298
|
+
source,
|
|
299
|
+
key
|
|
300
|
+
}), columns[key].type);
|
|
301
|
+
return refs;
|
|
302
|
+
}
|
|
57
303
|
// src/result/result.ts
|
|
58
304
|
/** Error thrown when a query of one row finds no row, executed or nested, unless a required relation guarantees the row. */
|
|
59
305
|
var NotFoundError = class extends Error {
|
|
@@ -110,9 +356,9 @@ function decodeStatement(plan, result) {
|
|
|
110
356
|
}
|
|
111
357
|
}
|
|
112
358
|
if (counted === "number") return count;
|
|
113
|
-
if (plan.rows !== "many") return
|
|
359
|
+
if (plan.rows !== "many") return createRecord(plan);
|
|
114
360
|
const records = [];
|
|
115
|
-
for (let index = 0; index < count; index++) records.push(
|
|
361
|
+
for (let index = 0; index < count; index++) records.push(createRecord(plan));
|
|
116
362
|
return records;
|
|
117
363
|
}
|
|
118
364
|
/**
|
|
@@ -231,7 +477,7 @@ function decodeRow(plan, row, packed) {
|
|
|
231
477
|
let index = 0;
|
|
232
478
|
try {
|
|
233
479
|
if (plan.value) return packed ? decodePackedValue(fields[0], row[0]) : decodeDriverValue(fields[0], row[0]);
|
|
234
|
-
const record =
|
|
480
|
+
const record = createRecord(plan);
|
|
235
481
|
for (; index < fields.length; index++) {
|
|
236
482
|
const field = fields[index];
|
|
237
483
|
record[field.key] = packed ? decodePackedValue(field, row[index]) : decodeDriverValue(field, row[index]);
|
|
@@ -242,6 +488,16 @@ function decodeRow(plan, row, packed) {
|
|
|
242
488
|
throw prefixPath(packed ? `.${key}` : key, error);
|
|
243
489
|
}
|
|
244
490
|
}
|
|
491
|
+
/** Creates a record of `plan` before the values of a row are assigned: empty, or with the fields of its template. */
|
|
492
|
+
function createRecord(plan) {
|
|
493
|
+
const record = {};
|
|
494
|
+
const { recordTemplate } = plan;
|
|
495
|
+
if (recordTemplate !== void 0) for (let index = 0; index < recordTemplate.length; index++) {
|
|
496
|
+
const { key, value } = recordTemplate[index];
|
|
497
|
+
record[key] = value;
|
|
498
|
+
}
|
|
499
|
+
return record;
|
|
500
|
+
}
|
|
245
501
|
/** Decodes the value of a field in a row of the driver: a scalar from its text, keeping `NULL` as `null`, or nested rows from JSON text. */
|
|
246
502
|
function decodeDriverValue(field, value) {
|
|
247
503
|
if (value === void 0) throw new Error("Driver returned no value");
|
|
@@ -386,63 +642,167 @@ function toColumn(definition) {
|
|
|
386
642
|
hasDefault: false
|
|
387
643
|
}) : void 0;
|
|
388
644
|
}
|
|
389
|
-
// src/
|
|
390
|
-
/** Encodes a parameter without a type, such as a value interpolated into `sql`, by its JavaScript type. */
|
|
391
|
-
function encodeUntyped(value) {
|
|
392
|
-
if (typeof value === "string") return value;
|
|
393
|
-
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
394
|
-
if (typeof value === "boolean") return value ? "t" : "f";
|
|
395
|
-
if (value instanceof Date) return value.toISOString();
|
|
396
|
-
if (value instanceof Uint8Array) return encodeBytea(value);
|
|
397
|
-
if (Array.isArray(value)) throw new TypeError("Pass an array with param(type, value), so that its elements are encoded by type");
|
|
398
|
-
return stringifyJson(value);
|
|
399
|
-
}
|
|
400
|
-
// src/compiler/postgres/identifier.ts
|
|
401
|
-
/** Most bytes a character other than ASCII takes in a server encoding, such as UTF-8 and EUC_TW ([PostgreSQL](https://www.postgresql.org/docs/current/multibyte.html)). */
|
|
402
|
-
const maxCharacterBytes = 4;
|
|
403
|
-
function quoteIdentifier(name) {
|
|
404
|
-
return `"${name.replaceAll("\"", "\"\"")}"`;
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Returns the longest prefix of `name` that takes at most `maxBytes` bytes in any server encoding.
|
|
408
|
-
*
|
|
409
|
-
* Every server encoding takes one byte for an ASCII character, and a character other than ASCII counts as the most bytes it can take.
|
|
410
|
-
*/
|
|
411
|
-
function cutIdentifier(name, maxBytes) {
|
|
412
|
-
if (name.length * maxCharacterBytes <= maxBytes) return name;
|
|
413
|
-
let bytes = 0;
|
|
414
|
-
for (let index = 0; index < name.length;) {
|
|
415
|
-
const code = name.codePointAt(index);
|
|
416
|
-
bytes += code < 128 ? 1 : maxCharacterBytes;
|
|
417
|
-
if (bytes > maxBytes) return name.slice(0, index);
|
|
418
|
-
index += code < 65536 ? 1 : 2;
|
|
419
|
-
}
|
|
420
|
-
return name;
|
|
421
|
-
}
|
|
645
|
+
// src/query/resolve-values.ts
|
|
422
646
|
/**
|
|
423
|
-
*
|
|
647
|
+
* Replaces objects of column values in a predicate with conditions on the columns of `source`.
|
|
424
648
|
*
|
|
425
|
-
*
|
|
426
|
-
* A longer key is named by its prefix of at most 54 bytes, which takes more than 50 since a character takes at most 4, and by its position, so the name differs from the names of the other projections.
|
|
649
|
+
* Groups are rebuilt, so constants that appear after resolution, such as an empty object, are simplified.
|
|
427
650
|
*/
|
|
428
|
-
function
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
651
|
+
function resolveValues(node, source) {
|
|
652
|
+
switch (node.kind) {
|
|
653
|
+
case "values": {
|
|
654
|
+
const conditions = [];
|
|
655
|
+
for (const key of Object.keys(node.values)) {
|
|
656
|
+
const value = node.values[key];
|
|
657
|
+
const type = findColumn(source.table, key)?.type;
|
|
658
|
+
const column = createNode({
|
|
659
|
+
kind: "column",
|
|
660
|
+
source,
|
|
661
|
+
key
|
|
662
|
+
});
|
|
663
|
+
if (value === null) conditions.push(createIsNullNode(column));
|
|
664
|
+
else if (type) conditions.push(matchValue(source, column, type, value));
|
|
665
|
+
else conditions.push(compareNodes("=", column, value, void 0));
|
|
666
|
+
}
|
|
667
|
+
return combineNodes("and", conditions);
|
|
668
|
+
}
|
|
669
|
+
case "and":
|
|
670
|
+
case "or": {
|
|
671
|
+
const operands = [];
|
|
672
|
+
for (const operand of node.operands) operands.push(resolveValues(operand, source));
|
|
673
|
+
return combineNodes(node.kind, operands);
|
|
674
|
+
}
|
|
675
|
+
case "not": return negateNode(resolveValues(node.operand, source));
|
|
676
|
+
case "raw": return createNode({
|
|
677
|
+
...node,
|
|
678
|
+
values: node.values.map((value) => resolveValues(value, source))
|
|
679
|
+
});
|
|
680
|
+
default: return node;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/** Matches a column with a value of an object condition by calling the `eq` operator of its type, whichever operator it is. */
|
|
684
|
+
function matchValue(source, column, type, value) {
|
|
685
|
+
const expression = toExpression(column, type);
|
|
686
|
+
if (typeof expression.eq !== "function") throw new TypeError(`Column "${column.key}" of table "${source.table.name}" has no eq operator; an object condition can only match it with null`);
|
|
687
|
+
const predicate = expression.eq(value);
|
|
688
|
+
if (!isExpression(predicate)) throw new TypeError(`The eq operator of column "${column.key}" of table "${source.table.name}" must return an expression`);
|
|
689
|
+
return toNode(predicate);
|
|
690
|
+
}
|
|
691
|
+
// src/query/filters.ts
|
|
692
|
+
/** Creates query filters with the filter of `where` over `source` with the relations of `context` combined with `AND` with the user filters of `filters`. */
|
|
693
|
+
function addUserFilter(filters, filter, source, context) {
|
|
694
|
+
const predicate = resolveValues(toConditionNode(typeof filter === "function" ? filter(createSelectionRefs(source, context)) : filter), source);
|
|
695
|
+
const { user } = filters;
|
|
696
|
+
return Object.freeze({
|
|
697
|
+
...filters,
|
|
698
|
+
user: user ? combineNodes("and", [user, predicate]) : predicate
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
function defineFilter(_table, filter) {
|
|
702
|
+
return filter;
|
|
703
|
+
}
|
|
704
|
+
// src/query/find.ts
|
|
705
|
+
/**
|
|
706
|
+
* Returns the object of column values that `find` filters a query of `table` by: `values` of the columns of its primary key, in their order.
|
|
707
|
+
*
|
|
708
|
+
* It rejects a table without a primary key, a number of values other than the number of its columns, and `null` and `undefined`.
|
|
709
|
+
*/
|
|
710
|
+
function toPrimaryKeyValues(table, values) {
|
|
711
|
+
const { primaryKey } = table.keys;
|
|
712
|
+
if (primaryKey === void 0) throw new Error(`find takes the values of a primary key, which table "${table.name}" does not have; use findBy with a unique key`);
|
|
713
|
+
if (values.length !== primaryKey.length) throw new Error(`find takes one value for each column of the primary key of table "${table.name}", ${quoteColumns(primaryKey)}, not ${values.length}`);
|
|
714
|
+
const filter = {};
|
|
715
|
+
for (let index = 0; index < primaryKey.length; index++) {
|
|
716
|
+
const column = primaryKey[index];
|
|
717
|
+
filter[column] = requireKeyValue(table, column, values[index], "find");
|
|
718
|
+
}
|
|
719
|
+
return filter;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Returns the object of column values that `findBy` filters a query of `table` by: a copy of `values`, read once.
|
|
723
|
+
*
|
|
724
|
+
* It rejects columns that are not exactly a key of `table`, primary or unique, as `isKey` tells, and `null` and `undefined`.
|
|
725
|
+
*/
|
|
726
|
+
function toKeyValues(table, values) {
|
|
727
|
+
if (typeof values !== "object" || values === null) throw new TypeError(`findBy takes an object of the columns of a key of table "${table.name}"`);
|
|
728
|
+
const columns = Object.keys(values);
|
|
729
|
+
if (!isKey(table, columns)) {
|
|
730
|
+
const listed = columns.length === 0 ? "an object without columns" : `columns ${quoteColumns(columns)}`;
|
|
731
|
+
throw new Error(`findBy takes the columns of one key of table "${table.name}", primary or unique, not ${listed}`);
|
|
732
|
+
}
|
|
733
|
+
const filter = {};
|
|
734
|
+
for (const column of columns) {
|
|
735
|
+
const value = values[column];
|
|
736
|
+
filter[column] = requireKeyValue(table, column, value, "findBy");
|
|
737
|
+
}
|
|
738
|
+
return filter;
|
|
739
|
+
}
|
|
740
|
+
/** Returns `value` of column `column` of a key of `table` that `method` takes, rejecting `null` and `undefined`. */
|
|
741
|
+
function requireKeyValue(table, column, value, method) {
|
|
742
|
+
if (value === null || value === void 0) throw new TypeError(`${method} got ${value} for column "${column}" of table "${table.name}"; a value of a key is never null or undefined`);
|
|
743
|
+
return value;
|
|
744
|
+
}
|
|
745
|
+
/** Lists `columns` in quotes, separated by commas. */
|
|
746
|
+
function quoteColumns(columns) {
|
|
747
|
+
return columns.map((column) => `"${column}"`).join(", ");
|
|
748
|
+
}
|
|
749
|
+
// src/type/untyped.ts
|
|
750
|
+
/** Encodes a parameter without a type, such as a value interpolated into `sql`, by its JavaScript type. */
|
|
751
|
+
function encodeUntyped(value) {
|
|
752
|
+
if (typeof value === "string") return value;
|
|
753
|
+
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
754
|
+
if (typeof value === "boolean") return value ? "t" : "f";
|
|
755
|
+
if (value instanceof Date) return value.toISOString();
|
|
756
|
+
if (value instanceof Uint8Array) return encodeBytea(value);
|
|
757
|
+
if (Array.isArray(value)) throw new TypeError("Pass an array with param(type, value), so that its elements are encoded by type");
|
|
758
|
+
return stringifyJson(value);
|
|
759
|
+
}
|
|
760
|
+
// src/compiler/postgres/identifier.ts
|
|
761
|
+
/** Most bytes a character other than ASCII takes in a server encoding, such as UTF-8 and EUC_TW ([PostgreSQL](https://www.postgresql.org/docs/current/multibyte.html)). */
|
|
762
|
+
const maxCharacterBytes = 4;
|
|
763
|
+
function quoteIdentifier(name) {
|
|
764
|
+
return `"${name.replaceAll("\"", "\"\"")}"`;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Returns the longest prefix of `name` that takes at most `maxBytes` bytes in any server encoding.
|
|
768
|
+
*
|
|
769
|
+
* Every server encoding takes one byte for an ASCII character, and a character other than ASCII counts as the most bytes it can take.
|
|
770
|
+
*/
|
|
771
|
+
function cutIdentifier(name, maxBytes) {
|
|
772
|
+
if (name.length * maxCharacterBytes <= maxBytes) return name;
|
|
773
|
+
let bytes = 0;
|
|
774
|
+
for (let index = 0; index < name.length;) {
|
|
775
|
+
const code = name.codePointAt(index);
|
|
776
|
+
bytes += code < 128 ? 1 : maxCharacterBytes;
|
|
777
|
+
if (bytes > maxBytes) return name.slice(0, index);
|
|
778
|
+
index += code < 65536 ? 1 : 2;
|
|
779
|
+
}
|
|
780
|
+
return name;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Returns the SQL name of the projection with `key` at `index` of its query.
|
|
784
|
+
*
|
|
785
|
+
* A key of up to 50 bytes is the name itself.
|
|
786
|
+
* A longer key is named by its prefix of at most 54 bytes, which takes more than 50 since a character takes at most 4, and by its position, so the name differs from the names of the other projections.
|
|
787
|
+
*/
|
|
788
|
+
function nameProjection(key, index) {
|
|
789
|
+
if (cutIdentifier(key, 50).length === key.length) return key;
|
|
790
|
+
return `${cutIdentifier(key, 54)}_${index + 1}`;
|
|
791
|
+
}
|
|
792
|
+
// src/compiler/postgres/context.ts
|
|
793
|
+
/** Most parameters that PostgreSQL takes in a statement. */
|
|
794
|
+
const maxParameters = 65535;
|
|
795
|
+
/** State of one compilation shared by all queries of the statement, never by reusable nodes: parameters and the aliases of all sources. */
|
|
796
|
+
var CompileContext = class {
|
|
797
|
+
schemaName;
|
|
798
|
+
tables;
|
|
799
|
+
db;
|
|
800
|
+
parameters = [];
|
|
801
|
+
#aliases = /* @__PURE__ */ new Set();
|
|
802
|
+
constructor(schemaName, tables = void 0, db = void 0) {
|
|
803
|
+
this.schemaName = schemaName;
|
|
804
|
+
this.tables = tables;
|
|
805
|
+
this.db = db;
|
|
446
806
|
}
|
|
447
807
|
/**
|
|
448
808
|
* Creates an alias based on `name` that is unique in the statement, so that no source shadows another one in SQL.
|
|
@@ -471,9 +831,11 @@ function needsParentheses(kind, position) {
|
|
|
471
831
|
case "param":
|
|
472
832
|
case "boolean":
|
|
473
833
|
case "values":
|
|
834
|
+
case "nestedQuery":
|
|
474
835
|
case "tableName":
|
|
475
836
|
case "subquery":
|
|
476
837
|
case "exists":
|
|
838
|
+
case "textSubquery":
|
|
477
839
|
case "aggregate": return false;
|
|
478
840
|
case "cast": return position === "interpolation";
|
|
479
841
|
case "raw": return position !== "interpolation";
|
|
@@ -482,6 +844,7 @@ function needsParentheses(kind, position) {
|
|
|
482
844
|
case "not":
|
|
483
845
|
case "comparison":
|
|
484
846
|
case "in":
|
|
847
|
+
case "inQuery":
|
|
485
848
|
case "isNull": return position !== "condition";
|
|
486
849
|
default: return kind;
|
|
487
850
|
}
|
|
@@ -534,6 +897,7 @@ function compileStatement(node, options = {}, tables, db) {
|
|
|
534
897
|
text: compileQuery(context, void 0, node),
|
|
535
898
|
parameters: context.parameters,
|
|
536
899
|
fields: node.projections,
|
|
900
|
+
recordTemplate: node.recordTemplate,
|
|
537
901
|
rows: node.rows,
|
|
538
902
|
value: node.value,
|
|
539
903
|
counted: void 0,
|
|
@@ -557,6 +921,7 @@ function compileStatement(node, options = {}, tables, db) {
|
|
|
557
921
|
text: compileMutation(context, node),
|
|
558
922
|
parameters: context.parameters,
|
|
559
923
|
fields: returning ?? noFields,
|
|
924
|
+
recordTemplate: node.recordTemplate,
|
|
560
925
|
rows: node.rows,
|
|
561
926
|
value: node.value,
|
|
562
927
|
counted: returning === void 0 ? "number" : returning.length === 0 ? "records" : void 0,
|
|
@@ -709,12 +1074,18 @@ function compileExpression(context, scope, node) {
|
|
|
709
1074
|
case "or": return node.operands.map((operand) => compileOperand(context, scope, operand, "condition")).join(node.kind === "and" ? " AND " : " OR ");
|
|
710
1075
|
case "not": return `NOT ${compileOperand(context, scope, node.operand, "condition")}`;
|
|
711
1076
|
case "values": throw new Error("An object condition can only be used in `where`");
|
|
1077
|
+
case "nestedQuery": throw new Error("A query in an expression must be lowered before compilation");
|
|
712
1078
|
case "comparison": return `${compileOperand(context, scope, node.left, "value")} ${node.operator} ${compileOperand(context, scope, node.right, "value")}`;
|
|
713
1079
|
case "in": {
|
|
714
1080
|
let text = `${compileOperand(context, scope, node.operand, "value")} IN (${compileOperand(context, scope, node.list[0], "value")}`;
|
|
715
1081
|
for (let index = 1; index < node.list.length; index++) text += `, ${compileOperand(context, scope, node.list[index], "value")}`;
|
|
716
1082
|
return `${text})`;
|
|
717
1083
|
}
|
|
1084
|
+
case "inQuery": {
|
|
1085
|
+
const { query } = node;
|
|
1086
|
+
if (query.kind === "nestedQuery") throw new Error("A query in an expression must be lowered before compilation");
|
|
1087
|
+
return `${compileOperand(context, scope, node.operand, "value")} IN ${compileSubquery(context, scope, query, "A subquery of IN")}`;
|
|
1088
|
+
}
|
|
718
1089
|
case "isNull": return `${compileOperand(context, scope, node.operand, "value")} IS NULL`;
|
|
719
1090
|
case "raw": {
|
|
720
1091
|
let text = node.fragments[0];
|
|
@@ -722,12 +1093,9 @@ function compileExpression(context, scope, node) {
|
|
|
722
1093
|
return text;
|
|
723
1094
|
}
|
|
724
1095
|
case "tableName": return compileInterpolatedTable(context, node);
|
|
725
|
-
case "subquery":
|
|
726
|
-
const { length } = node.query.projections;
|
|
727
|
-
if (length !== 1) throw new Error(`A scalar subquery must select one field, not ${length}`);
|
|
728
|
-
return `(${compileQuery(context, scope, node.query)})`;
|
|
729
|
-
}
|
|
1096
|
+
case "subquery": return compileSubquery(context, scope, node.query, "A scalar subquery");
|
|
730
1097
|
case "exists": return `EXISTS (${compileQuery(context, scope, node.query)})`;
|
|
1098
|
+
case "textSubquery": return `(${compileQuery(context, scope, node.query)})`;
|
|
731
1099
|
case "aggregate": {
|
|
732
1100
|
let text = `${node.name}(${node.distinct ? "DISTINCT " : ""}`;
|
|
733
1101
|
for (let index = 0; index < node.arguments.length; index++) text += `${index === 0 ? "" : ", "}${compileExpression(context, scope, node.arguments[index])}`;
|
|
@@ -737,6 +1105,12 @@ function compileExpression(context, scope, node) {
|
|
|
737
1105
|
default: return node;
|
|
738
1106
|
}
|
|
739
1107
|
}
|
|
1108
|
+
/** Compiles `query` in parentheses as a subquery of one field, and rejects a query of another number of fields, naming the subquery as `subject`. */
|
|
1109
|
+
function compileSubquery(context, scope, query, subject) {
|
|
1110
|
+
const { length } = query.projections;
|
|
1111
|
+
if (length !== 1) throw new Error(`${subject} must select one field, not ${length}`);
|
|
1112
|
+
return `(${compileQuery(context, scope, query)})`;
|
|
1113
|
+
}
|
|
740
1114
|
/** Encodes the value of a parameter node with its type, or by its JavaScript type without one. */
|
|
741
1115
|
function encodeParameter(node) {
|
|
742
1116
|
if (node.value === void 0) throw new TypeError("Query parameter is undefined; use null for NULL");
|
|
@@ -787,7 +1161,8 @@ function visitNodes(node, deep, visit) {
|
|
|
787
1161
|
case "param":
|
|
788
1162
|
case "boolean":
|
|
789
1163
|
case "tableName":
|
|
790
|
-
case "values":
|
|
1164
|
+
case "values":
|
|
1165
|
+
case "nestedQuery": return;
|
|
791
1166
|
case "cast":
|
|
792
1167
|
case "not":
|
|
793
1168
|
case "isNull":
|
|
@@ -805,11 +1180,16 @@ function visitNodes(node, deep, visit) {
|
|
|
805
1180
|
visitNodes(node.operand, deep, visit);
|
|
806
1181
|
for (const item of node.list) visitNodes(item, deep, visit);
|
|
807
1182
|
return;
|
|
1183
|
+
case "inQuery":
|
|
1184
|
+
visitNodes(node.operand, deep, visit);
|
|
1185
|
+
if (deep && node.query.kind === "select") visitQuery(node.query, visit);
|
|
1186
|
+
return;
|
|
808
1187
|
case "raw":
|
|
809
1188
|
for (const value of node.values) visitNodes(value, deep, visit);
|
|
810
1189
|
return;
|
|
811
1190
|
case "subquery":
|
|
812
1191
|
case "exists":
|
|
1192
|
+
case "textSubquery":
|
|
813
1193
|
if (deep) visitQuery(node.query, visit);
|
|
814
1194
|
return;
|
|
815
1195
|
case "aggregate":
|
|
@@ -834,170 +1214,6 @@ function visitQuery(query, visit) {
|
|
|
834
1214
|
if (query.limit !== void 0) visitNodes(query.limit, true, visit);
|
|
835
1215
|
if (query.offset !== void 0) visitNodes(query.offset, true, visit);
|
|
836
1216
|
}
|
|
837
|
-
// src/query/aggregate.ts
|
|
838
|
-
/** Type of a count: a `bigint` of PostgreSQL decoded as a number, rejecting a count that a number cannot hold exactly. */
|
|
839
|
-
const countType = /* @__PURE__ */ defineType({ sqlType: "bigint" }).transform({
|
|
840
|
-
decode: (text) => decodeSafeInteger(text, "Count"),
|
|
841
|
-
encode: (value) => String(value)
|
|
842
|
-
});
|
|
843
|
-
/** Types of the sums of the values of `smallint` and `integer` types, by the type of the values, created once for each. */
|
|
844
|
-
const sumTypes = /* @__PURE__ */ new WeakMap();
|
|
845
|
-
const noFallbacks = /* @__PURE__ */ Object.freeze([]);
|
|
846
|
-
/** Definitions of the aggregate methods that take a column or an expression. */
|
|
847
|
-
const aggregateDefinitions = /* @__PURE__ */ Object.freeze({
|
|
848
|
-
count: {
|
|
849
|
-
name: "count",
|
|
850
|
-
distinct: false,
|
|
851
|
-
ordered: false,
|
|
852
|
-
type: toCountType
|
|
853
|
-
},
|
|
854
|
-
countDistinct: {
|
|
855
|
-
name: "count",
|
|
856
|
-
distinct: true,
|
|
857
|
-
ordered: false,
|
|
858
|
-
type: toCountType
|
|
859
|
-
},
|
|
860
|
-
min: {
|
|
861
|
-
name: "min",
|
|
862
|
-
distinct: false,
|
|
863
|
-
ordered: false,
|
|
864
|
-
type: toArgumentType
|
|
865
|
-
},
|
|
866
|
-
max: {
|
|
867
|
-
name: "max",
|
|
868
|
-
distinct: false,
|
|
869
|
-
ordered: false,
|
|
870
|
-
type: toArgumentType
|
|
871
|
-
},
|
|
872
|
-
sum: {
|
|
873
|
-
name: "sum",
|
|
874
|
-
distinct: false,
|
|
875
|
-
ordered: false,
|
|
876
|
-
type: toSumType
|
|
877
|
-
},
|
|
878
|
-
avg: {
|
|
879
|
-
name: "avg",
|
|
880
|
-
distinct: false,
|
|
881
|
-
ordered: false,
|
|
882
|
-
type: toAverageType
|
|
883
|
-
},
|
|
884
|
-
every: {
|
|
885
|
-
name: "bool_and",
|
|
886
|
-
distinct: false,
|
|
887
|
-
ordered: false,
|
|
888
|
-
type: toArgumentType
|
|
889
|
-
},
|
|
890
|
-
some: {
|
|
891
|
-
name: "bool_or",
|
|
892
|
-
distinct: false,
|
|
893
|
-
ordered: false,
|
|
894
|
-
type: toArgumentType
|
|
895
|
-
},
|
|
896
|
-
stringAgg: {
|
|
897
|
-
name: "string_agg",
|
|
898
|
-
distinct: false,
|
|
899
|
-
ordered: true,
|
|
900
|
-
type: toArgumentType
|
|
901
|
-
}
|
|
902
|
-
});
|
|
903
|
-
/** Projection of `count(*)`, which `count` without an argument selects; its value does not depend on the values or the order of the rows. */
|
|
904
|
-
const countProjection = /* @__PURE__ */ Object.freeze({
|
|
905
|
-
key: "count",
|
|
906
|
-
expression: /* @__PURE__ */ createNode({
|
|
907
|
-
kind: "aggregate",
|
|
908
|
-
name: "count",
|
|
909
|
-
distinct: false,
|
|
910
|
-
arguments: [/* @__PURE__ */ createNode({
|
|
911
|
-
kind: "raw",
|
|
912
|
-
fragments: ["*"],
|
|
913
|
-
values: []
|
|
914
|
-
})],
|
|
915
|
-
orderBy: []
|
|
916
|
-
}),
|
|
917
|
-
type: countType,
|
|
918
|
-
ordered: false,
|
|
919
|
-
fallbacks: noFallbacks
|
|
920
|
-
});
|
|
921
|
-
/**
|
|
922
|
-
* Creates the projection of the aggregate of `method` over the values of `argument`, the projection of a column or an expression of each row.
|
|
923
|
-
*
|
|
924
|
-
* `delimiter` is the delimiter of `stringAgg`, sent as a parameter.
|
|
925
|
-
*/
|
|
926
|
-
function projectAggregate(method, argument, delimiter) {
|
|
927
|
-
const definition = aggregateDefinitions[method];
|
|
928
|
-
const args = [argument.expression];
|
|
929
|
-
if (delimiter !== void 0) args.push(createNode({
|
|
930
|
-
kind: "param",
|
|
931
|
-
value: delimiter,
|
|
932
|
-
type: void 0
|
|
933
|
-
}));
|
|
934
|
-
return Object.freeze({
|
|
935
|
-
key: method,
|
|
936
|
-
expression: createNode({
|
|
937
|
-
kind: "aggregate",
|
|
938
|
-
name: definition.name,
|
|
939
|
-
distinct: definition.distinct,
|
|
940
|
-
arguments: args,
|
|
941
|
-
orderBy: []
|
|
942
|
-
}),
|
|
943
|
-
type: definition.type(argument.type),
|
|
944
|
-
ordered: definition.ordered,
|
|
945
|
-
fallbacks: noFallbacks
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
/** Creates the projection of the aggregate of `field` whose `NULL` `fallback` replaces after its earlier fallbacks. */
|
|
949
|
-
function addFallback(field, fallback) {
|
|
950
|
-
const fallbacks = field.fallbacks.slice();
|
|
951
|
-
fallbacks.push(fallback);
|
|
952
|
-
return Object.freeze({
|
|
953
|
-
key: field.key,
|
|
954
|
-
expression: field.expression,
|
|
955
|
-
type: field.type,
|
|
956
|
-
ordered: field.ordered,
|
|
957
|
-
fallbacks
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
/** Tells whether `node` is an aggregate query, whose value is the aggregate over the rows that its filters, limit, and offset choose. */
|
|
961
|
-
function isAggregateQuery(node) {
|
|
962
|
-
return node.value && isAggregateProjection(node.projections[0]);
|
|
963
|
-
}
|
|
964
|
-
/** Tells whether a selected field is the projection of an aggregate, which only the aggregate methods select. */
|
|
965
|
-
function isAggregateProjection(field) {
|
|
966
|
-
return "ordered" in field;
|
|
967
|
-
}
|
|
968
|
-
function toCountType() {
|
|
969
|
-
return countType;
|
|
970
|
-
}
|
|
971
|
-
function toArgumentType(argument) {
|
|
972
|
-
return argument;
|
|
973
|
-
}
|
|
974
|
-
/** Returns the type of the sum of the values of type `argument`: for `smallint` and `integer` values, `argument` that rejects a sum that a number cannot hold exactly, and `argument` for others. */
|
|
975
|
-
function toSumType(argument) {
|
|
976
|
-
if (argument.castType !== "smallint" && argument.castType !== "integer") return argument;
|
|
977
|
-
let type = sumTypes.get(argument);
|
|
978
|
-
if (type === void 0) {
|
|
979
|
-
type = defineType({ sqlType: "bigint" }).transform({
|
|
980
|
-
decode: (text) => {
|
|
981
|
-
decodeSafeInteger(text, "Sum");
|
|
982
|
-
return argument.decode(text);
|
|
983
|
-
},
|
|
984
|
-
encode: (value) => argument.encode(value)
|
|
985
|
-
});
|
|
986
|
-
sumTypes.set(argument, type);
|
|
987
|
-
}
|
|
988
|
-
return type;
|
|
989
|
-
}
|
|
990
|
-
/** Returns the type of the average of the values of type `argument`, which decodes a fractional value where PostgreSQL gives one, such as `Number` of `integer`; rejects a type of the SQL type `bigint`. */
|
|
991
|
-
function toAverageType(argument) {
|
|
992
|
-
if (argument.castType === "bigint") throw new TypeError("avg of bigint values requires a type that can represent fractional values; average a sql fragment of the column with t.doublePrecision or t.numeric");
|
|
993
|
-
return argument;
|
|
994
|
-
}
|
|
995
|
-
/** Decodes `text` of a `bigint` as a number, rejecting one that a number cannot hold exactly; `subject` names it in the error. */
|
|
996
|
-
function decodeSafeInteger(text, subject) {
|
|
997
|
-
const number = Number(text);
|
|
998
|
-
if (!Number.isSafeInteger(number)) throw new RangeError(`${subject} ${text} is outside the safe integer range of numbers`);
|
|
999
|
-
return number;
|
|
1000
|
-
}
|
|
1001
1217
|
// src/query/rows.ts
|
|
1002
1218
|
/** Returns the rows mode of a query of the records of `relation`, as `RelationRows` gives it in types. */
|
|
1003
1219
|
function getRelationRows(relation) {
|
|
@@ -1043,6 +1259,10 @@ function isTableQuery(node) {
|
|
|
1043
1259
|
function isNestedSelection(node) {
|
|
1044
1260
|
return "query" in node;
|
|
1045
1261
|
}
|
|
1262
|
+
/** Tells whether a selected field is a literal. */
|
|
1263
|
+
function isLiteralSelection(node) {
|
|
1264
|
+
return "literal" in node;
|
|
1265
|
+
}
|
|
1046
1266
|
// src/schema/relation.ts
|
|
1047
1267
|
/** Runtime mark of the declarations of `many` and `one`. */
|
|
1048
1268
|
const declarationBrand = Symbol("kern-orm.relation");
|
|
@@ -1331,7 +1551,8 @@ function packRows(query) {
|
|
|
1331
1551
|
fields: projections,
|
|
1332
1552
|
rows: query.rows,
|
|
1333
1553
|
value: query.value,
|
|
1334
|
-
guaranteedBy: query.guaranteedBy
|
|
1554
|
+
guaranteedBy: query.guaranteedBy,
|
|
1555
|
+
recordTemplate: query.recordTemplate
|
|
1335
1556
|
});
|
|
1336
1557
|
const projection = Object.freeze({
|
|
1337
1558
|
key: "rows",
|
|
@@ -1370,85 +1591,176 @@ function createRef(source, key) {
|
|
|
1370
1591
|
// src/lowering/lower.ts
|
|
1371
1592
|
const noJoins = /* @__PURE__ */ Object.freeze([]);
|
|
1372
1593
|
const noOrder = /* @__PURE__ */ Object.freeze([]);
|
|
1594
|
+
const noProjections = /* @__PURE__ */ Object.freeze([]);
|
|
1373
1595
|
function lowerStatement(node, definitions, db) {
|
|
1596
|
+
const context = {
|
|
1597
|
+
definitions,
|
|
1598
|
+
db
|
|
1599
|
+
};
|
|
1374
1600
|
switch (node.kind) {
|
|
1375
|
-
case "query": return lowerSelect(node,
|
|
1376
|
-
case "record": return lowerRecord(node,
|
|
1377
|
-
case "text": return node;
|
|
1378
|
-
default: return lowerMutation(node,
|
|
1601
|
+
case "query": return lowerSelect(node, context, true);
|
|
1602
|
+
case "record": return lowerRecord(node, context);
|
|
1603
|
+
case "text": return lowerText(node, context);
|
|
1604
|
+
default: return lowerMutation(node, context);
|
|
1379
1605
|
}
|
|
1380
1606
|
}
|
|
1381
|
-
/** Lowers a
|
|
1382
|
-
function
|
|
1383
|
-
const
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1607
|
+
/** Lowers a statement of SQL text, replacing the queries in the expressions interpolated into it. */
|
|
1608
|
+
function lowerText(node, context) {
|
|
1609
|
+
const sql = lowerExpression(node.sql, context);
|
|
1610
|
+
if (sql === node.sql) return node;
|
|
1611
|
+
return Object.freeze({
|
|
1612
|
+
kind: "text",
|
|
1613
|
+
sql,
|
|
1614
|
+
fields: node.fields,
|
|
1615
|
+
rows: node.rows,
|
|
1616
|
+
value: node.value
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
/** Lowers a mutation to its statement, replacing the queries in its expressions, and packing the rows of each nested query of `RETURNING` into the value of its field, or selecting the value of a nested aggregate query, as a select list does. */
|
|
1620
|
+
function lowerMutation(node, context) {
|
|
1621
|
+
const { target, returning, rows, value } = node;
|
|
1622
|
+
const recordTemplate = returning === void 0 ? void 0 : createRecordTemplate(returning);
|
|
1623
|
+
const lowered = returning === void 0 ? void 0 : lowerProjections(dropLiterals(returning), context);
|
|
1387
1624
|
switch (node.kind) {
|
|
1388
|
-
case "insert":
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1625
|
+
case "insert": {
|
|
1626
|
+
const values = lowerEach(node.values, context, lowerRow);
|
|
1627
|
+
if (lowered === returning && values === node.values) return node;
|
|
1628
|
+
return Object.freeze({
|
|
1629
|
+
kind: "insert",
|
|
1630
|
+
target,
|
|
1631
|
+
columns: node.columns,
|
|
1632
|
+
values,
|
|
1633
|
+
returning: lowered,
|
|
1634
|
+
recordTemplate,
|
|
1635
|
+
rows,
|
|
1636
|
+
value
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
case "update": {
|
|
1640
|
+
const set = lowerEach(node.set, context, lowerAssignment);
|
|
1641
|
+
const filters = lowerFilters(node.filters, context);
|
|
1642
|
+
if (lowered === returning && set === node.set && filters === node.filters) return node;
|
|
1643
|
+
return Object.freeze({
|
|
1644
|
+
kind: "update",
|
|
1645
|
+
target,
|
|
1646
|
+
set,
|
|
1647
|
+
filters,
|
|
1648
|
+
returning: lowered,
|
|
1649
|
+
recordTemplate,
|
|
1650
|
+
rows,
|
|
1651
|
+
value
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
case "delete": {
|
|
1655
|
+
const filters = lowerFilters(node.filters, context);
|
|
1656
|
+
if (lowered === returning && filters === node.filters) return node;
|
|
1657
|
+
return Object.freeze({
|
|
1658
|
+
kind: "delete",
|
|
1659
|
+
target,
|
|
1660
|
+
filters,
|
|
1661
|
+
returning: lowered,
|
|
1662
|
+
recordTemplate,
|
|
1663
|
+
rows,
|
|
1664
|
+
value
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1414
1667
|
default: return node;
|
|
1415
1668
|
}
|
|
1416
1669
|
}
|
|
1670
|
+
/** Lowers a row of `VALUES` of an insert, whose `undefined` stands for `DEFAULT`. */
|
|
1671
|
+
function lowerRow(row, context) {
|
|
1672
|
+
return lowerEach(row, context, lowerValue);
|
|
1673
|
+
}
|
|
1674
|
+
function lowerValue(node, context) {
|
|
1675
|
+
return node === void 0 ? void 0 : lowerExpression(node, context);
|
|
1676
|
+
}
|
|
1677
|
+
function lowerAssignment(assignment, context) {
|
|
1678
|
+
const value = lowerExpression(assignment.value, context);
|
|
1679
|
+
return value === assignment.value ? assignment : Object.freeze({
|
|
1680
|
+
key: assignment.key,
|
|
1681
|
+
value
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1417
1684
|
/** Lowers a query as `lowerQuery` does, or as `lowerStatement` does when `positions` is set. */
|
|
1418
|
-
function lowerSelect(node,
|
|
1685
|
+
function lowerSelect(node, context, positions) {
|
|
1686
|
+
if (isExistsQuery(node)) return selectExists(node, context);
|
|
1687
|
+
checkRelation(node, context);
|
|
1688
|
+
if (isAggregateQuery(node)) return lowerAggregate(node, context);
|
|
1689
|
+
const recordTemplate = createRecordTemplate(node.projections);
|
|
1690
|
+
const selected = dropLiterals(node.projections);
|
|
1691
|
+
const projections = lowerProjections(selected, context);
|
|
1692
|
+
const filters = lowerFilters(node.filters, context);
|
|
1693
|
+
const orderBy = lowerOrder(node.orderBy, context, selected, projections);
|
|
1419
1694
|
const { relation } = node;
|
|
1420
|
-
if (relation !== void 0 && definitions !== void 0 && !definitions.has(relation)) throw new Error(`Relation "${formatRelationName(relation.key, relation.name)}" is not part of the schema`);
|
|
1421
|
-
if (isAggregateQuery(node)) return lowerAggregate(node);
|
|
1422
|
-
const projections = lowerProjections(node.projections, definitions, db);
|
|
1423
1695
|
const guaranteedBy = node.guaranteed && relation !== void 0 ? formatRelationName(relation.key, relation.name) : void 0;
|
|
1424
|
-
const page = node.offset !== void 0 &&
|
|
1696
|
+
const page = node.offset !== void 0 && selected.some((field, index) => isPagedField(field, projections[index], orderBy)) ? lowerPage(node, selected, projections, filters, orderBy, positions, guaranteedBy, recordTemplate) : void 0;
|
|
1425
1697
|
if (page !== void 0) return page;
|
|
1426
1698
|
return Object.freeze({
|
|
1427
1699
|
kind: "select",
|
|
1428
1700
|
from: node.from,
|
|
1429
1701
|
joins: noJoins,
|
|
1430
1702
|
projections,
|
|
1431
|
-
filters
|
|
1432
|
-
orderBy: positions &&
|
|
1703
|
+
filters,
|
|
1704
|
+
orderBy: positions && orderBy.length > 0 ? sortByPositions(orderBy, projections) : orderBy,
|
|
1433
1705
|
limit: node.limit,
|
|
1434
1706
|
offset: node.offset,
|
|
1435
1707
|
rows: node.rows,
|
|
1436
1708
|
value: node.value,
|
|
1437
|
-
guaranteedBy
|
|
1709
|
+
guaranteedBy,
|
|
1710
|
+
recordTemplate
|
|
1438
1711
|
});
|
|
1439
1712
|
}
|
|
1713
|
+
/** Returns the fields of `selected` that a statement selects, or `selected` itself without literals. */
|
|
1714
|
+
function dropLiterals(selected) {
|
|
1715
|
+
if (!selected.some(isLiteralSelection)) return selected;
|
|
1716
|
+
const fields = [];
|
|
1717
|
+
for (const field of selected) if (!isLiteralSelection(field)) fields.push(field);
|
|
1718
|
+
return fields;
|
|
1719
|
+
}
|
|
1720
|
+
/** Creates the template of the records of `selected`: every field in its order with the value of a literal, or `undefined` without literals. */
|
|
1721
|
+
function createRecordTemplate(selected) {
|
|
1722
|
+
if (!selected.some(isLiteralSelection)) return;
|
|
1723
|
+
const template = [];
|
|
1724
|
+
for (const field of selected) template.push(Object.freeze({
|
|
1725
|
+
key: field.key,
|
|
1726
|
+
value: isLiteralSelection(field) ? field.literal : void 0
|
|
1727
|
+
}));
|
|
1728
|
+
return template;
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Tells whether `field`, a field of a query lowered to `projection`, is a nested query that a page after an offset evaluates only over its rows: one that no sort key of `orderBy`, the lowered order, sorts by.
|
|
1732
|
+
*
|
|
1733
|
+
* The query evaluates a field that it sorts by, of an aggregate or of `exists`, for every row anyway.
|
|
1734
|
+
*/
|
|
1735
|
+
function isPagedField(field, projection, orderBy) {
|
|
1736
|
+
if (!isNestedSelection(field)) return false;
|
|
1737
|
+
const { expression } = projection;
|
|
1738
|
+
return !orderBy.some((order) => order.expression === expression);
|
|
1739
|
+
}
|
|
1740
|
+
/** Rejects a query of a relation outside the definitions of `context`. */
|
|
1741
|
+
function checkRelation(node, context) {
|
|
1742
|
+
const { relation } = node;
|
|
1743
|
+
const { definitions } = context;
|
|
1744
|
+
if (relation !== void 0 && definitions !== void 0 && !definitions.has(relation)) throw new Error(`Relation "${formatRelationName(relation.key, relation.name)}" is not part of the schema`);
|
|
1745
|
+
}
|
|
1746
|
+
/** Rejects a nested query bound to `db`, which the error names as `subject`, unless `db` is the `Db` of `context` or `undefined`. */
|
|
1747
|
+
function checkDb(db, context, subject) {
|
|
1748
|
+
if (db !== void 0 && db !== context.db) throw new Error(context.db === void 0 ? `${subject} is bound to a Db, so only that Db can compile the query` : `${subject} is bound to another Db`);
|
|
1749
|
+
}
|
|
1440
1750
|
/**
|
|
1441
|
-
* Lowers `node` with `projections` to a statement that evaluates its nested queries over the rows of a derived page.
|
|
1751
|
+
* Lowers `node` with `projections`, `filters`, and `orderBy`, the lowered parts of the node, to a statement that evaluates its nested queries, apart from those that it sorts by, over the rows of a derived page.
|
|
1752
|
+
* `queryFields` are the fields of the query without literals, and `projections` their lowered projections in the same order.
|
|
1442
1753
|
* The derived source applies the filters, the order, the limit, and the offset, and replaces the occurrence of the table.
|
|
1443
1754
|
*
|
|
1444
|
-
* It returns `undefined` when a nested query holds a SQL fragment that refers to a column of the table outside the queries nested in the fragment.
|
|
1755
|
+
* It returns `undefined` when a nested query that it evaluates over the rows of the page holds a SQL fragment that refers to a column of the table outside the queries nested in the fragment.
|
|
1445
1756
|
* Such a fragment may hold an aggregate of the query, which the limit and the offset apply after.
|
|
1446
1757
|
*
|
|
1447
1758
|
* The source selects the columns of the table that the fields and the nested queries of the query read, under their keys, and the other scalar fields of the query, which it evaluates before the limit and the offset as the query would, such as a window function over all its rows.
|
|
1759
|
+
* A field of an aggregate or of `exists` that the query sorts by is such a scalar field: the order needs its value for every row.
|
|
1448
1760
|
* A column that no field reads stays out of the source, which could not select it beside an aggregate of a SQL fragment, such as `count(*)`.
|
|
1449
1761
|
* The order of a derived source does not carry into the query that reads it, so the statement sorts the rows of the page again by the sort keys of the source, by positions when `positions` is set.
|
|
1450
1762
|
*/
|
|
1451
|
-
function lowerPage(node, projections, positions, guaranteedBy) {
|
|
1763
|
+
function lowerPage(node, queryFields, projections, filters, orderBy, positions, guaranteedBy, recordTemplate) {
|
|
1452
1764
|
const { from } = node;
|
|
1453
1765
|
const columnKeys = /* @__PURE__ */ new Set();
|
|
1454
1766
|
const fields = [];
|
|
@@ -1456,7 +1768,7 @@ function lowerPage(node, projections, positions, guaranteedBy) {
|
|
|
1456
1768
|
for (let index = 0; index < projections.length; index++) {
|
|
1457
1769
|
const { expression } = projections[index];
|
|
1458
1770
|
let key;
|
|
1459
|
-
if (
|
|
1771
|
+
if (isPagedField(queryFields[index], projections[index], orderBy)) {
|
|
1460
1772
|
if (holdsFragmentOfSource(expression, from)) return;
|
|
1461
1773
|
collectColumnKeys(expression, from, columnKeys);
|
|
1462
1774
|
} else if (expression.kind === "column" && expression.source === from) columnKeys.add(expression.key);
|
|
@@ -1486,8 +1798,8 @@ function lowerPage(node, projections, positions, guaranteedBy) {
|
|
|
1486
1798
|
from,
|
|
1487
1799
|
joins: noJoins,
|
|
1488
1800
|
projections: sourced,
|
|
1489
|
-
filters
|
|
1490
|
-
orderBy
|
|
1801
|
+
filters,
|
|
1802
|
+
orderBy,
|
|
1491
1803
|
limit: node.limit,
|
|
1492
1804
|
offset: node.offset,
|
|
1493
1805
|
rows: "many",
|
|
@@ -1519,17 +1831,19 @@ function lowerPage(node, projections, positions, guaranteedBy) {
|
|
|
1519
1831
|
offset: void 0,
|
|
1520
1832
|
rows: node.rows,
|
|
1521
1833
|
value: node.value,
|
|
1522
|
-
guaranteedBy
|
|
1834
|
+
guaranteedBy,
|
|
1835
|
+
recordTemplate
|
|
1523
1836
|
});
|
|
1524
1837
|
}
|
|
1525
1838
|
/** Lowers an aggregate query to a statement of the value of its aggregate over the rows that its filters, limit, and offset choose. */
|
|
1526
|
-
function lowerAggregate(node) {
|
|
1839
|
+
function lowerAggregate(node, context) {
|
|
1527
1840
|
const field = node.projections[0];
|
|
1528
1841
|
const aggregate = field.expression;
|
|
1842
|
+
const orderBy = lowerOrder(node.orderBy, context);
|
|
1529
1843
|
let from = node.from;
|
|
1530
|
-
let filters = node.filters;
|
|
1531
|
-
let args = aggregate.arguments;
|
|
1532
|
-
let order = field.ordered ?
|
|
1844
|
+
let filters = lowerFilters(node.filters, context);
|
|
1845
|
+
let args = lowerEach(aggregate.arguments, context, lowerExpression);
|
|
1846
|
+
let order = field.ordered ? orderBy : noOrder;
|
|
1533
1847
|
const counted = aggregate === countProjection.expression;
|
|
1534
1848
|
if (node.limit !== void 0 || node.offset !== void 0 || !counted && !refersToSource(args[0], node.from)) {
|
|
1535
1849
|
const projections = [];
|
|
@@ -1543,8 +1857,8 @@ function lowerAggregate(node) {
|
|
|
1543
1857
|
from: node.from,
|
|
1544
1858
|
joins: noJoins,
|
|
1545
1859
|
projections,
|
|
1546
|
-
filters
|
|
1547
|
-
orderBy: counted ? noOrder :
|
|
1860
|
+
filters,
|
|
1861
|
+
orderBy: counted ? noOrder : orderBy,
|
|
1548
1862
|
limit: node.limit,
|
|
1549
1863
|
offset: node.offset,
|
|
1550
1864
|
rows: "many",
|
|
@@ -1571,7 +1885,7 @@ function lowerAggregate(node) {
|
|
|
1571
1885
|
arguments: args,
|
|
1572
1886
|
orderBy: order
|
|
1573
1887
|
});
|
|
1574
|
-
const
|
|
1888
|
+
const fallbacks = lowerEach(field.fallbacks, context, lowerExpression);
|
|
1575
1889
|
let value = call;
|
|
1576
1890
|
if (fallbacks.length > 0) {
|
|
1577
1891
|
const values = [call];
|
|
@@ -1607,12 +1921,15 @@ function refersToSource(node, source) {
|
|
|
1607
1921
|
case "param":
|
|
1608
1922
|
case "boolean":
|
|
1609
1923
|
case "values":
|
|
1924
|
+
case "nestedQuery":
|
|
1610
1925
|
case "tableName":
|
|
1611
1926
|
case "subquery":
|
|
1612
|
-
case "exists":
|
|
1927
|
+
case "exists":
|
|
1928
|
+
case "textSubquery": return false;
|
|
1613
1929
|
case "cast":
|
|
1614
1930
|
case "not":
|
|
1615
|
-
case "isNull":
|
|
1931
|
+
case "isNull":
|
|
1932
|
+
case "inQuery": return refersToSource(node.operand, source);
|
|
1616
1933
|
case "and":
|
|
1617
1934
|
case "or": return node.operands.some((operand) => refersToSource(operand, source));
|
|
1618
1935
|
case "comparison": return refersToSource(node.left, source) || refersToSource(node.right, source);
|
|
@@ -1622,40 +1939,102 @@ function refersToSource(node, source) {
|
|
|
1622
1939
|
default: return node;
|
|
1623
1940
|
}
|
|
1624
1941
|
}
|
|
1942
|
+
/** Lowers a query of `exists` to `EXISTS` of the rows that its filters, limit, and offset choose. */
|
|
1943
|
+
function lowerExists(node, context) {
|
|
1944
|
+
checkRelation(node, context);
|
|
1945
|
+
return createNode({
|
|
1946
|
+
kind: "exists",
|
|
1947
|
+
query: Object.freeze({
|
|
1948
|
+
kind: "select",
|
|
1949
|
+
from: node.from,
|
|
1950
|
+
joins: noJoins,
|
|
1951
|
+
projections: noProjections,
|
|
1952
|
+
filters: lowerFilters(node.filters, context),
|
|
1953
|
+
orderBy: noOrder,
|
|
1954
|
+
limit: node.limit,
|
|
1955
|
+
offset: node.offset,
|
|
1956
|
+
rows: "many",
|
|
1957
|
+
value: false,
|
|
1958
|
+
guaranteedBy: void 0
|
|
1959
|
+
})
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
/** Lowers a query of `exists` to a statement without a source of one row of `EXISTS` of its rows. */
|
|
1963
|
+
function selectExists(node, context) {
|
|
1964
|
+
return Object.freeze({
|
|
1965
|
+
kind: "select",
|
|
1966
|
+
from: void 0,
|
|
1967
|
+
joins: noJoins,
|
|
1968
|
+
projections: [projectExists(node.projections[0].key, node, context)],
|
|
1969
|
+
filters: noFilters,
|
|
1970
|
+
orderBy: noOrder,
|
|
1971
|
+
limit: void 0,
|
|
1972
|
+
offset: void 0,
|
|
1973
|
+
rows: node.rows,
|
|
1974
|
+
value: true,
|
|
1975
|
+
guaranteedBy: void 0
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
/** Creates the projection of `EXISTS` of the rows of `node`, a query of `exists`, under `key`, decoded by the type of the projection of `exists`. */
|
|
1979
|
+
function projectExists(key, node, context) {
|
|
1980
|
+
return Object.freeze({
|
|
1981
|
+
key,
|
|
1982
|
+
expression: lowerExists(node, context),
|
|
1983
|
+
type: node.projections[0].type
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1625
1986
|
/** Lowers a query of `$select` to a statement without a source, which returns one row of its fields. */
|
|
1626
|
-
function lowerRecord(node,
|
|
1987
|
+
function lowerRecord(node, context) {
|
|
1627
1988
|
return Object.freeze({
|
|
1628
1989
|
kind: "select",
|
|
1629
1990
|
from: void 0,
|
|
1630
1991
|
joins: noJoins,
|
|
1631
|
-
projections: lowerProjections(node.projections,
|
|
1992
|
+
projections: lowerProjections(dropLiterals(node.projections), context),
|
|
1632
1993
|
filters: noFilters,
|
|
1633
1994
|
orderBy: noOrder,
|
|
1634
1995
|
limit: void 0,
|
|
1635
1996
|
offset: void 0,
|
|
1636
1997
|
rows: "one",
|
|
1637
1998
|
value: false,
|
|
1638
|
-
guaranteedBy: void 0
|
|
1999
|
+
guaranteedBy: void 0,
|
|
2000
|
+
recordTemplate: createRecordTemplate(node.projections)
|
|
1639
2001
|
});
|
|
1640
2002
|
}
|
|
1641
2003
|
/**
|
|
1642
|
-
* Lowers the fields of a query to projections, packing the rows of each nested query into the value of its field, or selecting the value of an aggregate query; checks them as `lowerQuery` does.
|
|
2004
|
+
* Lowers the fields of a query to projections, packing the rows of each nested query into the value of its field, or selecting the value of an aggregate query or `EXISTS` of a query of `exists`, and replacing the queries in the expressions of the other fields; checks them as `lowerQuery` does.
|
|
1643
2005
|
*
|
|
1644
|
-
* Without
|
|
2006
|
+
* Without queries, it returns `selected` itself.
|
|
1645
2007
|
*/
|
|
1646
|
-
function lowerProjections(selected,
|
|
2008
|
+
function lowerProjections(selected, context) {
|
|
1647
2009
|
let lowered;
|
|
1648
2010
|
for (let index = 0; index < selected.length; index++) {
|
|
1649
2011
|
const field = selected[index];
|
|
2012
|
+
let projection;
|
|
1650
2013
|
if (isNestedSelection(field)) {
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
2014
|
+
checkDb(field.db, context, `Selected field "${field.key}"`);
|
|
2015
|
+
projection = projectQuery(field.key, field.query, context);
|
|
2016
|
+
} else {
|
|
2017
|
+
const expression = lowerExpression(field.expression, context);
|
|
2018
|
+
projection = expression === field.expression ? field : Object.freeze({
|
|
2019
|
+
key: field.key,
|
|
2020
|
+
expression,
|
|
2021
|
+
type: field.type
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
if (lowered !== void 0) lowered.push(projection);
|
|
2025
|
+
else if (projection !== field) {
|
|
2026
|
+
lowered = selected.slice(0, index);
|
|
2027
|
+
lowered.push(projection);
|
|
2028
|
+
}
|
|
1656
2029
|
}
|
|
1657
2030
|
return lowered ?? selected;
|
|
1658
2031
|
}
|
|
2032
|
+
/** Creates the projection of the value of `node`, a nested query, under `key`: `EXISTS` of a query of `exists`, the value of an aggregate query, or the packed rows of another query. */
|
|
2033
|
+
function projectQuery(key, node, context) {
|
|
2034
|
+
if (isExistsQuery(node)) return projectExists(key, node, context);
|
|
2035
|
+
const query = lowerSelect(node, context, false);
|
|
2036
|
+
return isAggregateQuery(node) ? projectAggregateValue(key, query) : projectRows(key, query);
|
|
2037
|
+
}
|
|
1659
2038
|
/** Creates the projection of the value of `query`, the statement of an aggregate query, under `key`, selected as a scalar subquery and decoded by the type of the aggregate. */
|
|
1660
2039
|
function projectAggregateValue(key, query) {
|
|
1661
2040
|
return Object.freeze({
|
|
@@ -1667,508 +2046,230 @@ function projectAggregateValue(key, query) {
|
|
|
1667
2046
|
type: query.projections[0].type
|
|
1668
2047
|
});
|
|
1669
2048
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
2049
|
+
/** Lowers the filters of a query, replacing the queries in their expressions; without queries, it returns `filters` itself. */
|
|
2050
|
+
function lowerFilters(filters, context) {
|
|
2051
|
+
const { relation, scope, user } = filters;
|
|
2052
|
+
const loweredRelation = relation === void 0 ? void 0 : lowerExpression(relation, context);
|
|
2053
|
+
const loweredScope = scope === void 0 ? void 0 : lowerExpression(scope, context);
|
|
2054
|
+
const loweredUser = user === void 0 ? void 0 : lowerExpression(user, context);
|
|
2055
|
+
if (loweredRelation === relation && loweredScope === scope && loweredUser === user) return filters;
|
|
2056
|
+
const lowered = {};
|
|
2057
|
+
if (loweredRelation !== void 0) lowered.relation = loweredRelation;
|
|
2058
|
+
if (loweredScope !== void 0) lowered.scope = loweredScope;
|
|
2059
|
+
if (loweredUser !== void 0) lowered.user = loweredUser;
|
|
2060
|
+
return Object.freeze(lowered);
|
|
1674
2061
|
}
|
|
1675
|
-
/**
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
2062
|
+
/**
|
|
2063
|
+
* Lowers the sort keys of a query, replacing the queries in their expressions; without queries, it returns `orderBy` itself.
|
|
2064
|
+
*
|
|
2065
|
+
* A sort key of a field of `selected`, the fields of the query, takes the expression of its projection among `projections`, the lowered fields, so that the statement still finds the projection that has its value.
|
|
2066
|
+
* A field of an aggregate or of `exists` is identified by the node of its reference, which holds the field.
|
|
2067
|
+
*/
|
|
2068
|
+
function lowerOrder(orderBy, context, selected, projections) {
|
|
2069
|
+
let lowered;
|
|
2070
|
+
for (let index = 0; index < orderBy.length; index++) {
|
|
2071
|
+
const order = orderBy[index];
|
|
2072
|
+
const { expression } = order;
|
|
2073
|
+
const position = selected === void 0 || projections === void 0 || projections === selected ? -1 : selected.findIndex((field) => isFieldValue(field, expression));
|
|
2074
|
+
const sorted = position === -1 ? lowerExpression(expression, context) : projections[position].expression;
|
|
2075
|
+
if (lowered === void 0 && sorted === expression) continue;
|
|
2076
|
+
lowered ??= orderBy.slice(0, index);
|
|
2077
|
+
lowered.push(sorted === expression ? order : Object.freeze({
|
|
2078
|
+
expression: sorted,
|
|
2079
|
+
direction: order.direction,
|
|
2080
|
+
nulls: order.nulls
|
|
2081
|
+
}));
|
|
1681
2082
|
}
|
|
1682
|
-
|
|
1683
|
-
return db.$compile(query);
|
|
1684
|
-
}
|
|
1685
|
-
// src/query/column-refs.ts
|
|
1686
|
-
/** Context of a query whose table has no relations, such as one of `from(table)`. */
|
|
1687
|
-
const noContext = /* @__PURE__ */ Object.freeze({ relations: /* @__PURE__ */ Object.freeze({}) });
|
|
1688
|
-
/** Creates expressions of the columns of `source`, each with the type of its column. */
|
|
1689
|
-
function createColumnRefs(source) {
|
|
1690
|
-
return addColumns({}, source);
|
|
2083
|
+
return lowered ?? orderBy;
|
|
1691
2084
|
}
|
|
1692
|
-
/**
|
|
1693
|
-
function
|
|
1694
|
-
return
|
|
1695
|
-
}
|
|
1696
|
-
function addColumns(refs, source) {
|
|
1697
|
-
const { columns } = source.table;
|
|
1698
|
-
for (const key of Object.keys(columns)) refs[key] = toExpression(createNode({
|
|
1699
|
-
kind: "column",
|
|
1700
|
-
source,
|
|
1701
|
-
key
|
|
1702
|
-
}), columns[key].type);
|
|
1703
|
-
return refs;
|
|
2085
|
+
/** Tells whether the sort expression `expression` is the value of `field`: its expression, or for a field of an aggregate or of `exists`, the node of its reference in the callback of `orderBy`. */
|
|
2086
|
+
function isFieldValue(field, expression) {
|
|
2087
|
+
return isNestedSelection(field) ? expression.kind === "nestedQuery" && expression.field === field : field.expression === expression;
|
|
1704
2088
|
}
|
|
1705
|
-
// src/query/resolve-values.ts
|
|
1706
2089
|
/**
|
|
1707
|
-
*
|
|
2090
|
+
* Lowers an expression, replacing each query in it and checking the query as a nested query.
|
|
2091
|
+
* A query of `exists`, such as in a condition, becomes `EXISTS` of its rows, another query of one value a scalar subquery of its value, the query of the values of `IN` its statement, and another query in SQL text its statement in parentheses.
|
|
1708
2092
|
*
|
|
1709
|
-
*
|
|
2093
|
+
* It returns `node` itself when it holds no query, and creates only the nodes that hold one.
|
|
1710
2094
|
*/
|
|
1711
|
-
function
|
|
2095
|
+
function lowerExpression(node, context) {
|
|
1712
2096
|
switch (node.kind) {
|
|
1713
|
-
case "
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
2097
|
+
case "column":
|
|
2098
|
+
case "param":
|
|
2099
|
+
case "boolean":
|
|
2100
|
+
case "values":
|
|
2101
|
+
case "tableName":
|
|
2102
|
+
case "subquery":
|
|
2103
|
+
case "exists":
|
|
2104
|
+
case "textSubquery": return node;
|
|
2105
|
+
case "nestedQuery": return lowerNestedQuery(node, context);
|
|
2106
|
+
case "cast": {
|
|
2107
|
+
const operand = lowerExpression(node.operand, context);
|
|
2108
|
+
return operand === node.operand ? node : createNode({
|
|
2109
|
+
kind: "cast",
|
|
2110
|
+
operand,
|
|
2111
|
+
type: node.type
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
case "not": {
|
|
2115
|
+
const operand = lowerExpression(node.operand, context);
|
|
2116
|
+
return operand === node.operand ? node : createNode({
|
|
2117
|
+
kind: "not",
|
|
2118
|
+
operand
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
case "isNull": {
|
|
2122
|
+
const operand = lowerExpression(node.operand, context);
|
|
2123
|
+
return operand === node.operand ? node : createNode({
|
|
2124
|
+
kind: "isNull",
|
|
2125
|
+
operand
|
|
2126
|
+
});
|
|
1728
2127
|
}
|
|
1729
2128
|
case "and":
|
|
1730
2129
|
case "or": {
|
|
1731
|
-
const operands =
|
|
1732
|
-
|
|
1733
|
-
|
|
2130
|
+
const operands = lowerEach(node.operands, context, lowerExpression);
|
|
2131
|
+
return operands === node.operands ? node : createNode({
|
|
2132
|
+
kind: node.kind,
|
|
2133
|
+
operands
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
case "comparison": {
|
|
2137
|
+
const left = lowerExpression(node.left, context);
|
|
2138
|
+
const right = lowerExpression(node.right, context);
|
|
2139
|
+
return left === node.left && right === node.right ? node : createNode({
|
|
2140
|
+
kind: "comparison",
|
|
2141
|
+
operator: node.operator,
|
|
2142
|
+
left,
|
|
2143
|
+
right
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
case "in": {
|
|
2147
|
+
const operand = lowerExpression(node.operand, context);
|
|
2148
|
+
const list = lowerEach(node.list, context, lowerExpression);
|
|
2149
|
+
return operand === node.operand && list === node.list ? node : createNode({
|
|
2150
|
+
kind: "in",
|
|
2151
|
+
operand,
|
|
2152
|
+
list
|
|
2153
|
+
});
|
|
2154
|
+
}
|
|
2155
|
+
case "inQuery": {
|
|
2156
|
+
const operand = lowerExpression(node.operand, context);
|
|
2157
|
+
const query = node.query.kind === "nestedQuery" ? lowerQueryOfValues(node.query, context) : node.query;
|
|
2158
|
+
return operand === node.operand && query === node.query ? node : createNode({
|
|
2159
|
+
kind: "inQuery",
|
|
2160
|
+
operand,
|
|
2161
|
+
query
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
case "raw": {
|
|
2165
|
+
const values = lowerEach(node.values, context, lowerExpression);
|
|
2166
|
+
return values === node.values ? node : createNode({
|
|
2167
|
+
kind: "raw",
|
|
2168
|
+
fragments: node.fragments,
|
|
2169
|
+
values
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
case "aggregate": {
|
|
2173
|
+
const args = lowerEach(node.arguments, context, lowerExpression);
|
|
2174
|
+
const orderBy = lowerOrder(node.orderBy, context);
|
|
2175
|
+
return args === node.arguments && orderBy === node.orderBy ? node : createNode({
|
|
2176
|
+
kind: "aggregate",
|
|
2177
|
+
name: node.name,
|
|
2178
|
+
distinct: node.distinct,
|
|
2179
|
+
arguments: args,
|
|
2180
|
+
orderBy
|
|
2181
|
+
});
|
|
1734
2182
|
}
|
|
1735
|
-
case "not": return negateNode(resolveValues(node.operand, source));
|
|
1736
|
-
case "raw": return createNode({
|
|
1737
|
-
...node,
|
|
1738
|
-
values: node.values.map((value) => resolveValues(value, source))
|
|
1739
|
-
});
|
|
1740
2183
|
default: return node;
|
|
1741
2184
|
}
|
|
1742
2185
|
}
|
|
1743
|
-
/**
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
const { user } = filters;
|
|
1756
|
-
return Object.freeze({
|
|
1757
|
-
...filters,
|
|
1758
|
-
user: user ? combineNodes("and", [user, predicate]) : predicate
|
|
2186
|
+
/**
|
|
2187
|
+
* Lowers a query in an expression to `EXISTS` of the rows of a query of `exists`, to a scalar subquery of the value of another query of one value, or to its statement in parentheses for a query of records or of rows `many`, which only SQL text holds.
|
|
2188
|
+
*
|
|
2189
|
+
* In SQL text, a query of `exists` stays `EXISTS` rather than a subquery of its value, so that PostgreSQL can join its rows in a condition, as a semi-join.
|
|
2190
|
+
*/
|
|
2191
|
+
function lowerNestedQuery(node, context) {
|
|
2192
|
+
checkDb(node.db, context, "A query in an expression");
|
|
2193
|
+
const { query } = node;
|
|
2194
|
+
if (isExistsQuery(query)) return lowerExists(query, context);
|
|
2195
|
+
return createNode({
|
|
2196
|
+
kind: query.value && query.rows !== "many" ? "subquery" : "textSubquery",
|
|
2197
|
+
query: lowerSelect(query, context, false)
|
|
1759
2198
|
});
|
|
1760
2199
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
2200
|
+
/** Lowers the query of the values of `IN` to its statement, whatever its rows. */
|
|
2201
|
+
function lowerQueryOfValues(node, context) {
|
|
2202
|
+
checkDb(node.db, context, "A query in an expression");
|
|
2203
|
+
return lowerSelect(node.query, context, false);
|
|
1763
2204
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
for (let index = 0; index < primaryKey.length; index++) {
|
|
1776
|
-
const column = primaryKey[index];
|
|
1777
|
-
filter[column] = requireKeyValue(table, column, values[index], "find");
|
|
1778
|
-
}
|
|
1779
|
-
return filter;
|
|
1780
|
-
}
|
|
1781
|
-
/**
|
|
1782
|
-
* Returns the object of column values that `findBy` filters a query of `table` by: a copy of `values`, read once.
|
|
1783
|
-
*
|
|
1784
|
-
* It rejects columns that are not exactly a key of `table`, primary or unique, as `isKey` tells, and `null` and `undefined`.
|
|
1785
|
-
*/
|
|
1786
|
-
function toKeyValues(table, values) {
|
|
1787
|
-
if (typeof values !== "object" || values === null) throw new TypeError(`findBy takes an object of the columns of a key of table "${table.name}"`);
|
|
1788
|
-
const columns = Object.keys(values);
|
|
1789
|
-
if (!isKey(table, columns)) {
|
|
1790
|
-
const listed = columns.length === 0 ? "an object without columns" : `columns ${quoteColumns(columns)}`;
|
|
1791
|
-
throw new Error(`findBy takes the columns of one key of table "${table.name}", primary or unique, not ${listed}`);
|
|
1792
|
-
}
|
|
1793
|
-
const filter = {};
|
|
1794
|
-
for (const column of columns) {
|
|
1795
|
-
const value = values[column];
|
|
1796
|
-
filter[column] = requireKeyValue(table, column, value, "findBy");
|
|
1797
|
-
}
|
|
1798
|
-
return filter;
|
|
1799
|
-
}
|
|
1800
|
-
/** Returns `value` of column `column` of a key of `table` that `method` takes, rejecting `null` and `undefined`. */
|
|
1801
|
-
function requireKeyValue(table, column, value, method) {
|
|
1802
|
-
if (value === null || value === void 0) throw new TypeError(`${method} got ${value} for column "${column}" of table "${table.name}"; a value of a key is never null or undefined`);
|
|
1803
|
-
return value;
|
|
1804
|
-
}
|
|
1805
|
-
/** Lists `columns` in quotes, separated by commas. */
|
|
1806
|
-
function quoteColumns(columns) {
|
|
1807
|
-
return columns.map((column) => `"${column}"`).join(", ");
|
|
1808
|
-
}
|
|
1809
|
-
// src/query/order.ts
|
|
1810
|
-
/** Direction and placement of `NULL` of a sort key node, by `OrderDirection`. */
|
|
1811
|
-
const directions = /* @__PURE__ */ Object.freeze({
|
|
1812
|
-
asc: {
|
|
1813
|
-
direction: "asc",
|
|
1814
|
-
nulls: void 0
|
|
1815
|
-
},
|
|
1816
|
-
desc: {
|
|
1817
|
-
direction: "desc",
|
|
1818
|
-
nulls: void 0
|
|
1819
|
-
},
|
|
1820
|
-
"asc nulls first": {
|
|
1821
|
-
direction: "asc",
|
|
1822
|
-
nulls: "first"
|
|
1823
|
-
},
|
|
1824
|
-
"asc nulls last": {
|
|
1825
|
-
direction: "asc",
|
|
1826
|
-
nulls: "last"
|
|
1827
|
-
},
|
|
1828
|
-
"desc nulls first": {
|
|
1829
|
-
direction: "desc",
|
|
1830
|
-
nulls: "first"
|
|
1831
|
-
},
|
|
1832
|
-
"desc nulls last": {
|
|
1833
|
-
direction: "desc",
|
|
1834
|
-
nulls: "last"
|
|
1835
|
-
}
|
|
1836
|
-
});
|
|
1837
|
-
const orderingNode = Symbol("kern-orm.ordering.node");
|
|
1838
|
-
/** Runtime class of an expression with a direction, which holds its sort key node. */
|
|
1839
|
-
var OrderingObject = class {
|
|
1840
|
-
[orderingNode];
|
|
1841
|
-
constructor(node) {
|
|
1842
|
-
this[orderingNode] = node;
|
|
1843
|
-
Object.freeze(this);
|
|
1844
|
-
}
|
|
1845
|
-
};
|
|
1846
|
-
/** Sorts by `expression` in ascending order, in the callback of `orderBy`. */
|
|
1847
|
-
function asc(expression) {
|
|
1848
|
-
return orderBy(expression, "asc");
|
|
1849
|
-
}
|
|
1850
|
-
/** Sorts by `expression` in descending order, in the callback of `orderBy`. */
|
|
1851
|
-
function desc(expression) {
|
|
1852
|
-
return orderBy(expression, "desc");
|
|
1853
|
-
}
|
|
1854
|
-
/** Sorts by `expression` in `direction`, such as `"desc nulls last"`, in the callback of `orderBy`. */
|
|
1855
|
-
function orderBy(expression, direction) {
|
|
1856
|
-
if (!isExpression(expression)) throw new TypeError("A sort key must be an expression");
|
|
1857
|
-
return new OrderingObject(createOrderNode(toNode(expression), direction));
|
|
1858
|
-
}
|
|
1859
|
-
/**
|
|
1860
|
-
* Creates the order of `node` with the sort keys of the arguments of `orderBy` after its own.
|
|
1861
|
-
*
|
|
1862
|
-
* `args` are column keys and objects of columns with directions, or a callback that receives the columns and the selected fields and returns one sort key or an array of them.
|
|
1863
|
-
*/
|
|
1864
|
-
function addOrder(node, args) {
|
|
1865
|
-
const order = node.orderBy.slice();
|
|
1866
|
-
const first = args[0];
|
|
1867
|
-
if (typeof first === "function") {
|
|
1868
|
-
const terms = first(createColumnRefs(node.from), createSelectedRefs(node));
|
|
1869
|
-
if (Array.isArray(terms)) for (const term of terms) order.push(readTerm(term));
|
|
1870
|
-
else order.push(readTerm(terms));
|
|
1871
|
-
return order;
|
|
2205
|
+
/** Lowers each of `items` with `lower`; when none changes, it returns `items` itself. */
|
|
2206
|
+
function lowerEach(items, context, lower) {
|
|
2207
|
+
let lowered;
|
|
2208
|
+
for (let index = 0; index < items.length; index++) {
|
|
2209
|
+
const item = items[index];
|
|
2210
|
+
const result = lower(item, context);
|
|
2211
|
+
if (lowered !== void 0) lowered.push(result);
|
|
2212
|
+
else if (result !== item) {
|
|
2213
|
+
lowered = items.slice(0, index);
|
|
2214
|
+
lowered.push(result);
|
|
2215
|
+
}
|
|
1872
2216
|
}
|
|
1873
|
-
|
|
1874
|
-
else if (typeof arg === "object" && arg !== null && !Array.isArray(arg)) {
|
|
1875
|
-
const columns = arg;
|
|
1876
|
-
for (const key of Object.keys(columns)) order.push(createOrderNode(createColumnNode(node.from, key), columns[key]));
|
|
1877
|
-
} else throw new TypeError("orderBy takes column keys and objects of columns with directions, or a callback");
|
|
1878
|
-
return order;
|
|
2217
|
+
return lowered ?? items;
|
|
1879
2218
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
throw new TypeError("The callback of orderBy must return expressions, or sort keys of asc, desc, and orderBy");
|
|
1885
|
-
}
|
|
1886
|
-
/** Creates the sort key node of `expression` in `direction`, an `OrderDirection` checked at runtime. */
|
|
1887
|
-
function createOrderNode(expression, direction) {
|
|
1888
|
-
const placement = typeof direction === "string" && Object.hasOwn(directions, direction) ? directions[direction] : void 0;
|
|
1889
|
-
if (placement === void 0) throw new TypeError(`Unknown sort direction ${JSON.stringify(direction)}`);
|
|
1890
|
-
return Object.freeze({
|
|
1891
|
-
expression,
|
|
1892
|
-
direction: placement.direction,
|
|
1893
|
-
nulls: placement.nulls
|
|
1894
|
-
});
|
|
1895
|
-
}
|
|
1896
|
-
/** Creates a reference to the column `key` of the table of `source`. */
|
|
1897
|
-
function createColumnNode(source, key) {
|
|
1898
|
-
requireColumn(source.table, key);
|
|
1899
|
-
return createNode({
|
|
1900
|
-
kind: "column",
|
|
1901
|
-
source,
|
|
1902
|
-
key
|
|
1903
|
-
});
|
|
2219
|
+
// src/query/kind.ts
|
|
2220
|
+
/** `then` of every bound query, which executes the query on its `Db`. */
|
|
2221
|
+
function executeOnAwait(onFulfilled, onRejected) {
|
|
2222
|
+
return this.db.$execute(this).then(onFulfilled, onRejected);
|
|
1904
2223
|
}
|
|
1905
|
-
/**
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
const { projections } = node;
|
|
1912
|
-
if (node.value) return toSelectedExpression(projections[0]);
|
|
1913
|
-
const refs = {};
|
|
1914
|
-
for (const projection of projections) {
|
|
1915
|
-
const { key } = projection;
|
|
1916
|
-
if (isNestedSelection(projection)) Object.defineProperty(refs, key, {
|
|
1917
|
-
get() {
|
|
1918
|
-
throw new Error(`Field "${key}" holds a nested query and cannot be sorted by`);
|
|
1919
|
-
},
|
|
1920
|
-
enumerable: true
|
|
1921
|
-
});
|
|
1922
|
-
else refs[key] = toSelectedExpression(projection);
|
|
2224
|
+
/** Compiles `query` as its `toSQL` does: an unbound query with `options`, and a bound query as its `Db` executes it, which takes no options. */
|
|
2225
|
+
function compileToSQL(query, options) {
|
|
2226
|
+
const { db } = query;
|
|
2227
|
+
if (db === void 0) {
|
|
2228
|
+
const { text, parameters } = compileStatement(lowerStatement(query.node), options);
|
|
2229
|
+
return createStatement(text, parameters);
|
|
1923
2230
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
/** Creates an expression over the node of a projection of a query, which has a type: only lowering adds projections of nested queries. */
|
|
1927
|
-
function toSelectedExpression(projection) {
|
|
1928
|
-
return toExpression(projection.expression, projection.type);
|
|
1929
|
-
}
|
|
1930
|
-
// src/query/pagination.ts
|
|
1931
|
-
/** Creates the parameter of the number of rows of `limit` or `offset`, named by `method`, of query `node`, rejecting a query of one row and a count that is not a non-negative safe integer. */
|
|
1932
|
-
function createPageCount(node, method, count) {
|
|
1933
|
-
if (node.rows !== "many") throw new Error(`${method} applies only to a query of many rows${createPaginationHint(node)}`);
|
|
1934
|
-
if (!Number.isSafeInteger(count) || count < 0) throw new RangeError(`${method} takes a non-negative integer, not ${count}`);
|
|
1935
|
-
return createNode({
|
|
1936
|
-
kind: "param",
|
|
1937
|
-
value: count,
|
|
1938
|
-
type: void 0
|
|
1939
|
-
});
|
|
1940
|
-
}
|
|
1941
|
-
/** Creates the hint of the error of `limit` or `offset` of query `node` of one row: the method to call them before, if any. */
|
|
1942
|
-
function createPaginationHint(node) {
|
|
1943
|
-
if (node.relation?.kind === "one") return "";
|
|
1944
|
-
return isAggregateQuery(node) ? "; call it before count or another aggregate to aggregate the rows of a page" : "; call it before take or get";
|
|
2231
|
+
if (options !== void 0) throw new Error("A query bound to a Db compiles with the options of its Db");
|
|
2232
|
+
return db.$compile(query);
|
|
1945
2233
|
}
|
|
1946
|
-
// src/query/
|
|
2234
|
+
// src/query/record.ts
|
|
1947
2235
|
/**
|
|
1948
|
-
* Immutable
|
|
2236
|
+
* Immutable query of `$select` of state `S`, unbound or bound to a `Db`: one record whose fields are the values of expressions and of literals and the results of independent queries, selected in one statement.
|
|
1949
2237
|
*
|
|
1950
|
-
* Every method returns a query of the same kind and leaves this query unchanged.
|
|
1951
2238
|
* An unbound query is not thenable, so it can be returned from async functions; it is compiled with `toSQL` or executed by a `Db`.
|
|
1952
2239
|
* A bound query executes on its `Db` when awaited.
|
|
1953
2240
|
*/
|
|
1954
|
-
var
|
|
1955
|
-
table;
|
|
2241
|
+
var RecordQuery = class {
|
|
1956
2242
|
node;
|
|
1957
|
-
context;
|
|
1958
2243
|
/** `Db` that executes the query when it is awaited; `undefined` for an unbound query. */
|
|
1959
2244
|
db;
|
|
1960
2245
|
/** Executes a bound query on its `Db`; `undefined` for an unbound query, so that it is not thenable. */
|
|
1961
2246
|
then;
|
|
1962
|
-
constructor(
|
|
1963
|
-
this.table = table;
|
|
2247
|
+
constructor(node, db) {
|
|
1964
2248
|
this.node = node;
|
|
1965
|
-
this.context = context;
|
|
1966
2249
|
this.db = db;
|
|
1967
2250
|
this.then = db === void 0 ? void 0 : executeOnAwait;
|
|
1968
2251
|
Object.freeze(this);
|
|
1969
2252
|
}
|
|
1970
|
-
where(filter) {
|
|
1971
|
-
return this.derive(this.addFilter(filter));
|
|
1972
|
-
}
|
|
1973
|
-
select(...args) {
|
|
1974
|
-
this.rejectValue("select");
|
|
1975
|
-
return this.derive({ projections: mergeProjections(this.node.projections, this.node.from, args, this.context) });
|
|
1976
|
-
}
|
|
1977
|
-
clearSelect() {
|
|
1978
|
-
this.rejectValue("clearSelect");
|
|
1979
|
-
return this.derive({ projections: [] });
|
|
1980
|
-
}
|
|
1981
|
-
take() {
|
|
1982
|
-
return this.deriveTaken({});
|
|
1983
|
-
}
|
|
1984
|
-
find(...values) {
|
|
1985
|
-
return this.takeWhere(toPrimaryKeyValues(this.table, values));
|
|
1986
|
-
}
|
|
1987
|
-
findBy(values) {
|
|
1988
|
-
return this.takeWhere(toKeyValues(this.table, values));
|
|
1989
|
-
}
|
|
1990
|
-
get(arg) {
|
|
1991
|
-
this.rejectAggregate("get");
|
|
1992
|
-
return this.deriveTaken({
|
|
1993
|
-
projections: [projectValue(this.node.from, arg, "get")],
|
|
1994
|
-
value: true,
|
|
1995
|
-
rows: toValueRows(this.node.rows)
|
|
1996
|
-
});
|
|
1997
|
-
}
|
|
1998
|
-
pluck(arg) {
|
|
1999
|
-
this.rejectAggregate("pluck");
|
|
2000
|
-
return this.derive({
|
|
2001
|
-
projections: [projectValue(this.node.from, arg, "pluck")],
|
|
2002
|
-
value: true,
|
|
2003
|
-
rows: toValueRows(this.node.rows)
|
|
2004
|
-
});
|
|
2005
|
-
}
|
|
2006
|
-
count(arg) {
|
|
2007
|
-
if (arg !== void 0) return this.aggregateBy("count", arg);
|
|
2008
|
-
this.rejectAggregate("count");
|
|
2009
|
-
return this.deriveAggregate(countProjection);
|
|
2010
|
-
}
|
|
2011
|
-
countDistinct(arg) {
|
|
2012
|
-
return this.aggregateBy("countDistinct", arg);
|
|
2013
|
-
}
|
|
2014
|
-
min(arg) {
|
|
2015
|
-
return this.aggregateBy("min", arg);
|
|
2016
|
-
}
|
|
2017
|
-
max(arg) {
|
|
2018
|
-
return this.aggregateBy("max", arg);
|
|
2019
|
-
}
|
|
2020
|
-
sum(arg) {
|
|
2021
|
-
return this.aggregateBy("sum", arg);
|
|
2022
|
-
}
|
|
2023
|
-
avg(arg) {
|
|
2024
|
-
return this.aggregateBy("avg", arg);
|
|
2025
|
-
}
|
|
2026
|
-
every(arg) {
|
|
2027
|
-
return this.aggregateBy("every", arg);
|
|
2028
|
-
}
|
|
2029
|
-
some(arg) {
|
|
2030
|
-
return this.aggregateBy("some", arg);
|
|
2031
|
-
}
|
|
2032
|
-
stringAgg(arg, delimiter) {
|
|
2033
|
-
return this.aggregateBy("stringAgg", arg, delimiter);
|
|
2034
|
-
}
|
|
2035
|
-
coalesce(fallback) {
|
|
2036
|
-
const { node } = this;
|
|
2037
|
-
if (!node.value) throw new Error("coalesce applies only to a query of values, such as one made by get, pluck, or an aggregate");
|
|
2038
|
-
return this.derive({ projections: [coalesceValue(node.projections[0], fallback)] });
|
|
2039
|
-
}
|
|
2040
|
-
orderBy(...args) {
|
|
2041
|
-
return this.derive({ orderBy: addOrder(this.node, args) });
|
|
2042
|
-
}
|
|
2043
|
-
limit(count) {
|
|
2044
|
-
return this.derive({ limit: createPageCount(this.node, "limit", count) });
|
|
2045
|
-
}
|
|
2046
|
-
offset(count) {
|
|
2047
|
-
return this.derive({ offset: createPageCount(this.node, "offset", count) });
|
|
2048
|
-
}
|
|
2049
|
-
optional() {
|
|
2050
|
-
return this.changeOneRow("optional", "optional");
|
|
2051
|
-
}
|
|
2052
|
-
nullable() {
|
|
2053
|
-
if (this.node.value) throw new Error("nullable applies only to a query of records; use optional after get, pluck, or an aggregate");
|
|
2054
|
-
return this.changeOneRow("nullable", "nullable");
|
|
2055
|
-
}
|
|
2056
|
-
required() {
|
|
2057
|
-
return this.changeOneRow("required", "one");
|
|
2058
|
-
}
|
|
2059
|
-
insert(values) {
|
|
2060
|
-
return createInsert(this, [values], "one", "insert");
|
|
2061
|
-
}
|
|
2062
|
-
insertMany(records) {
|
|
2063
|
-
if (!Array.isArray(records)) throw new TypeError("insertMany takes an array of records");
|
|
2064
|
-
return createInsert(this, records, "many", "insertMany");
|
|
2065
|
-
}
|
|
2066
|
-
update(set) {
|
|
2067
|
-
return createUpdate(this, set);
|
|
2068
|
-
}
|
|
2069
|
-
delete() {
|
|
2070
|
-
return createDelete(this);
|
|
2071
|
-
}
|
|
2072
|
-
all() {
|
|
2073
|
-
if (this.node.rows !== "many") throw new Error("all applies only to a query of many rows");
|
|
2074
|
-
return this.derive({ all: true });
|
|
2075
|
-
}
|
|
2076
|
-
use(callback, ...args) {
|
|
2077
|
-
return requireDerived(this, callback(this, ...args), "The callback of use");
|
|
2078
|
-
}
|
|
2079
2253
|
toSQL(options) {
|
|
2080
2254
|
return compileToSQL(this, options);
|
|
2081
2255
|
}
|
|
2082
|
-
/**
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
...this.node,
|
|
2086
|
-
...changes
|
|
2087
|
-
}), this.context, this.db);
|
|
2088
|
-
}
|
|
2089
|
-
/**
|
|
2090
|
-
* Creates a query of the same kind and `Db` with the node changes applied and then those of `take`, in one derived query.
|
|
2091
|
-
*
|
|
2092
|
-
* It adds the changes of `take` to `changes`, which must leave the limit as it is.
|
|
2093
|
-
*/
|
|
2094
|
-
deriveTaken(changes) {
|
|
2095
|
-
if ((changes.rows ?? this.node.rows) === "many") {
|
|
2096
|
-
changes.limit = limitToOne(this.node.limit);
|
|
2097
|
-
changes.rows = "one";
|
|
2098
|
-
}
|
|
2099
|
-
return this.derive(changes);
|
|
2100
|
-
}
|
|
2101
|
-
/** Creates the query of the value of `projection`, an aggregate over the rows of this query, which always returns one row. */
|
|
2102
|
-
deriveAggregate(projection) {
|
|
2103
|
-
return this.derive({
|
|
2104
|
-
projections: [projection],
|
|
2105
|
-
rows: "one",
|
|
2106
|
-
value: true,
|
|
2107
|
-
guaranteed: false
|
|
2108
|
-
});
|
|
2109
|
-
}
|
|
2110
|
-
/** Creates the query of the aggregate of `method` over `arg`, a column key or a callback that receives the columns and returns an expression; `delimiter` is the delimiter of `stringAgg`. */
|
|
2111
|
-
aggregateBy(method, arg, delimiter) {
|
|
2112
|
-
this.rejectAggregate(method);
|
|
2113
|
-
return this.deriveAggregate(projectAggregate(method, projectValue(this.node.from, arg, method), delimiter));
|
|
2114
|
-
}
|
|
2115
|
-
/** Returns the changes of the node that add `filter` with `AND`, ending the guarantee of the row as `FilteredState` does in types. */
|
|
2116
|
-
addFilter(filter) {
|
|
2117
|
-
const { node } = this;
|
|
2118
|
-
return {
|
|
2119
|
-
filters: addUserFilter(node.filters, filter, node.from),
|
|
2120
|
-
rows: toFilteredRows(node.rows, node.value, node.guaranteed),
|
|
2121
|
-
guaranteed: false
|
|
2122
|
-
};
|
|
2123
|
-
}
|
|
2124
|
-
/** Creates the query of `find` and `findBy`: `where` of `values`, an object of column values, returning one row like `take`. */
|
|
2125
|
-
takeWhere(values) {
|
|
2126
|
-
const changes = this.addFilter(values);
|
|
2127
|
-
if (changes.rows === "many") changes.rows = "one";
|
|
2128
|
-
return this.derive(changes);
|
|
2129
|
-
}
|
|
2130
|
-
/** Creates the query of one row with rows mode `rows` that `method` returns, rejecting a query of many rows. */
|
|
2131
|
-
changeOneRow(method, rows) {
|
|
2132
|
-
if (this.node.rows === "many") throw new Error(`${method} applies only to a query of one row, such as one made by take`);
|
|
2133
|
-
return this.derive({
|
|
2134
|
-
rows,
|
|
2135
|
-
guaranteed: false
|
|
2136
|
-
});
|
|
2137
|
-
}
|
|
2138
|
-
/** Rejects a change of the selection by `method` of a query made by `get`, `pluck`, or an aggregate method. */
|
|
2139
|
-
rejectValue(method) {
|
|
2140
|
-
if (!this.node.value) return;
|
|
2141
|
-
this.rejectAggregate(method);
|
|
2142
|
-
throw new Error(`${method} cannot be used after get or pluck; call get or pluck to replace the value`);
|
|
2143
|
-
}
|
|
2144
|
-
/** Rejects `method`, which replaces the value or the selection, on a query made by an aggregate method. */
|
|
2145
|
-
rejectAggregate(method) {
|
|
2146
|
-
if (isAggregateQuery(this.node)) throw new Error(`${method} cannot be used after an aggregate`);
|
|
2256
|
+
/** Rejects the query as an operand of an expression, which takes a query of one value of a table. */
|
|
2257
|
+
[operandNode]() {
|
|
2258
|
+
throw new TypeError("An expression takes a query of one value of a table, such as of get, an aggregate, or exists, not a query of $select");
|
|
2147
2259
|
}
|
|
2148
2260
|
};
|
|
2149
2261
|
/**
|
|
2150
|
-
*
|
|
2262
|
+
* Starts the query of `$select` of `selection`, an object of expressions, queries, and literals by key, bound to `db` when given.
|
|
2151
2263
|
*
|
|
2152
|
-
*
|
|
2264
|
+
* It reads the keys and values of `selection` when it is called.
|
|
2153
2265
|
*/
|
|
2154
|
-
function
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
}
|
|
2162
|
-
/** Starts a query over a new occurrence of the table. */
|
|
2163
|
-
function from(table) {
|
|
2164
|
-
return startQuery(table, noContext);
|
|
2165
|
-
}
|
|
2166
|
-
/** Starts a query over a new occurrence of the table with the relations of `context`, such as a query of a table of a schema. */
|
|
2167
|
-
function startQuery(table, context) {
|
|
2168
|
-
return new SelectQuery(table, createQueryNode(Object.freeze({
|
|
2169
|
-
kind: "table",
|
|
2170
|
-
table
|
|
2171
|
-
})), context);
|
|
2266
|
+
function startRecordQuery(selection, db) {
|
|
2267
|
+
const projections = [];
|
|
2268
|
+
addSelection(projections, selection);
|
|
2269
|
+
return new RecordQuery(Object.freeze({
|
|
2270
|
+
kind: "record",
|
|
2271
|
+
projections
|
|
2272
|
+
}), db);
|
|
2172
2273
|
}
|
|
2173
2274
|
// src/query/sql-query.ts
|
|
2174
2275
|
/**
|
|
@@ -2230,6 +2331,10 @@ var SqlQuery = class SqlQuery {
|
|
|
2230
2331
|
toSQL() {
|
|
2231
2332
|
return this.db.$compile(this);
|
|
2232
2333
|
}
|
|
2334
|
+
/** Rejects the query as an operand of an expression, which takes a query of one value of a table. */
|
|
2335
|
+
[operandNode]() {
|
|
2336
|
+
throw new TypeError("An expression takes a query of one value of a table, such as of get, an aggregate, or exists, not a query of $query");
|
|
2337
|
+
}
|
|
2233
2338
|
/** Creates a query of the same `Db` with the node changes applied. */
|
|
2234
2339
|
derive(changes) {
|
|
2235
2340
|
return new SqlQuery(Object.freeze({
|
|
@@ -2265,10 +2370,112 @@ function startSqlQuery(sql, db) {
|
|
|
2265
2370
|
value: false
|
|
2266
2371
|
}), db);
|
|
2267
2372
|
}
|
|
2268
|
-
/** Returns `value` that `what` takes as its type, rejecting a value that is not a type, such as a column. */
|
|
2269
|
-
function requireType(value, what) {
|
|
2270
|
-
if (!isType(value)) throw new TypeError(`${what} takes a type, such as t.integer, not a column or another value`);
|
|
2271
|
-
return value;
|
|
2373
|
+
/** Returns `value` that `what` takes as its type, rejecting a value that is not a type, such as a column. */
|
|
2374
|
+
function requireType(value, what) {
|
|
2375
|
+
if (!isType(value)) throw new TypeError(`${what} takes a type, such as t.integer, not a column or another value`);
|
|
2376
|
+
return value;
|
|
2377
|
+
}
|
|
2378
|
+
// src/query/projections.ts
|
|
2379
|
+
/**
|
|
2380
|
+
* Creates the fields of `select` over `source`, appending new keys and replacing existing ones in place.
|
|
2381
|
+
*
|
|
2382
|
+
* `args` are the arguments of `select`: `"*"`, column keys, or a callback that receives the references of `source` with the relations of `context` and returns a selection.
|
|
2383
|
+
*/
|
|
2384
|
+
function mergeProjections(projections, source, args, context) {
|
|
2385
|
+
const result = projections.slice();
|
|
2386
|
+
const first = args[0];
|
|
2387
|
+
if (typeof first === "function") addSelection(result, first(createSelectionRefs(source, context)));
|
|
2388
|
+
else {
|
|
2389
|
+
const keys = first === "*" ? Object.keys(source.table.columns) : args;
|
|
2390
|
+
for (const key of keys) setProjection(result, projectColumn(source, key));
|
|
2391
|
+
}
|
|
2392
|
+
return result;
|
|
2393
|
+
}
|
|
2394
|
+
/** Adds the fields of `selection`, expressions, queries, and literals by key, to `projections`, replacing existing keys in place. */
|
|
2395
|
+
function addSelection(projections, selection) {
|
|
2396
|
+
for (const key of Reflect.ownKeys(selection)) {
|
|
2397
|
+
if (typeof key === "symbol") throw new TypeError("A selected field must have a string key");
|
|
2398
|
+
setProjection(projections, selectValue(key, selection[key]));
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Creates the projection of the value of `get` or `pluck`, or of the argument of an aggregate method, such as `max`, over `source`.
|
|
2403
|
+
*
|
|
2404
|
+
* `arg` is the argument of `method`: a column key, or a callback that receives the columns and returns an expression.
|
|
2405
|
+
*/
|
|
2406
|
+
function projectValue(source, arg, method) {
|
|
2407
|
+
if (typeof arg !== "function") return projectColumn(source, arg);
|
|
2408
|
+
const value = arg(createColumnRefs(source));
|
|
2409
|
+
if (!isExpression(value)) throw new TypeError(`The callback of ${method} must return an expression`);
|
|
2410
|
+
const type = getType(value);
|
|
2411
|
+
if (!type) throw new TypeError(`The expression of ${method} has no type to decode its values; build a sql fragment with a type, such as sql(t.integer)`);
|
|
2412
|
+
return projectExpression("value", value, type);
|
|
2413
|
+
}
|
|
2414
|
+
/**
|
|
2415
|
+
* Creates the projection of the value of `field`, the only projection of a query of values, whose `NULL` `fallback` replaces: a value that the type of the field encodes, an expression, or a query of one value.
|
|
2416
|
+
*
|
|
2417
|
+
* The projection of an aggregate keeps its call, which lowering builds, and adds the fallback after its earlier ones.
|
|
2418
|
+
* The projection of `exists` stays as it is, since `EXISTS` is never `NULL`.
|
|
2419
|
+
*/
|
|
2420
|
+
function coalesceValue(field, fallback) {
|
|
2421
|
+
const value = toFunctionOperandNode(fallback, field.type, "coalesce");
|
|
2422
|
+
if (isExistsProjection(field)) return field;
|
|
2423
|
+
if (isAggregateProjection(field)) return addFallback(field, value);
|
|
2424
|
+
return Object.freeze({
|
|
2425
|
+
key: field.key,
|
|
2426
|
+
expression: createList("coalesce(", [field.expression, value], ")"),
|
|
2427
|
+
type: field.type
|
|
2428
|
+
});
|
|
2429
|
+
}
|
|
2430
|
+
/** Creates the field of a value of a selection under `key`: an expression with a type, a query whose rows become its value, or a literal. */
|
|
2431
|
+
function selectValue(key, value) {
|
|
2432
|
+
if (value instanceof Literal) return Object.freeze({
|
|
2433
|
+
key,
|
|
2434
|
+
literal: value.literal
|
|
2435
|
+
});
|
|
2436
|
+
if (value instanceof SelectQuery) return Object.freeze({
|
|
2437
|
+
key,
|
|
2438
|
+
query: value.node,
|
|
2439
|
+
db: value.db
|
|
2440
|
+
});
|
|
2441
|
+
if (value instanceof RecordQuery) throw new TypeError(`Selected field "${key}" is a query of $select, which cannot be nested; select its fields directly instead`);
|
|
2442
|
+
if (value instanceof MutationQuery) throw new TypeError(`Selected field "${key}" is a query of insert, update, or delete, which cannot be nested`);
|
|
2443
|
+
if (value instanceof SqlQuery) throw new TypeError(`Selected field "${key}" is a query of $query, which cannot be nested`);
|
|
2444
|
+
if (!isExpression(value)) throw new TypeError(`Selected field "${key}" must be an expression, a query, or a literal`);
|
|
2445
|
+
const type = getType(value);
|
|
2446
|
+
if (!type) throw new TypeError(`Selected field "${key}" has no type to decode its values; build a sql fragment with a type, such as sql(t.integer)`);
|
|
2447
|
+
return projectExpression(key, value, type);
|
|
2448
|
+
}
|
|
2449
|
+
/** Creates the projection of a column of the table of `source` under its key. */
|
|
2450
|
+
function projectColumn(source, key) {
|
|
2451
|
+
const { type } = requireColumn(source.table, key);
|
|
2452
|
+
return Object.freeze({
|
|
2453
|
+
key,
|
|
2454
|
+
expression: createNode({
|
|
2455
|
+
kind: "column",
|
|
2456
|
+
source,
|
|
2457
|
+
key
|
|
2458
|
+
}),
|
|
2459
|
+
type
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
/** Creates the projection of an expression under `key`, decoded with `type`, the type of the expression. */
|
|
2463
|
+
function projectExpression(key, value, type) {
|
|
2464
|
+
return Object.freeze({
|
|
2465
|
+
key,
|
|
2466
|
+
expression: toNode(value),
|
|
2467
|
+
type
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
/** Adds or replaces a field by key, rejecting a key that a record cannot have. */
|
|
2471
|
+
function setProjection(projections, projection) {
|
|
2472
|
+
const { key } = projection;
|
|
2473
|
+
if (!isRecordKey(key)) throw new Error(`Field "${key}" cannot be selected: it is not a valid record key`);
|
|
2474
|
+
for (let index = 0; index < projections.length; index++) if (projections[index].key === key) {
|
|
2475
|
+
projections[index] = projection;
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
projections.push(projection);
|
|
2272
2479
|
}
|
|
2273
2480
|
// src/query/values.ts
|
|
2274
2481
|
/**
|
|
@@ -2374,6 +2581,10 @@ var MutationQuery = class MutationQuery {
|
|
|
2374
2581
|
toSQL(options) {
|
|
2375
2582
|
return compileToSQL(this, options);
|
|
2376
2583
|
}
|
|
2584
|
+
/** Rejects the query as an operand of an expression, which takes a query of one value of a table. */
|
|
2585
|
+
[operandNode]() {
|
|
2586
|
+
throw new TypeError("An expression takes a query of one value of a table, such as of get, an aggregate, or exists, not a query of insert, update, or delete");
|
|
2587
|
+
}
|
|
2377
2588
|
/** Creates a mutation of the same kind and `Db` with the node changes applied. */
|
|
2378
2589
|
derive(changes) {
|
|
2379
2590
|
return new MutationQuery(this.table, Object.freeze({
|
|
@@ -2449,138 +2660,481 @@ function checkChangedRows(node, method) {
|
|
|
2449
2660
|
const predicate = combineFilters(node.filters);
|
|
2450
2661
|
if (!node.all && predicate.kind === "boolean" && predicate.value) throw new Error(`${method} requires a filter that restricts the rows, such as of where or find; call all() to ${method} every row`);
|
|
2451
2662
|
}
|
|
2452
|
-
// src/query/
|
|
2453
|
-
/**
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2663
|
+
// src/query/order.ts
|
|
2664
|
+
/** Direction and placement of `NULL` of a sort key node, by `OrderDirection`. */
|
|
2665
|
+
const directions = /* @__PURE__ */ Object.freeze({
|
|
2666
|
+
asc: {
|
|
2667
|
+
direction: "asc",
|
|
2668
|
+
nulls: void 0
|
|
2669
|
+
},
|
|
2670
|
+
desc: {
|
|
2671
|
+
direction: "desc",
|
|
2672
|
+
nulls: void 0
|
|
2673
|
+
},
|
|
2674
|
+
"asc nulls first": {
|
|
2675
|
+
direction: "asc",
|
|
2676
|
+
nulls: "first"
|
|
2677
|
+
},
|
|
2678
|
+
"asc nulls last": {
|
|
2679
|
+
direction: "asc",
|
|
2680
|
+
nulls: "last"
|
|
2681
|
+
},
|
|
2682
|
+
"desc nulls first": {
|
|
2683
|
+
direction: "desc",
|
|
2684
|
+
nulls: "first"
|
|
2685
|
+
},
|
|
2686
|
+
"desc nulls last": {
|
|
2687
|
+
direction: "desc",
|
|
2688
|
+
nulls: "last"
|
|
2465
2689
|
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
/**
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2690
|
+
});
|
|
2691
|
+
const orderingNode = Symbol("kern-orm.ordering.node");
|
|
2692
|
+
/** Runtime class of an expression with a direction, which holds its sort key node. */
|
|
2693
|
+
var OrderingObject = class {
|
|
2694
|
+
[orderingNode];
|
|
2695
|
+
constructor(node) {
|
|
2696
|
+
this[orderingNode] = node;
|
|
2697
|
+
Object.freeze(this);
|
|
2473
2698
|
}
|
|
2699
|
+
};
|
|
2700
|
+
/** Sorts by `value`, an expression or a query of one value, in ascending order, in the callback of `orderBy`. */
|
|
2701
|
+
function asc(value) {
|
|
2702
|
+
return orderBy(value, "asc");
|
|
2703
|
+
}
|
|
2704
|
+
/** Sorts by `value`, an expression or a query of one value, in descending order, in the callback of `orderBy`. */
|
|
2705
|
+
function desc(value) {
|
|
2706
|
+
return orderBy(value, "desc");
|
|
2707
|
+
}
|
|
2708
|
+
/** Sorts by `value`, an expression or a query of one value, in `direction`, such as `"desc nulls last"`, in the callback of `orderBy`. */
|
|
2709
|
+
function orderBy(value, direction) {
|
|
2710
|
+
if (!isOperandSource(value)) throw new TypeError("A sort key must be an expression or a query of one value");
|
|
2711
|
+
return new OrderingObject(createOrderNode(value[operandNode](), direction));
|
|
2474
2712
|
}
|
|
2475
2713
|
/**
|
|
2476
|
-
* Creates the
|
|
2714
|
+
* Creates the order of `node` with the sort keys of the arguments of `orderBy` after its own.
|
|
2477
2715
|
*
|
|
2478
|
-
* `
|
|
2716
|
+
* `args` are column keys and objects of columns with directions, or a callback that receives the columns with the relations of `context` and the selected fields, and returns one sort key or an array of them.
|
|
2479
2717
|
*/
|
|
2480
|
-
function
|
|
2481
|
-
|
|
2482
|
-
const
|
|
2483
|
-
if (
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2718
|
+
function addOrder(node, context, args) {
|
|
2719
|
+
const order = node.orderBy.slice();
|
|
2720
|
+
const first = args[0];
|
|
2721
|
+
if (typeof first === "function") {
|
|
2722
|
+
const terms = first(createSelectionRefs(node.from, context), createSelectedRefs(node));
|
|
2723
|
+
if (Array.isArray(terms)) for (const term of terms) order.push(readTerm(term));
|
|
2724
|
+
else order.push(readTerm(terms));
|
|
2725
|
+
return order;
|
|
2726
|
+
}
|
|
2727
|
+
for (const arg of args) if (typeof arg === "string") order.push(createOrderNode(createColumnNode(node.from, arg), "asc"));
|
|
2728
|
+
else if (typeof arg === "object" && arg !== null && !Array.isArray(arg)) {
|
|
2729
|
+
const columns = arg;
|
|
2730
|
+
for (const key of Object.keys(columns)) order.push(createOrderNode(createColumnNode(node.from, key), columns[key]));
|
|
2731
|
+
} else throw new TypeError("orderBy takes column keys and objects of columns with directions, or a callback");
|
|
2732
|
+
return order;
|
|
2487
2733
|
}
|
|
2488
2734
|
/**
|
|
2489
|
-
* Creates the
|
|
2735
|
+
* Creates the sort key node of a sort key returned by the callback of `orderBy`.
|
|
2490
2736
|
*
|
|
2491
|
-
*
|
|
2737
|
+
* A query is converted by the method of `operandNode`, which rejects a query other than of one value.
|
|
2492
2738
|
*/
|
|
2493
|
-
function
|
|
2494
|
-
if (
|
|
2495
|
-
|
|
2496
|
-
|
|
2739
|
+
function readTerm(term) {
|
|
2740
|
+
if (term instanceof OrderingObject) return term[orderingNode];
|
|
2741
|
+
if (isOperandSource(term)) return createOrderNode(term[operandNode](), "asc");
|
|
2742
|
+
throw new TypeError("The callback of orderBy must return expressions, queries of one value, or sort keys of asc, desc, and orderBy");
|
|
2743
|
+
}
|
|
2744
|
+
/** Creates the sort key node of `expression` in `direction`, an `OrderDirection` checked at runtime. */
|
|
2745
|
+
function createOrderNode(expression, direction) {
|
|
2746
|
+
const placement = typeof direction === "string" && Object.hasOwn(directions, direction) ? directions[direction] : void 0;
|
|
2747
|
+
if (placement === void 0) throw new TypeError(`Unknown sort direction ${JSON.stringify(direction)}`);
|
|
2497
2748
|
return Object.freeze({
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2749
|
+
expression,
|
|
2750
|
+
direction: placement.direction,
|
|
2751
|
+
nulls: placement.nulls
|
|
2501
2752
|
});
|
|
2502
2753
|
}
|
|
2503
|
-
/** Creates
|
|
2504
|
-
function
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2754
|
+
/** Creates a reference to the column `key` of the table of `source`. */
|
|
2755
|
+
function createColumnNode(source, key) {
|
|
2756
|
+
requireColumn(source.table, key);
|
|
2757
|
+
return createNode({
|
|
2758
|
+
kind: "column",
|
|
2759
|
+
source,
|
|
2760
|
+
key
|
|
2509
2761
|
});
|
|
2510
|
-
if (value instanceof RecordQuery) throw new TypeError(`Selected field "${key}" is a query of $select, which cannot be nested; select its fields directly instead`);
|
|
2511
|
-
if (value instanceof MutationQuery) throw new TypeError(`Selected field "${key}" is a query of insert, update, or delete, which cannot be nested`);
|
|
2512
|
-
if (value instanceof SqlQuery) throw new TypeError(`Selected field "${key}" is a query of $query, which cannot be nested`);
|
|
2513
|
-
if (!isExpression(value)) throw new TypeError(`Selected field "${key}" must be an expression or a query`);
|
|
2514
|
-
const type = getType(value);
|
|
2515
|
-
if (!type) throw new TypeError(`Selected field "${key}" has no type to decode its values; build a sql fragment with a type, such as sql(t.integer)`);
|
|
2516
|
-
return projectExpression(key, value, type);
|
|
2517
2762
|
}
|
|
2518
|
-
/**
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2763
|
+
/**
|
|
2764
|
+
* Creates the references of the callback of `orderBy` to the fields selected by `node`.
|
|
2765
|
+
*
|
|
2766
|
+
* Each is an expression over the node of its projection, so that a sort key of it is the projection itself.
|
|
2767
|
+
* A field of an aggregate or of `exists` is an expression of its query that holds the field, by which lowering finds the projection of the field.
|
|
2768
|
+
*/
|
|
2769
|
+
function createSelectedRefs(node) {
|
|
2770
|
+
const { projections } = node;
|
|
2771
|
+
if (node.value) return toSelectedExpression(projections[0]);
|
|
2772
|
+
const refs = {};
|
|
2773
|
+
for (const projection of projections) {
|
|
2774
|
+
const { key } = projection;
|
|
2775
|
+
if (isLiteralSelection(projection)) Object.defineProperty(refs, key, {
|
|
2776
|
+
get() {
|
|
2777
|
+
throw new Error(`Field "${key}" holds a literal and cannot be sorted by`);
|
|
2778
|
+
},
|
|
2779
|
+
enumerable: true
|
|
2780
|
+
});
|
|
2781
|
+
else if (isNestedSelection(projection)) {
|
|
2782
|
+
if (isSelectedAsValue(projection.query)) refs[key] = toQueryExpression(projection);
|
|
2783
|
+
else Object.defineProperty(refs, key, {
|
|
2784
|
+
get() {
|
|
2785
|
+
throw new Error(`Field "${key}" holds nested rows and cannot be sorted by`);
|
|
2786
|
+
},
|
|
2787
|
+
enumerable: true
|
|
2788
|
+
});
|
|
2789
|
+
} else refs[key] = toSelectedExpression(projection);
|
|
2790
|
+
}
|
|
2791
|
+
return refs;
|
|
2530
2792
|
}
|
|
2531
|
-
/** Creates the
|
|
2532
|
-
function
|
|
2533
|
-
return
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2793
|
+
/** Creates an expression over the node of a projection of a query, which has a type: only lowering adds projections of nested queries. */
|
|
2794
|
+
function toSelectedExpression(projection) {
|
|
2795
|
+
return toExpression(projection.expression, projection.type);
|
|
2796
|
+
}
|
|
2797
|
+
/** Creates an expression of the value of `field`, a selected aggregate query or query of `exists`, with the type of its value. */
|
|
2798
|
+
function toQueryExpression(field) {
|
|
2799
|
+
const { query } = field;
|
|
2800
|
+
return toExpression(createNode({
|
|
2801
|
+
kind: "nestedQuery",
|
|
2802
|
+
query,
|
|
2803
|
+
db: field.db,
|
|
2804
|
+
field
|
|
2805
|
+
}), query.projections[0].type);
|
|
2806
|
+
}
|
|
2807
|
+
// src/query/pagination.ts
|
|
2808
|
+
/** Creates the parameter of the number of rows of `limit` or `offset`, named by `method`, of query `node`, rejecting a query of one row and a count that is not a non-negative safe integer. */
|
|
2809
|
+
function createPageCount(node, method, count) {
|
|
2810
|
+
if (node.rows !== "many") throw new Error(`${method} applies only to a query of many rows${createPaginationHint(node)}`);
|
|
2811
|
+
if (!Number.isSafeInteger(count) || count < 0) throw new RangeError(`${method} takes a non-negative integer, not ${count}`);
|
|
2812
|
+
return createNode({
|
|
2813
|
+
kind: "param",
|
|
2814
|
+
value: count,
|
|
2815
|
+
type: void 0
|
|
2537
2816
|
});
|
|
2538
2817
|
}
|
|
2539
|
-
/**
|
|
2540
|
-
function
|
|
2541
|
-
|
|
2542
|
-
if (
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
return;
|
|
2546
|
-
}
|
|
2547
|
-
projections.push(projection);
|
|
2818
|
+
/** Creates the hint of the error of `limit` or `offset` of query `node` of one row: the method to call them before, if any. */
|
|
2819
|
+
function createPaginationHint(node) {
|
|
2820
|
+
if (node.relation?.kind === "one") return "";
|
|
2821
|
+
if (isAggregateQuery(node)) return "; call it before count or another aggregate to aggregate the rows of a page";
|
|
2822
|
+
if (isExistsQuery(node)) return "; call it before exists to check the rows of a page";
|
|
2823
|
+
return "; call it before take or get";
|
|
2548
2824
|
}
|
|
2549
|
-
// src/query/
|
|
2825
|
+
// src/query/select.ts
|
|
2550
2826
|
/**
|
|
2551
|
-
* Immutable query of
|
|
2827
|
+
* Immutable select query of state `S`, unbound or bound to a `Db`.
|
|
2552
2828
|
*
|
|
2829
|
+
* Every method returns a query of the same kind and leaves this query unchanged.
|
|
2553
2830
|
* An unbound query is not thenable, so it can be returned from async functions; it is compiled with `toSQL` or executed by a `Db`.
|
|
2554
2831
|
* A bound query executes on its `Db` when awaited.
|
|
2555
2832
|
*/
|
|
2556
|
-
var
|
|
2833
|
+
var SelectQuery = class SelectQuery {
|
|
2834
|
+
table;
|
|
2557
2835
|
node;
|
|
2836
|
+
context;
|
|
2558
2837
|
/** `Db` that executes the query when it is awaited; `undefined` for an unbound query. */
|
|
2559
2838
|
db;
|
|
2560
2839
|
/** Executes a bound query on its `Db`; `undefined` for an unbound query, so that it is not thenable. */
|
|
2561
2840
|
then;
|
|
2562
|
-
constructor(node, db) {
|
|
2841
|
+
constructor(table, node, context, db) {
|
|
2842
|
+
this.table = table;
|
|
2563
2843
|
this.node = node;
|
|
2844
|
+
this.context = context;
|
|
2564
2845
|
this.db = db;
|
|
2565
2846
|
this.then = db === void 0 ? void 0 : executeOnAwait;
|
|
2566
2847
|
Object.freeze(this);
|
|
2567
2848
|
}
|
|
2849
|
+
where(filter) {
|
|
2850
|
+
return this.derive(this.addFilter(filter));
|
|
2851
|
+
}
|
|
2852
|
+
select(...args) {
|
|
2853
|
+
this.rejectValue("select");
|
|
2854
|
+
return this.derive({ projections: mergeProjections(this.node.projections, this.node.from, args, this.context) });
|
|
2855
|
+
}
|
|
2856
|
+
clearSelect() {
|
|
2857
|
+
this.rejectValue("clearSelect");
|
|
2858
|
+
return this.derive({ projections: [] });
|
|
2859
|
+
}
|
|
2860
|
+
take() {
|
|
2861
|
+
return this.deriveTaken({});
|
|
2862
|
+
}
|
|
2863
|
+
find(...values) {
|
|
2864
|
+
return this.takeWhere(toPrimaryKeyValues(this.table, values));
|
|
2865
|
+
}
|
|
2866
|
+
findBy(values) {
|
|
2867
|
+
return this.takeWhere(toKeyValues(this.table, values));
|
|
2868
|
+
}
|
|
2869
|
+
get(arg) {
|
|
2870
|
+
this.rejectAggregate("get");
|
|
2871
|
+
return this.deriveTaken({
|
|
2872
|
+
projections: [projectValue(this.node.from, arg, "get")],
|
|
2873
|
+
value: true,
|
|
2874
|
+
rows: toValueRows(this.node.rows)
|
|
2875
|
+
});
|
|
2876
|
+
}
|
|
2877
|
+
pluck(arg) {
|
|
2878
|
+
this.rejectAggregate("pluck");
|
|
2879
|
+
return this.derive({
|
|
2880
|
+
projections: [projectValue(this.node.from, arg, "pluck")],
|
|
2881
|
+
value: true,
|
|
2882
|
+
rows: toValueRows(this.node.rows)
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
count(arg) {
|
|
2886
|
+
if (arg !== void 0) return this.aggregateBy("count", arg);
|
|
2887
|
+
this.rejectAggregate("count");
|
|
2888
|
+
return this.deriveAggregate(countProjection);
|
|
2889
|
+
}
|
|
2890
|
+
countDistinct(arg) {
|
|
2891
|
+
return this.aggregateBy("countDistinct", arg);
|
|
2892
|
+
}
|
|
2893
|
+
min(arg) {
|
|
2894
|
+
return this.aggregateBy("min", arg);
|
|
2895
|
+
}
|
|
2896
|
+
max(arg) {
|
|
2897
|
+
return this.aggregateBy("max", arg);
|
|
2898
|
+
}
|
|
2899
|
+
sum(arg) {
|
|
2900
|
+
return this.aggregateBy("sum", arg);
|
|
2901
|
+
}
|
|
2902
|
+
avg(arg) {
|
|
2903
|
+
return this.aggregateBy("avg", arg);
|
|
2904
|
+
}
|
|
2905
|
+
every(arg) {
|
|
2906
|
+
return this.aggregateBy("every", arg);
|
|
2907
|
+
}
|
|
2908
|
+
some(arg) {
|
|
2909
|
+
return this.aggregateBy("some", arg);
|
|
2910
|
+
}
|
|
2911
|
+
stringAgg(arg, delimiter) {
|
|
2912
|
+
return this.aggregateBy("stringAgg", arg, delimiter);
|
|
2913
|
+
}
|
|
2914
|
+
exists() {
|
|
2915
|
+
this.rejectAggregate("exists");
|
|
2916
|
+
return this.deriveAggregate(getExistsProjection());
|
|
2917
|
+
}
|
|
2918
|
+
coalesce(fallback) {
|
|
2919
|
+
const { node } = this;
|
|
2920
|
+
if (!node.value) throw new Error("coalesce applies only to a query of values, such as one made by get, pluck, or an aggregate");
|
|
2921
|
+
return this.derive({ projections: [coalesceValue(node.projections[0], fallback)] });
|
|
2922
|
+
}
|
|
2923
|
+
expr() {
|
|
2924
|
+
const value = this[operandNode]();
|
|
2925
|
+
return toExpression(value, this.node.projections[0].type);
|
|
2926
|
+
}
|
|
2927
|
+
orderBy(...args) {
|
|
2928
|
+
return this.derive({ orderBy: addOrder(this.node, this.context, args) });
|
|
2929
|
+
}
|
|
2930
|
+
limit(count) {
|
|
2931
|
+
return this.derive({ limit: createPageCount(this.node, "limit", count) });
|
|
2932
|
+
}
|
|
2933
|
+
offset(count) {
|
|
2934
|
+
return this.derive({ offset: createPageCount(this.node, "offset", count) });
|
|
2935
|
+
}
|
|
2936
|
+
optional() {
|
|
2937
|
+
return this.changeOneRow("optional", "optional");
|
|
2938
|
+
}
|
|
2939
|
+
nullable() {
|
|
2940
|
+
if (this.node.value) throw new Error("nullable applies only to a query of records; use optional after get, pluck, or an aggregate");
|
|
2941
|
+
return this.changeOneRow("nullable", "nullable");
|
|
2942
|
+
}
|
|
2943
|
+
required() {
|
|
2944
|
+
return this.changeOneRow("required", "one");
|
|
2945
|
+
}
|
|
2946
|
+
insert(values) {
|
|
2947
|
+
return createInsert(this, [values], "one", "insert");
|
|
2948
|
+
}
|
|
2949
|
+
insertMany(records) {
|
|
2950
|
+
if (!Array.isArray(records)) throw new TypeError("insertMany takes an array of records");
|
|
2951
|
+
return createInsert(this, records, "many", "insertMany");
|
|
2952
|
+
}
|
|
2953
|
+
update(set) {
|
|
2954
|
+
return createUpdate(this, set);
|
|
2955
|
+
}
|
|
2956
|
+
delete() {
|
|
2957
|
+
return createDelete(this);
|
|
2958
|
+
}
|
|
2959
|
+
all() {
|
|
2960
|
+
if (this.node.rows !== "many") throw new Error("all applies only to a query of many rows");
|
|
2961
|
+
return this.derive({ all: true });
|
|
2962
|
+
}
|
|
2963
|
+
use(callback, ...args) {
|
|
2964
|
+
return requireDerived(this, callback(this, ...args), "The callback of use");
|
|
2965
|
+
}
|
|
2568
2966
|
toSQL(options) {
|
|
2569
2967
|
return compileToSQL(this, options);
|
|
2570
2968
|
}
|
|
2969
|
+
/**
|
|
2970
|
+
* Returns the node of the query as an operand of an expression, which lowering replaces with a scalar subquery of its value, or with `EXISTS` of its rows for a query of `exists`.
|
|
2971
|
+
*
|
|
2972
|
+
* It rejects a query of records or of rows `many`, which `ScalarQuery` rejects in types.
|
|
2973
|
+
*/
|
|
2974
|
+
[operandNode]() {
|
|
2975
|
+
const { node } = this;
|
|
2976
|
+
if (!node.value || node.rows === "many") throw new TypeError("An expression takes a query of one value, such as of get, an aggregate, or exists, not a query of records or of an array");
|
|
2977
|
+
return createNode({
|
|
2978
|
+
kind: "nestedQuery",
|
|
2979
|
+
query: node,
|
|
2980
|
+
db: this.db
|
|
2981
|
+
});
|
|
2982
|
+
}
|
|
2983
|
+
/**
|
|
2984
|
+
* Returns the node of the query as the values of `in`, which lowering replaces with its statement.
|
|
2985
|
+
*
|
|
2986
|
+
* It rejects a query of records, which `ValuesQuery` rejects in types.
|
|
2987
|
+
*/
|
|
2988
|
+
[valuesQueryNode]() {
|
|
2989
|
+
const { node } = this;
|
|
2990
|
+
if (!node.value) throw new TypeError("in takes a query of values, such as of pluck, not a query of records");
|
|
2991
|
+
return createNode({
|
|
2992
|
+
kind: "nestedQuery",
|
|
2993
|
+
query: node,
|
|
2994
|
+
db: this.db
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
/** Creates a query of the same kind and `Db` with the node changes applied. */
|
|
2998
|
+
derive(changes) {
|
|
2999
|
+
return new SelectQuery(this.table, Object.freeze({
|
|
3000
|
+
...this.node,
|
|
3001
|
+
...changes
|
|
3002
|
+
}), this.context, this.db);
|
|
3003
|
+
}
|
|
3004
|
+
/**
|
|
3005
|
+
* Creates a query of the same kind and `Db` with the node changes applied and then those of `take`, in one derived query.
|
|
3006
|
+
*
|
|
3007
|
+
* It adds the changes of `take` to `changes`, which must leave the limit as it is.
|
|
3008
|
+
*/
|
|
3009
|
+
deriveTaken(changes) {
|
|
3010
|
+
if ((changes.rows ?? this.node.rows) === "many") {
|
|
3011
|
+
changes.limit = limitToOne(this.node.limit);
|
|
3012
|
+
changes.rows = "one";
|
|
3013
|
+
}
|
|
3014
|
+
return this.derive(changes);
|
|
3015
|
+
}
|
|
3016
|
+
/** Creates the query of the value of `projection`, of an aggregate or of `exists` over the rows of this query, which always returns one row. */
|
|
3017
|
+
deriveAggregate(projection) {
|
|
3018
|
+
return this.derive({
|
|
3019
|
+
projections: [projection],
|
|
3020
|
+
rows: "one",
|
|
3021
|
+
value: true,
|
|
3022
|
+
guaranteed: false
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
/** Creates the query of the aggregate of `method` over `arg`, a column key or a callback that receives the columns and returns an expression; `delimiter` is the delimiter of `stringAgg`. */
|
|
3026
|
+
aggregateBy(method, arg, delimiter) {
|
|
3027
|
+
this.rejectAggregate(method);
|
|
3028
|
+
return this.deriveAggregate(projectAggregate(method, projectValue(this.node.from, arg, method), delimiter));
|
|
3029
|
+
}
|
|
3030
|
+
/** Returns the changes of the node that add `filter` with `AND`, ending the guarantee of the row as `FilteredState` does in types. */
|
|
3031
|
+
addFilter(filter) {
|
|
3032
|
+
const { node } = this;
|
|
3033
|
+
return {
|
|
3034
|
+
filters: addUserFilter(node.filters, filter, node.from, this.context),
|
|
3035
|
+
rows: toFilteredRows(node.rows, node.value, node.guaranteed),
|
|
3036
|
+
guaranteed: false
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
/** Creates the query of `find` and `findBy`: `where` of `values`, an object of column values, returning one row like `take`. */
|
|
3040
|
+
takeWhere(values) {
|
|
3041
|
+
const changes = this.addFilter(values);
|
|
3042
|
+
if (changes.rows === "many") changes.rows = "one";
|
|
3043
|
+
return this.derive(changes);
|
|
3044
|
+
}
|
|
3045
|
+
/** Creates the query of one row with rows mode `rows` that `method` returns, rejecting a query of many rows. */
|
|
3046
|
+
changeOneRow(method, rows) {
|
|
3047
|
+
if (this.node.rows === "many") throw new Error(`${method} applies only to a query of one row, such as one made by take`);
|
|
3048
|
+
return this.derive({
|
|
3049
|
+
rows,
|
|
3050
|
+
guaranteed: false
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
/** Rejects a change of the selection by `method` of a query made by `get`, `pluck`, or an aggregate method. */
|
|
3054
|
+
rejectValue(method) {
|
|
3055
|
+
if (!this.node.value) return;
|
|
3056
|
+
this.rejectAggregate(method);
|
|
3057
|
+
throw new Error(`${method} cannot be used after get or pluck; call get or pluck to replace the value`);
|
|
3058
|
+
}
|
|
3059
|
+
/** Rejects `method`, which replaces the value or the selection, on a query made by an aggregate method or `exists`. */
|
|
3060
|
+
rejectAggregate(method) {
|
|
3061
|
+
const { node } = this;
|
|
3062
|
+
if (isAggregateQuery(node)) throw new Error(`${method} cannot be used after an aggregate`);
|
|
3063
|
+
if (isExistsQuery(node)) throw new Error(`${method} cannot be used after exists`);
|
|
3064
|
+
}
|
|
2571
3065
|
};
|
|
2572
3066
|
/**
|
|
2573
|
-
*
|
|
3067
|
+
* Returns `result`, which a callback named by `subject` returned for `query`, after checking that it is `query` or a query derived from it.
|
|
2574
3068
|
*
|
|
2575
|
-
*
|
|
3069
|
+
* An ancestor of `query` also passes, since only the source occurrence is compared.
|
|
2576
3070
|
*/
|
|
2577
|
-
function
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
3071
|
+
function requireDerived(query, result, subject) {
|
|
3072
|
+
if (!(result instanceof SelectQuery) || result.node.from !== query.node.from) throw new Error(`${subject} must return the query it receives or one derived from it`);
|
|
3073
|
+
return result;
|
|
3074
|
+
}
|
|
3075
|
+
/** Binds a query to `db`, so that awaiting it executes it there. */
|
|
3076
|
+
function bindQuery(query, db) {
|
|
3077
|
+
return new SelectQuery(query.table, query.node, query.context, db);
|
|
3078
|
+
}
|
|
3079
|
+
/** Starts a query over a new occurrence of the table. */
|
|
3080
|
+
function from(table) {
|
|
3081
|
+
return startQuery(table, noContext);
|
|
3082
|
+
}
|
|
3083
|
+
/** Starts a query over a new occurrence of the table with the relations of `context`, such as a query of a table of a schema. */
|
|
3084
|
+
function startQuery(table, context) {
|
|
3085
|
+
return new SelectQuery(table, createQueryNode(Object.freeze({
|
|
3086
|
+
kind: "table",
|
|
3087
|
+
table
|
|
3088
|
+
})), context);
|
|
3089
|
+
}
|
|
3090
|
+
// src/expression/functions.ts
|
|
3091
|
+
function coalesce(first, ...rest) {
|
|
3092
|
+
return callFunction("coalesce", first, rest);
|
|
3093
|
+
}
|
|
3094
|
+
function greatest(first, ...rest) {
|
|
3095
|
+
return callFunction("greatest", first, rest);
|
|
3096
|
+
}
|
|
3097
|
+
function least(first, ...rest) {
|
|
3098
|
+
return callFunction("least", first, rest);
|
|
3099
|
+
}
|
|
3100
|
+
function caseWhen(type, branches, otherwise) {
|
|
3101
|
+
if (!isType(type)) throw new TypeError("caseWhen() accepts a type, not a column");
|
|
3102
|
+
if (branches.length === 0) return toExpression(toCastValueNode(otherwise === void 0 ? null : otherwise, type), type);
|
|
3103
|
+
const fragments = [];
|
|
3104
|
+
const values = [];
|
|
3105
|
+
for (const branch of branches) {
|
|
3106
|
+
fragments.push(values.length === 0 ? "CASE WHEN " : " WHEN ", " THEN ");
|
|
3107
|
+
values.push(toCaseConditionNode(branch[0]), toCastValueNode(branch[1], type));
|
|
3108
|
+
}
|
|
3109
|
+
if (otherwise !== void 0) {
|
|
3110
|
+
fragments.push(" ELSE ");
|
|
3111
|
+
values.push(toCastValueNode(otherwise, type));
|
|
3112
|
+
}
|
|
3113
|
+
fragments.push(" END");
|
|
3114
|
+
return toExpression(createNode({
|
|
3115
|
+
kind: "raw",
|
|
3116
|
+
fragments,
|
|
3117
|
+
values
|
|
3118
|
+
}), type);
|
|
3119
|
+
}
|
|
3120
|
+
/** Converts a condition of `caseWhen` into a node, rejecting a value other than an expression or a query, such as an object of column values, which has no table here. */
|
|
3121
|
+
function toCaseConditionNode(condition) {
|
|
3122
|
+
if (!isOperandSource(condition)) throw new TypeError("A condition of caseWhen must be an expression or a query of one value");
|
|
3123
|
+
return condition[operandNode]();
|
|
3124
|
+
}
|
|
3125
|
+
/** Creates the expression of the call of the SQL function `name`, of the type of its first operand. */
|
|
3126
|
+
function callFunction(name, first, rest) {
|
|
3127
|
+
const expression = toTypedExpression(first, name);
|
|
3128
|
+
const type = getType(expression);
|
|
3129
|
+
const operands = [toNode(expression)];
|
|
3130
|
+
for (const value of rest) operands.push(toFunctionOperandNode(value, type, name));
|
|
3131
|
+
return toExpression(createList(`${name}(`, operands, ")"), type);
|
|
3132
|
+
}
|
|
3133
|
+
/** Converts the first operand of the function `name` into an expression with a type, rejecting any other value. */
|
|
3134
|
+
function toTypedExpression(value, name) {
|
|
3135
|
+
const expression = value instanceof SelectQuery ? value.expr() : value;
|
|
3136
|
+
if (!isExpression(expression) || getType(expression) === void 0) throw new TypeError(`The first operand of ${name} must be a query of one value of a table or an expression with a type to decode the result; build a sql fragment with a type, such as sql(t.integer)`);
|
|
3137
|
+
return expression;
|
|
2584
3138
|
}
|
|
2585
3139
|
// src/query/relations.ts
|
|
2586
3140
|
/** Creates the contexts of the queries of the tables under `keys` in a schema of relations `relations`, by table key. */
|
|
@@ -2699,6 +3253,26 @@ function sql(first, ...values) {
|
|
|
2699
3253
|
if (!isType(first)) throw new TypeError("sql() accepts a type, not a column");
|
|
2700
3254
|
return (strings, ...values) => createFragment(strings, values, first);
|
|
2701
3255
|
}
|
|
3256
|
+
/**
|
|
3257
|
+
* Creates a SQL fragment of a quoted name, such as of a table or a column known only at runtime, for the templates of `sql`, of `$query` and `$script` of a `Db`, and of `query` and `script` of a `Database`.
|
|
3258
|
+
*
|
|
3259
|
+
* Several names make a qualified name, such as `identifier(schema, table)` for `"schema"."table"`.
|
|
3260
|
+
* The fragment has no parameters, so a script takes it.
|
|
3261
|
+
* The name is not qualified by the PostgreSQL schema of the statement.
|
|
3262
|
+
*/
|
|
3263
|
+
function identifier(...names) {
|
|
3264
|
+
let text = "";
|
|
3265
|
+
for (const name of names) {
|
|
3266
|
+
if (typeof name !== "string" || name === "") throw new TypeError("identifier() takes names as non-empty strings");
|
|
3267
|
+
text += (text === "" ? "" : ".") + quoteIdentifier(name);
|
|
3268
|
+
}
|
|
3269
|
+
if (text === "") throw new TypeError("identifier() takes at least one name");
|
|
3270
|
+
return toExpression(createNode({
|
|
3271
|
+
kind: "raw",
|
|
3272
|
+
fragments: [text],
|
|
3273
|
+
values: []
|
|
3274
|
+
}));
|
|
3275
|
+
}
|
|
2702
3276
|
function param(type, value) {
|
|
2703
3277
|
if (!isType(type) && !isExpression(type)) throw new TypeError("param() accepts a type or an expression, not a column");
|
|
2704
3278
|
if (isExpression(type)) {
|
|
@@ -2706,12 +3280,7 @@ function param(type, value) {
|
|
|
2706
3280
|
const valueType = getType(type);
|
|
2707
3281
|
return toExpression(toValueNode(value, valueType), valueType);
|
|
2708
3282
|
}
|
|
2709
|
-
|
|
2710
|
-
return toExpression(node.kind === "param" ? createNode({
|
|
2711
|
-
kind: "cast",
|
|
2712
|
-
operand: node,
|
|
2713
|
-
type
|
|
2714
|
-
}) : node, type);
|
|
3283
|
+
return toExpression(toCastValueNode(value, type), type);
|
|
2715
3284
|
}
|
|
2716
3285
|
function isTemplateStrings(value) {
|
|
2717
3286
|
return Array.isArray(value);
|
|
@@ -2720,9 +3289,12 @@ function createFragment(strings, values, type) {
|
|
|
2720
3289
|
return toExpression(toRawSqlNode(strings, values), type);
|
|
2721
3290
|
}
|
|
2722
3291
|
/**
|
|
2723
|
-
* Creates the node of the SQL of a template
|
|
3292
|
+
* Creates the node of the SQL of a template, whose interpolations stay structured nodes.
|
|
2724
3293
|
*
|
|
2725
|
-
*
|
|
3294
|
+
* An interpolated expression keeps its node, and a query of a table without clauses becomes the name of the table, qualified by the PostgreSQL schema of the statement.
|
|
3295
|
+
* Another query of a table becomes a query in an expression, which lowering replaces with its SQL.
|
|
3296
|
+
* Other values become query parameters.
|
|
3297
|
+
* It rejects the other queries, which a parameter would encode as text.
|
|
2726
3298
|
*/
|
|
2727
3299
|
function toRawSqlNode(strings, values) {
|
|
2728
3300
|
const nodes = [];
|
|
@@ -2736,14 +3308,18 @@ function toRawSqlNode(strings, values) {
|
|
|
2736
3308
|
/** Returns the node of a value interpolated into SQL, as `toRawSqlNode` takes it. */
|
|
2737
3309
|
function toInterpolatedNode(value) {
|
|
2738
3310
|
if (value instanceof SelectQuery) {
|
|
2739
|
-
|
|
2740
|
-
return createNode({
|
|
3311
|
+
const { node } = value;
|
|
3312
|
+
return isTableQuery(node) ? createNode({
|
|
2741
3313
|
kind: "tableName",
|
|
2742
3314
|
table: value.table,
|
|
2743
3315
|
db: value.db
|
|
3316
|
+
}) : createNode({
|
|
3317
|
+
kind: "nestedQuery",
|
|
3318
|
+
query: node,
|
|
3319
|
+
db: value.db
|
|
2744
3320
|
});
|
|
2745
3321
|
}
|
|
2746
|
-
if (value instanceof RecordQuery || value instanceof MutationQuery || value instanceof SqlQuery) throw new TypeError("sql takes a query of a table
|
|
3322
|
+
if (value instanceof RecordQuery || value instanceof MutationQuery || value instanceof SqlQuery) throw new TypeError("sql takes a query of a table, such as db.form or a query of its records, not a query of $select, a mutation, or a query of SQL text");
|
|
2747
3323
|
return toValueNode(value);
|
|
2748
3324
|
}
|
|
2749
3325
|
// src/execution/db.ts
|
|
@@ -2777,10 +3353,13 @@ function bindSchema(schema, database, options = {}) {
|
|
|
2777
3353
|
async $script(script, ...values) {
|
|
2778
3354
|
if (typeof script === "string") return database.script(script);
|
|
2779
3355
|
const { text, parameters } = compile(startSqlQuery(toRawSqlNode(script, values), db));
|
|
2780
|
-
if (parameters.length > 0) throw new Error("A script
|
|
3356
|
+
if (parameters.length > 0) throw new Error("A script has no parameters, so it takes no values, also in the queries and sql fragments that it interpolates");
|
|
2781
3357
|
return database.script(text);
|
|
2782
3358
|
},
|
|
2783
|
-
$transaction: (callback, options) => database.transaction(callback, options)
|
|
3359
|
+
$transaction: (callback, options) => database.transaction(callback, options),
|
|
3360
|
+
$ensureTransaction: (callback) => database.ensureTransaction(callback),
|
|
3361
|
+
$isInTransaction: () => database.isInTransaction(),
|
|
3362
|
+
$lock: (key) => database.lock(key)
|
|
2784
3363
|
};
|
|
2785
3364
|
const contexts = createQueryContexts(Object.keys(schema.$tables), relations);
|
|
2786
3365
|
const db = defineEntries(methods, schema.$tables, (key) => bindQuery(startQuery(schema.$tables[key], contexts[key]), db));
|
|
@@ -2851,6 +3430,27 @@ var TransactionCoordinator = class {
|
|
|
2851
3430
|
return this.test ? Promise.reject(new Error(describeOutsideTest("start a transaction"))) : this.#runRoot(callback, true, void 0, settings);
|
|
2852
3431
|
}
|
|
2853
3432
|
/**
|
|
3433
|
+
* Runs `callback` in the current transaction, or in a new transaction outside of one.
|
|
3434
|
+
*
|
|
3435
|
+
* Joining starts no savepoint, so a rejection of `callback` does not undo its changes.
|
|
3436
|
+
* It rejects without calling `callback` when the current level accepts no statements, as a nested transaction would.
|
|
3437
|
+
*/
|
|
3438
|
+
async ensureTransaction(callback) {
|
|
3439
|
+
const level = this.#storage.getStore();
|
|
3440
|
+
if (!level?.inTransaction) return this.transaction(callback);
|
|
3441
|
+
const unavailable = createUnavailableError(level, "join the transaction");
|
|
3442
|
+
if (unavailable) throw unavailable;
|
|
3443
|
+
return callback();
|
|
3444
|
+
}
|
|
3445
|
+
/**
|
|
3446
|
+
* Tells whether the current async context runs in a transaction of `transaction`.
|
|
3447
|
+
*
|
|
3448
|
+
* A test transaction and the seed of a test fixture are not transactions, unless they run inside one, so that code under test behaves as in production.
|
|
3449
|
+
*/
|
|
3450
|
+
isInTransaction() {
|
|
3451
|
+
return this.#storage.getStore()?.inTransaction ?? false;
|
|
3452
|
+
}
|
|
3453
|
+
/**
|
|
2854
3454
|
* Runs `callback` in a transaction that always rolls back, on a coordinator created for tests.
|
|
2855
3455
|
*
|
|
2856
3456
|
* Inside a transaction, it runs `callback` in a savepoint nested in it that always rolls back.
|
|
@@ -3017,7 +3617,7 @@ var TransactionCoordinator = class {
|
|
|
3017
3617
|
*/
|
|
3018
3618
|
async #seedFixture(fixture, seed) {
|
|
3019
3619
|
try {
|
|
3020
|
-
return await this.#run(await this.#startNested(fixture), seed, true);
|
|
3620
|
+
return await this.#run(await this.#startNested(fixture, false), seed, true);
|
|
3021
3621
|
} catch (error) {
|
|
3022
3622
|
return { rejection: { error } };
|
|
3023
3623
|
}
|
|
@@ -3069,7 +3669,7 @@ var TransactionCoordinator = class {
|
|
|
3069
3669
|
} catch (error) {
|
|
3070
3670
|
throw (await releaseConnection(connection, { error }, { error })).error;
|
|
3071
3671
|
}
|
|
3072
|
-
return createLevel(connection, void 0);
|
|
3672
|
+
return createLevel(connection, void 0, false);
|
|
3073
3673
|
}
|
|
3074
3674
|
/**
|
|
3075
3675
|
* Starts a nested level of a test fixture, preempting its active one.
|
|
@@ -3085,12 +3685,12 @@ var TransactionCoordinator = class {
|
|
|
3085
3685
|
const unavailable = createUnavailableError(fixture, "start a level of the test fixture");
|
|
3086
3686
|
if (unavailable) throw suppressAbortReason(unavailable, signal);
|
|
3087
3687
|
signal?.throwIfAborted();
|
|
3088
|
-
return this.#startNested(fixture, signal);
|
|
3688
|
+
return this.#startNested(fixture, false, signal);
|
|
3089
3689
|
}
|
|
3090
3690
|
async #runRoot(callback, commits, signal, settings) {
|
|
3091
3691
|
const connection = await (signal ? acquireUnlessAborted(this.driver, signal) : this.driver.acquire());
|
|
3092
3692
|
if (signal?.aborted) throw (await releaseConnection(connection, void 0, { error: signal.reason })).error;
|
|
3093
|
-
const level = createLevel(connection, void 0);
|
|
3693
|
+
const level = createLevel(connection, void 0, commits);
|
|
3094
3694
|
let outcome;
|
|
3095
3695
|
try {
|
|
3096
3696
|
await connection.execute(beginStatement);
|
|
@@ -3114,16 +3714,17 @@ var TransactionCoordinator = class {
|
|
|
3114
3714
|
async #runNested(parent, callback, commits, signal, settings) {
|
|
3115
3715
|
const unavailable = createUnavailableError(parent, "start a nested transaction");
|
|
3116
3716
|
if (unavailable) throw unavailable;
|
|
3117
|
-
return this.#runStarted(await this.#startNested(parent, signal, settings), callback, commits, signal);
|
|
3717
|
+
return this.#runStarted(await this.#startNested(parent, commits, signal, settings), callback, commits, signal);
|
|
3118
3718
|
}
|
|
3119
3719
|
/**
|
|
3120
3720
|
* Reserves a nested level of `parent`, sends its `SAVEPOINT`, and applies its `settings`, rejecting when one of them fails.
|
|
3121
3721
|
*
|
|
3722
|
+
* `transaction` tells whether the level is a transaction of `transaction` rather than a level of the test helpers.
|
|
3122
3723
|
* A level whose settings fail rolls back to its savepoint first.
|
|
3123
3724
|
* The failure suppresses the reason of `signal` when the signal aborted before it.
|
|
3124
3725
|
*/
|
|
3125
|
-
async #startNested(parent, signal, settings) {
|
|
3126
|
-
const level = createLevel(parent.connection, parent);
|
|
3726
|
+
async #startNested(parent, transaction, signal, settings) {
|
|
3727
|
+
const level = createLevel(parent.connection, parent, transaction);
|
|
3127
3728
|
parent.child = level;
|
|
3128
3729
|
try {
|
|
3129
3730
|
await level.connection.execute(createStatement(`SAVEPOINT ${getSavepointName(level)}`));
|
|
@@ -3210,11 +3811,12 @@ var TransactionCoordinator = class {
|
|
|
3210
3811
|
const beginStatement = createStatement("BEGIN");
|
|
3211
3812
|
const commitStatement = createStatement("COMMIT");
|
|
3212
3813
|
const rollbackStatement = createStatement("ROLLBACK");
|
|
3213
|
-
function createLevel(connection, parent) {
|
|
3814
|
+
function createLevel(connection, parent, transaction) {
|
|
3214
3815
|
return {
|
|
3215
3816
|
connection,
|
|
3216
3817
|
parent,
|
|
3217
3818
|
depth: parent ? parent.depth + 1 : 0,
|
|
3819
|
+
inTransaction: transaction || (parent?.inTransaction ?? false),
|
|
3218
3820
|
state: "starting",
|
|
3219
3821
|
child: void 0,
|
|
3220
3822
|
rollbackOnly: void 0,
|
|
@@ -3410,13 +4012,23 @@ function createDatabase({ driver, test }) {
|
|
|
3410
4012
|
script: (script, ...values) => typeof script === "string" ? coordinator.executeScript(script) : getTablelessDb().$script(script, ...values),
|
|
3411
4013
|
execute: (statement) => coordinator.execute(statement),
|
|
3412
4014
|
transaction: (callback, options) => coordinator.transaction(callback, options),
|
|
4015
|
+
ensureTransaction: (callback) => coordinator.ensureTransaction(callback),
|
|
4016
|
+
isInTransaction: () => coordinator.isInTransaction(),
|
|
4017
|
+
async lock(key) {
|
|
4018
|
+
if (!coordinator.isInTransaction()) throw new Error("Cannot take an advisory lock outside a transaction, where it would end with its statement; a test transaction alone does not count");
|
|
4019
|
+
await coordinator.execute(createLockStatement(key));
|
|
4020
|
+
},
|
|
3413
4021
|
close: () => coordinator.close(),
|
|
3414
4022
|
[Symbol.asyncDispose]: () => coordinator.close()
|
|
3415
4023
|
};
|
|
3416
4024
|
return database;
|
|
3417
4025
|
}
|
|
4026
|
+
/** Creates the statement that takes a transaction-level advisory lock on a 64-bit hash of `key`. */
|
|
4027
|
+
function createLockStatement(key) {
|
|
4028
|
+
return createStatement("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [key]);
|
|
4029
|
+
}
|
|
3418
4030
|
// src/query/helper.ts
|
|
3419
4031
|
function defineHelper(_table, helper) {
|
|
3420
4032
|
return (query, ...args) => requireDerived(query, helper(query, ...args), "The callback of a helper");
|
|
3421
4033
|
}
|
|
3422
|
-
export { NotFoundError, RequiredRelationError, ResultDecodeError, and, asc, compare, createDatabase, createStatement, defineFilter, defineGenericOperator, defineHelper, defineSchema, defineTable, defineType, desc, equalityOperators, from, many, not, one, or, orderBy, orderingOperators, param, sql, textOperators };
|
|
4034
|
+
export { FALSE, NotFoundError, RequiredRelationError, ResultDecodeError, TRUE, and, asc, caseWhen, coalesce, compare, createDatabase, createStatement, defineFilter, defineGenericOperator, defineHelper, defineSchema, defineTable, defineType, desc, equalityOperators, from, greatest, identifier, least, literal, many, not, one, or, orderBy, orderingOperators, param, sql, textOperators };
|