kern-orm 0.4.0 → 0.5.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/database-BJ0hNYrF.mjs +3968 -0
- package/dist/{index--0tyG7sU.d.mts → index-Cag6_erK.d.mts} +665 -550
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +15 -3972
- package/dist/testing.d.mts +28 -2
- package/dist/testing.mjs +521 -1
- package/dist/types-D9LtZ9W-.mjs +248 -0
- package/dist/types.d.mts +49 -49
- package/dist/types.mjs +2 -247
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -1,3092 +1,10 @@
|
|
|
1
|
-
import { B as trueNode, C as toNode, D as isType, E as defineType, F as createNode,
|
|
2
|
-
import {
|
|
3
|
-
// src/ast/boolean.ts
|
|
4
|
-
/** Combines predicate nodes with `AND` or `OR`, flattening nested groups of the same kind. */
|
|
5
|
-
function combineNodes(kind, operands) {
|
|
6
|
-
const neutral = kind === "and";
|
|
7
|
-
const flat = [];
|
|
8
|
-
for (const operand of operands) if (operand.kind === "boolean") {
|
|
9
|
-
if (operand.value !== neutral) return operand;
|
|
10
|
-
} else if (operand.kind === kind) flat.push(...operand.operands);
|
|
11
|
-
else flat.push(operand);
|
|
12
|
-
if (flat.length === 0) return neutral ? trueNode : falseNode;
|
|
13
|
-
return flat.length === 1 ? flat[0] : createNode({
|
|
14
|
-
kind,
|
|
15
|
-
operands: flat
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
/** Negates a predicate node, simplifying constants and double negation. */
|
|
19
|
-
function negateNode(operand) {
|
|
20
|
-
if (operand.kind === "boolean") return operand.value ? falseNode : trueNode;
|
|
21
|
-
if (operand.kind === "not") return operand.operand;
|
|
22
|
-
return createNode({
|
|
23
|
-
kind: "not",
|
|
24
|
-
operand
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
/** Combines the filters of a query into its effective predicate, `relation AND scope AND user`, which is `TRUE` when they restrict no row. */
|
|
28
|
-
function combineFilters(filters) {
|
|
29
|
-
const operands = [];
|
|
30
|
-
if (filters.relation !== void 0) operands.push(filters.relation);
|
|
31
|
-
if (filters.scope !== void 0) operands.push(filters.scope);
|
|
32
|
-
if (filters.user !== void 0) operands.push(filters.user);
|
|
33
|
-
return combineNodes("and", operands);
|
|
34
|
-
}
|
|
35
|
-
// src/expression/predicates.ts
|
|
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
|
-
function toConditionNode(operand) {
|
|
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({
|
|
41
|
-
kind: "values",
|
|
42
|
-
values: operand
|
|
43
|
-
});
|
|
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
|
-
}
|
|
51
|
-
function and(...operands) {
|
|
52
|
-
return combineConditions("and", operands);
|
|
53
|
-
}
|
|
54
|
-
function or(...operands) {
|
|
55
|
-
return combineConditions("or", operands);
|
|
56
|
-
}
|
|
57
|
-
function not(operand) {
|
|
58
|
-
return toBooleanExpression(negateNode(toConditionNode(operand)));
|
|
59
|
-
}
|
|
60
|
-
function combineConditions(kind, operands) {
|
|
61
|
-
const nodes = [];
|
|
62
|
-
for (const operand of operands) nodes.push(toConditionNode(operand));
|
|
63
|
-
return toBooleanExpression(combineNodes(kind, nodes));
|
|
64
|
-
}
|
|
1
|
+
import { B as trueNode, C as toNode, D as isType, E as defineType, F as createNode, L as falseNode, N as defineGenericOperator, P as createList, S as toFunctionOperandNode, T as getBooleanType, _ as isOperandSource, b as toCastValueNode, c as textOperators, d as orderingOperators, f as compare, g as isExpression, h as getType, k as equalityOperators, v as operandNode, x as toExpression, y as toBooleanExpression } from "./text-BTFErHIi.mjs";
|
|
2
|
+
import { C as not, S as isColumnValues, T as combineNodes, _ as NotFoundError, a as defineSchema, b as literal, c as requireDerived, d as orderBy, f as many, g as defineTable, h as matchColumn, i as sql, l as asc, m as createStatement, n as identifier, o as SelectQuery, p as one, r as param, s as from, t as createDatabase, u as desc, v as RequiredRelationError, w as or, x as and, y as ResultDecodeError } from "./database-BJ0hNYrF.mjs";
|
|
65
3
|
// src/expression/constants.ts
|
|
66
4
|
/** Condition that always holds, such as for a branch of `and` built in code: `and(check, isAdmin ? TRUE : other)`. */
|
|
67
5
|
const TRUE = /* @__PURE__ */ toExpression(trueNode, /* @__PURE__ */ getBooleanType());
|
|
68
6
|
/** Condition that never holds, such as for a branch of `or` built in code: `or(check, isAdmin ? other : FALSE)`. */
|
|
69
7
|
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
|
-
}
|
|
303
|
-
// src/result/result.ts
|
|
304
|
-
/** Error thrown when a query of one row finds no row, executed or nested, unless a required relation guarantees the row. */
|
|
305
|
-
var NotFoundError = class extends Error {
|
|
306
|
-
name = "NotFoundError";
|
|
307
|
-
/** Path of the missing nested rows from the result field that holds them, such as `user` or `fields[2].form`, after the index of the row of an executed query of rows `many`, such as `[2].form`; `undefined` when the executed query itself finds no row. */
|
|
308
|
-
path;
|
|
309
|
-
constructor(message, path) {
|
|
310
|
-
super(message);
|
|
311
|
-
this.path = path;
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
/**
|
|
315
|
-
* Error thrown when a required relation, such as `field.form`, has no record for a row.
|
|
316
|
-
*
|
|
317
|
-
* A required relation promises the record, so its absence means that the database breaks the promise, such as without a foreign key, or that the relation should not be required.
|
|
318
|
-
*/
|
|
319
|
-
var RequiredRelationError = class extends Error {
|
|
320
|
-
relation;
|
|
321
|
-
path;
|
|
322
|
-
name = "RequiredRelationError";
|
|
323
|
-
constructor(relation, path) {
|
|
324
|
-
super(`Required relation "${relation}" has no record for result field "${path}"`);
|
|
325
|
-
this.relation = relation;
|
|
326
|
-
this.path = path;
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
/** Error thrown when a row does not match the result fields or a value fails to decode; the failure of the value is its cause. */
|
|
330
|
-
var ResultDecodeError = class extends Error {
|
|
331
|
-
path;
|
|
332
|
-
name = "ResultDecodeError";
|
|
333
|
-
constructor(message, path, options) {
|
|
334
|
-
super(message, options);
|
|
335
|
-
this.path = path;
|
|
336
|
-
}
|
|
337
|
-
};
|
|
338
|
-
/** Tells whether `key` can be a key of a decoded record, such as a column key: assigning `__proto__` would change the prototype of the record instead. */
|
|
339
|
-
function isRecordKey(key) {
|
|
340
|
-
return key !== "__proto__";
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Decodes the result of a statement by its plan: its rows, as `decodeResult` does, or the number of the rows of its command tag, such as of the rows that a mutation changes.
|
|
344
|
-
*
|
|
345
|
-
* A statement of one row that returns its number or a record without fields rejects as a query of one row does: with `NotFoundError` without a row, unless it is optional or nullable, and with several rows.
|
|
346
|
-
*/
|
|
347
|
-
function decodeStatement(plan, result) {
|
|
348
|
-
const { counted } = plan;
|
|
349
|
-
if (counted === void 0) return decodeResult(plan, plan.named ? selectColumns(plan, result) : result.rows);
|
|
350
|
-
const { count } = result;
|
|
351
|
-
if (plan.rows !== "many") {
|
|
352
|
-
if (count > 1) throw new Error(plan.named ? `Query of one row reported ${count} rows` : `Query of one row changed ${count} rows`);
|
|
353
|
-
if (count === 0) {
|
|
354
|
-
if (plan.rows === "one") throw new NotFoundError(plan.named ? "Query reported no rows" : "Query found no row to change");
|
|
355
|
-
if (counted === "records") return decodeMissing(plan);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
if (counted === "number") return count;
|
|
359
|
-
if (plan.rows !== "many") return createRecord(plan);
|
|
360
|
-
const records = [];
|
|
361
|
-
for (let index = 0; index < count; index++) records.push(createRecord(plan));
|
|
362
|
-
return records;
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Returns the rows of `result` with the values of the fields of `plan` in their order, read from the result columns of their keys: a value from the only column.
|
|
366
|
-
*
|
|
367
|
-
* It rejects a missing column or one that the result has twice, and other columns than one for a value; it ignores the other columns of records.
|
|
368
|
-
*/
|
|
369
|
-
function selectColumns(plan, result) {
|
|
370
|
-
const { columns, rows } = result;
|
|
371
|
-
if (plan.value) {
|
|
372
|
-
if (columns.length !== 1) throw new ResultDecodeError(`Statement returned ${columns.length} columns for one value`, void 0);
|
|
373
|
-
return rows;
|
|
374
|
-
}
|
|
375
|
-
const { fields } = plan;
|
|
376
|
-
const indexes = [];
|
|
377
|
-
let ordered = fields.length === columns.length;
|
|
378
|
-
for (let index = 0; index < fields.length; index++) {
|
|
379
|
-
const { key } = fields[index];
|
|
380
|
-
const column = columns.indexOf(key);
|
|
381
|
-
if (column === -1) throw new ResultDecodeError(`Statement returned no column "${key}"`, void 0);
|
|
382
|
-
if (columns.indexOf(key, column + 1) !== -1) throw new ResultDecodeError(`Statement returned column "${key}" more than once`, void 0);
|
|
383
|
-
indexes.push(column);
|
|
384
|
-
ordered &&= column === index;
|
|
385
|
-
}
|
|
386
|
-
if (ordered) return rows;
|
|
387
|
-
const selected = [];
|
|
388
|
-
for (let row = 0; row < rows.length; row++) {
|
|
389
|
-
const values = [];
|
|
390
|
-
for (let index = 0; index < indexes.length; index++) values.push(rows[row][indexes[index]]);
|
|
391
|
-
selected.push(values);
|
|
392
|
-
}
|
|
393
|
-
return selected;
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* Decodes driver rows into the result of a query by its result plan.
|
|
397
|
-
*
|
|
398
|
-
* It rejects a row whose values do not match the result fields, and names the path of the value that fails to decode from its result field, such as `fields[2].label`, after the index of the row of rows `many`, such as `[2].fields[2].label`.
|
|
399
|
-
*/
|
|
400
|
-
function decodeResult(plan, rows) {
|
|
401
|
-
if (plan.rows === "many") {
|
|
402
|
-
const results = [];
|
|
403
|
-
let index = 0;
|
|
404
|
-
try {
|
|
405
|
-
for (; index < rows.length; index++) results.push(decodeRow(plan, rows[index], false));
|
|
406
|
-
} catch (error) {
|
|
407
|
-
throw createRowError(`[${index}].`, error);
|
|
408
|
-
}
|
|
409
|
-
return results;
|
|
410
|
-
}
|
|
411
|
-
if (rows.length > 1) throw new Error(`Query of one row returned ${rows.length} rows`);
|
|
412
|
-
const row = rows[0];
|
|
413
|
-
if (row === void 0) {
|
|
414
|
-
if (plan.rows === "one") throw new NotFoundError("Query returned no rows");
|
|
415
|
-
return decodeMissing(plan);
|
|
416
|
-
}
|
|
417
|
-
try {
|
|
418
|
-
return decodeRow(plan, row, false);
|
|
419
|
-
} catch (error) {
|
|
420
|
-
throw createRowError("", error);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
/** Returns the result of a query of rows `nullable` or `optional` without a row: `null` or `undefined`. */
|
|
424
|
-
function decodeMissing(plan) {
|
|
425
|
-
return plan.rows === "nullable" ? null : void 0;
|
|
426
|
-
}
|
|
427
|
-
/** Missing row of nested rows `one`, which the query turns into its error once the path is known. */
|
|
428
|
-
var MissingRow = class extends Error {
|
|
429
|
-
guaranteedBy;
|
|
430
|
-
constructor(guaranteedBy) {
|
|
431
|
-
super();
|
|
432
|
-
this.guaranteedBy = guaranteedBy;
|
|
433
|
-
}
|
|
434
|
-
};
|
|
435
|
-
/**
|
|
436
|
-
* Failure inside a row, with the path to it, such as `[2].label` from the field of nested rows or `fields[2].label` from a row of the driver: a missing row or a value that fails to decode.
|
|
437
|
-
*
|
|
438
|
-
* Every enclosing level adds its own part of the path, and the query turns it into its error.
|
|
439
|
-
*/
|
|
440
|
-
var NestedFailure = class extends Error {
|
|
441
|
-
path;
|
|
442
|
-
cause;
|
|
443
|
-
constructor(path, cause) {
|
|
444
|
-
super();
|
|
445
|
-
this.path = path;
|
|
446
|
-
this.cause = cause;
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
/** Adds `segment` before the path of a failure inside a row. */
|
|
450
|
-
function prefixPath(segment, error) {
|
|
451
|
-
return error instanceof NestedFailure ? new NestedFailure(segment + error.path, error.cause) : new NestedFailure(segment, error);
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Creates the error of a query from the failure of a row of the driver, adding `prefix`, such as the index of the row, before its path: `NotFoundError` or `RequiredRelationError` for a missing row of nested rows, and `ResultDecodeError` for a value that fails to decode.
|
|
455
|
-
*
|
|
456
|
-
* A missing row keeps its class wherever it is nested, so it is never reported as a decoding failure.
|
|
457
|
-
* A row of another width than the result fields is already the error of the query.
|
|
458
|
-
*/
|
|
459
|
-
function createRowError(prefix, error) {
|
|
460
|
-
if (!(error instanceof NestedFailure)) return error;
|
|
461
|
-
const path = prefix + error.path;
|
|
462
|
-
const { cause } = error;
|
|
463
|
-
if (cause instanceof MissingRow) return cause.guaranteedBy === void 0 ? new NotFoundError(`Nested query of result field "${path}" returned no rows`, path) : new RequiredRelationError(cause.guaranteedBy, path);
|
|
464
|
-
return new ResultDecodeError(`Cannot decode result field "${path}"${cause instanceof Error ? `: ${cause.message}` : ""}`, path, { cause });
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Decodes a row into a record keyed by the result fields, or into the value of its only field.
|
|
468
|
-
*
|
|
469
|
-
* A row of the driver holds text and `null`; a packed row of nested rows holds parsed JSON.
|
|
470
|
-
*/
|
|
471
|
-
function decodeRow(plan, row, packed) {
|
|
472
|
-
const { fields } = plan;
|
|
473
|
-
if (row.length !== fields.length) {
|
|
474
|
-
if (packed) throw new Error(`Nested row of ${row.length} values for ${fields.length} result fields`);
|
|
475
|
-
throw new ResultDecodeError(`Driver returned a row of ${row.length} values for ${fields.length} result fields`, void 0);
|
|
476
|
-
}
|
|
477
|
-
let index = 0;
|
|
478
|
-
try {
|
|
479
|
-
if (plan.value) return packed ? decodePackedValue(fields[0], row[0]) : decodeDriverValue(fields[0], row[0]);
|
|
480
|
-
const record = createRecord(plan);
|
|
481
|
-
for (; index < fields.length; index++) {
|
|
482
|
-
const field = fields[index];
|
|
483
|
-
record[field.key] = packed ? decodePackedValue(field, row[index]) : decodeDriverValue(field, row[index]);
|
|
484
|
-
}
|
|
485
|
-
return record;
|
|
486
|
-
} catch (error) {
|
|
487
|
-
const { key } = fields[index];
|
|
488
|
-
throw prefixPath(packed ? `.${key}` : key, error);
|
|
489
|
-
}
|
|
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
|
-
}
|
|
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. */
|
|
502
|
-
function decodeDriverValue(field, value) {
|
|
503
|
-
if (value === void 0) throw new Error("Driver returned no value");
|
|
504
|
-
if (field.nested !== void 0) return decodeNested(field.nested, value === null ? null : JSON.parse(value));
|
|
505
|
-
return value === null ? null : field.type.decode(value);
|
|
506
|
-
}
|
|
507
|
-
/** Decodes the value of a field in a packed row: a scalar from its text, keeping JSON `null` as `null`, or nested rows from parsed JSON. */
|
|
508
|
-
function decodePackedValue(field, value) {
|
|
509
|
-
if (field.nested !== void 0) return decodeNested(field.nested, value);
|
|
510
|
-
if (value === null) return null;
|
|
511
|
-
if (typeof value !== "string") throw new TypeError(`Nested value must be text, not ${describeJson(value)}`);
|
|
512
|
-
return field.type.decode(value);
|
|
513
|
-
}
|
|
514
|
-
/** Decodes the rows of a nested query from JSON: an array of packed rows for rows `many`, and a packed row or `null` otherwise. */
|
|
515
|
-
function decodeNested(plan, json) {
|
|
516
|
-
if (plan.rows === "many") {
|
|
517
|
-
if (!Array.isArray(json)) throw new TypeError(`Nested rows must be an array, not ${describeJson(json)}`);
|
|
518
|
-
const results = [];
|
|
519
|
-
let index = 0;
|
|
520
|
-
try {
|
|
521
|
-
for (; index < json.length; index++) results.push(decodePackedRow(plan, json[index]));
|
|
522
|
-
} catch (error) {
|
|
523
|
-
throw prefixPath(`[${index}]`, error);
|
|
524
|
-
}
|
|
525
|
-
return results;
|
|
526
|
-
}
|
|
527
|
-
if (json === null) {
|
|
528
|
-
if (plan.rows === "one") throw new MissingRow(plan.guaranteedBy);
|
|
529
|
-
return decodeMissing(plan);
|
|
530
|
-
}
|
|
531
|
-
return decodePackedRow(plan, json);
|
|
532
|
-
}
|
|
533
|
-
/** Decodes a packed row, which must be an array. */
|
|
534
|
-
function decodePackedRow(plan, json) {
|
|
535
|
-
if (!Array.isArray(json)) throw new TypeError(`Nested row must be an array, not ${describeJson(json)}`);
|
|
536
|
-
return decodeRow(plan, json, true);
|
|
537
|
-
}
|
|
538
|
-
/** Describes the kind of a parsed JSON value for an error message. */
|
|
539
|
-
function describeJson(value) {
|
|
540
|
-
if (value === null) return "null";
|
|
541
|
-
if (typeof value === "object") return Array.isArray(value) ? "an array" : "an object";
|
|
542
|
-
return `a ${typeof value}`;
|
|
543
|
-
}
|
|
544
|
-
// src/table/table.ts
|
|
545
|
-
function defineTable(name, definitions, options) {
|
|
546
|
-
const columns = {};
|
|
547
|
-
for (const [key, definition] of Object.entries(definitions)) {
|
|
548
|
-
if (key.startsWith("$")) throw new Error(`Column key "${key}" of table "${name}" must not start with "$"`);
|
|
549
|
-
if (!isRecordKey(key)) throw new Error(`Column key "__proto__" of table "${name}" is not allowed; use columnName("__proto__")`);
|
|
550
|
-
const column = toColumn(definition);
|
|
551
|
-
if (!column) throw new TypeError(`Column "${key}" of table "${name}" must be a type or a column`);
|
|
552
|
-
columns[key] = column;
|
|
553
|
-
}
|
|
554
|
-
return Object.freeze({
|
|
555
|
-
kind: "table",
|
|
556
|
-
name,
|
|
557
|
-
columns: Object.freeze(columns),
|
|
558
|
-
keys: defineKeys(name, columns, options)
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
/**
|
|
562
|
-
* Defines the keys of table `name` of `columns` from the keys marked on the columns and those of `options`.
|
|
563
|
-
*
|
|
564
|
-
* The unique keys are those of the columns in their order, followed by those of the options.
|
|
565
|
-
* It rejects a primary key declared twice, a key of the options with fewer than two columns, an unknown column or a column listed twice in such a key, a nullable column of the primary key, and two keys of the same columns.
|
|
566
|
-
*/
|
|
567
|
-
function defineKeys(name, columns, options) {
|
|
568
|
-
let primaryKey;
|
|
569
|
-
const unique = [];
|
|
570
|
-
for (const key of Object.keys(columns)) {
|
|
571
|
-
const keyKind = columns[key].attributes.keyKind;
|
|
572
|
-
if (keyKind === "primaryKey") {
|
|
573
|
-
if (primaryKey !== void 0) throw new Error(`Table "${name}" marks columns "${primaryKey[0]}" and "${key}" with primaryKey(); list the columns of a primary key of several columns in the options`);
|
|
574
|
-
primaryKey = Object.freeze([key]);
|
|
575
|
-
} else if (keyKind === "unique") unique.push(Object.freeze([key]));
|
|
576
|
-
}
|
|
577
|
-
if (options?.primaryKey !== void 0) {
|
|
578
|
-
if (primaryKey !== void 0) throw new Error(`Table "${name}" declares its primary key both on column "${primaryKey[0]}" and in the options`);
|
|
579
|
-
primaryKey = toKeyColumns(name, columns, options.primaryKey);
|
|
580
|
-
}
|
|
581
|
-
if (options?.unique !== void 0) {
|
|
582
|
-
if (!Array.isArray(options.unique)) throw new TypeError(`Unique keys in the options of table "${name}" must be an array`);
|
|
583
|
-
for (const key of options.unique) unique.push(toKeyColumns(name, columns, key));
|
|
584
|
-
}
|
|
585
|
-
for (const key of primaryKey ?? []) if (columns[key].type.isNullable) throw new Error(`Primary key column "${key}" of table "${name}" must not be nullable`);
|
|
586
|
-
const keys = primaryKey === void 0 ? unique : [primaryKey, ...unique];
|
|
587
|
-
for (let index = 1; index < keys.length; index++) for (let other = 0; other < index; other++) if (hasSameColumns(keys[index], keys[other])) throw new Error(`Table "${name}" declares the key of columns ${keys[index].map((key) => `"${key}"`).join(", ")} twice`);
|
|
588
|
-
return Object.freeze({
|
|
589
|
-
primaryKey,
|
|
590
|
-
unique: Object.freeze(unique)
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
/** Returns the column keys of a key of several columns from the options of table `name`, frozen, rejecting what `defineKeys` rejects in such a key. */
|
|
594
|
-
function toKeyColumns(name, columns, key) {
|
|
595
|
-
if (!Array.isArray(key) || key.length < 2) throw new Error(`A key in the options of table "${name}" must list at least two columns; mark a key of one column with primaryKey() or unique()`);
|
|
596
|
-
for (let index = 0; index < key.length; index++) {
|
|
597
|
-
const column = key[index];
|
|
598
|
-
if (typeof column !== "string" || !Object.hasOwn(columns, column)) throw new Error(`A key in the options of table "${name}" lists unknown column "${column}"`);
|
|
599
|
-
if (key.indexOf(column) !== index) throw new Error(`A key in the options of table "${name}" lists column "${column}" twice`);
|
|
600
|
-
}
|
|
601
|
-
return Object.freeze(key.slice());
|
|
602
|
-
}
|
|
603
|
-
/** Tells whether keys `key` and `other` have the same columns, in any order; neither lists a column twice. */
|
|
604
|
-
function hasSameColumns(key, other) {
|
|
605
|
-
return key.length === other.length && key.every((column) => other.includes(column));
|
|
606
|
-
}
|
|
607
|
-
/** Finds the column of `table` under `key`, ignoring properties inherited from the object prototype, such as `toString`. */
|
|
608
|
-
function findColumn(table, key) {
|
|
609
|
-
return Object.hasOwn(table.columns, key) ? table.columns[key] : void 0;
|
|
610
|
-
}
|
|
611
|
-
/** Tells whether the columns of `table` under `columns` are exactly a key of it, primary or unique, in any order, as `IsKey` does in types. */
|
|
612
|
-
function isKey(table, columns) {
|
|
613
|
-
const listed = new Set(columns);
|
|
614
|
-
return someKey(table, (key) => key.length === listed.size && isKeyAmong(key, listed));
|
|
615
|
-
}
|
|
616
|
-
/** Tells whether the columns of `table` under `columns` include a key of it, primary or unique, as `ContainsKey` does in types. */
|
|
617
|
-
function containsKey(table, columns) {
|
|
618
|
-
const listed = new Set(columns);
|
|
619
|
-
return someKey(table, (key) => isKeyAmong(key, listed));
|
|
620
|
-
}
|
|
621
|
-
/** Tells whether `predicate` holds for a key of `table`, primary or unique. */
|
|
622
|
-
function someKey(table, predicate) {
|
|
623
|
-
const { primaryKey, unique } = table.keys;
|
|
624
|
-
return primaryKey !== void 0 && predicate(primaryKey) || unique.some(predicate);
|
|
625
|
-
}
|
|
626
|
-
/** Tells whether every column of `key` is in `columns`. */
|
|
627
|
-
function isKeyAmong(key, columns) {
|
|
628
|
-
return key.every((column) => columns.has(column));
|
|
629
|
-
}
|
|
630
|
-
/** Returns the column of `table` under `key` as `findColumn` finds it, rejecting a key that is not a column. */
|
|
631
|
-
function requireColumn(table, key) {
|
|
632
|
-
const column = findColumn(table, key);
|
|
633
|
-
if (!column) throw new Error(`Unknown column "${key}" of table "${table.name}"`);
|
|
634
|
-
return column;
|
|
635
|
-
}
|
|
636
|
-
/** Creates a column from a column definition, or returns `undefined` for a value that is neither a type nor a column. */
|
|
637
|
-
function toColumn(definition) {
|
|
638
|
-
if (definition instanceof Column) return definition;
|
|
639
|
-
return isType(definition) ? new Column(definition, {
|
|
640
|
-
name: void 0,
|
|
641
|
-
keyKind: void 0,
|
|
642
|
-
hasDefault: false
|
|
643
|
-
}) : void 0;
|
|
644
|
-
}
|
|
645
|
-
// src/query/resolve-values.ts
|
|
646
|
-
/**
|
|
647
|
-
* Replaces objects of column values in a predicate with conditions on the columns of `source`.
|
|
648
|
-
*
|
|
649
|
-
* Groups are rebuilt, so constants that appear after resolution, such as an empty object, are simplified.
|
|
650
|
-
*/
|
|
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;
|
|
806
|
-
}
|
|
807
|
-
/**
|
|
808
|
-
* Creates an alias based on `name` that is unique in the statement, so that no source shadows another one in SQL.
|
|
809
|
-
*
|
|
810
|
-
* The name is cut to 50 bytes and numbered when the statement already has it, so an alias fits an identifier and PostgreSQL compares aliases as created rather than truncated.
|
|
811
|
-
*/
|
|
812
|
-
createAlias(name) {
|
|
813
|
-
const base = cutIdentifier(name.toWellFormed(), 50);
|
|
814
|
-
let alias = base;
|
|
815
|
-
for (let index = 2; this.#aliases.has(alias); index++) alias = `${base}_${index}`;
|
|
816
|
-
this.#aliases.add(alias);
|
|
817
|
-
return alias;
|
|
818
|
-
}
|
|
819
|
-
/** Adds a parameter of the statement and returns its placeholder, rejecting more parameters than PostgreSQL takes. */
|
|
820
|
-
bind(value) {
|
|
821
|
-
if (this.parameters.length === 65535) throw new Error(`Statement has more than ${maxParameters} parameters, which PostgreSQL does not take; split it into several statements, such as in a transaction`);
|
|
822
|
-
this.parameters.push(value);
|
|
823
|
-
return `$${this.parameters.length}`;
|
|
824
|
-
}
|
|
825
|
-
};
|
|
826
|
-
// src/compiler/postgres/parentheses.ts
|
|
827
|
-
/** Tells whether an operand of `kind` needs parentheses at `position`, so that the SQL keeps the structure of the tree. */
|
|
828
|
-
function needsParentheses(kind, position) {
|
|
829
|
-
switch (kind) {
|
|
830
|
-
case "column":
|
|
831
|
-
case "param":
|
|
832
|
-
case "boolean":
|
|
833
|
-
case "values":
|
|
834
|
-
case "nestedQuery":
|
|
835
|
-
case "tableName":
|
|
836
|
-
case "subquery":
|
|
837
|
-
case "exists":
|
|
838
|
-
case "textSubquery":
|
|
839
|
-
case "aggregate": return false;
|
|
840
|
-
case "cast": return position === "interpolation";
|
|
841
|
-
case "raw": return position !== "interpolation";
|
|
842
|
-
case "and":
|
|
843
|
-
case "or": return true;
|
|
844
|
-
case "not":
|
|
845
|
-
case "comparison":
|
|
846
|
-
case "in":
|
|
847
|
-
case "inQuery":
|
|
848
|
-
case "isNull": return position !== "condition";
|
|
849
|
-
default: return kind;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
// src/compiler/postgres/scope.ts
|
|
853
|
-
/**
|
|
854
|
-
* Declares a source of `FROM` on top of `outer` with a new alias.
|
|
855
|
-
*
|
|
856
|
-
* It rejects a source already visible there, such as a query nested in itself: a column reference could not tell its occurrences apart.
|
|
857
|
-
*/
|
|
858
|
-
function declareSource(context, outer, source) {
|
|
859
|
-
if (findSource(outer, source) !== void 0 || source.kind === "derived" && source.replaces !== void 0 && findSource(outer, source.replaces) !== void 0) throw new Error(`An occurrence of ${describeSource(source)} appears where it is already visible, so its column references would be ambiguous`);
|
|
860
|
-
const name = source.kind === "table" ? source.table.name : source.replaces?.table.name ?? "subquery";
|
|
861
|
-
return {
|
|
862
|
-
source,
|
|
863
|
-
alias: context.createAlias(name),
|
|
864
|
-
outer
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
/**
|
|
868
|
-
* Returns the declared source that a column reference reads in `scope`: its source, or a derived source that replaces it; rejects a source that is not visible there.
|
|
869
|
-
*/
|
|
870
|
-
function resolveSource(scope, column) {
|
|
871
|
-
const declared = findSource(scope, column.source);
|
|
872
|
-
if (declared === void 0) throw new Error(`Column "${column.key}" of ${describeSource(column.source)} refers to a source that is not visible where it is used`);
|
|
873
|
-
return declared;
|
|
874
|
-
}
|
|
875
|
-
/** Returns the declared source of `scope` that is `source` or replaces it, or `undefined` when the source is not visible there. */
|
|
876
|
-
function findSource(scope, source) {
|
|
877
|
-
for (let current = scope; current; current = current.outer) {
|
|
878
|
-
const declared = current.source;
|
|
879
|
-
if (declared === source || declared.kind === "derived" && declared.replaces === source) return current;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
function describeSource(source) {
|
|
883
|
-
return source.kind === "table" ? `table "${source.table.name}"` : "a subquery";
|
|
884
|
-
}
|
|
885
|
-
// src/compiler/postgres/compile.ts
|
|
886
|
-
const noFields = /* @__PURE__ */ Object.freeze([]);
|
|
887
|
-
/**
|
|
888
|
-
* Compiles a statement for PostgreSQL: a select statement, a mutation, or a statement of SQL text.
|
|
889
|
-
*
|
|
890
|
-
* With `tables`, such as the tables of the schema of a `Db`, an occurrence or an interpolated name of any other table at any depth of the statement is rejected.
|
|
891
|
-
* `db` is the `Db` that runs the statement: a table interpolated from a query bound to another `Db`, or to any `Db` without it, is rejected.
|
|
892
|
-
*/
|
|
893
|
-
function compileStatement(node, options = {}, tables, db) {
|
|
894
|
-
const context = new CompileContext(options.schemaName, tables, db);
|
|
895
|
-
switch (node.kind) {
|
|
896
|
-
case "select": return {
|
|
897
|
-
text: compileQuery(context, void 0, node),
|
|
898
|
-
parameters: context.parameters,
|
|
899
|
-
fields: node.projections,
|
|
900
|
-
recordTemplate: node.recordTemplate,
|
|
901
|
-
rows: node.rows,
|
|
902
|
-
value: node.value,
|
|
903
|
-
counted: void 0,
|
|
904
|
-
named: false
|
|
905
|
-
};
|
|
906
|
-
case "text": {
|
|
907
|
-
const { fields } = node;
|
|
908
|
-
return {
|
|
909
|
-
text: compileExpression(context, void 0, node.sql),
|
|
910
|
-
parameters: context.parameters,
|
|
911
|
-
fields: fields ?? noFields,
|
|
912
|
-
rows: node.rows,
|
|
913
|
-
value: node.value,
|
|
914
|
-
counted: fields === void 0 ? "number" : void 0,
|
|
915
|
-
named: true
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
|
-
default: {
|
|
919
|
-
const { returning } = node;
|
|
920
|
-
return {
|
|
921
|
-
text: compileMutation(context, node),
|
|
922
|
-
parameters: context.parameters,
|
|
923
|
-
fields: returning ?? noFields,
|
|
924
|
-
recordTemplate: node.recordTemplate,
|
|
925
|
-
rows: node.rows,
|
|
926
|
-
value: node.value,
|
|
927
|
-
counted: returning === void 0 ? "number" : returning.length === 0 ? "records" : void 0,
|
|
928
|
-
named: false
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
/** Compiles a mutation, whose expressions see only the occurrence of its table, apart from the values of an insert, which see no source. */
|
|
934
|
-
function compileMutation(context, node) {
|
|
935
|
-
const scope = declareSource(context, void 0, node.target);
|
|
936
|
-
const target = compileSource(context, void 0, scope);
|
|
937
|
-
let text;
|
|
938
|
-
switch (node.kind) {
|
|
939
|
-
case "insert":
|
|
940
|
-
text = `INSERT INTO ${target}${compileValues(context, node)}`;
|
|
941
|
-
break;
|
|
942
|
-
case "update": {
|
|
943
|
-
text = `UPDATE ${target} SET`;
|
|
944
|
-
const { set } = node;
|
|
945
|
-
for (let index = 0; index < set.length; index++) {
|
|
946
|
-
const { key, value } = set[index];
|
|
947
|
-
text += `${index === 0 ? " " : ", "}${compileColumnName(node.target, key)} = ${compileExpression(context, scope, value)}`;
|
|
948
|
-
}
|
|
949
|
-
text += compileWhere(context, scope, node.filters);
|
|
950
|
-
break;
|
|
951
|
-
}
|
|
952
|
-
case "delete":
|
|
953
|
-
text = `DELETE FROM ${target}${compileWhere(context, scope, node.filters)}`;
|
|
954
|
-
break;
|
|
955
|
-
default: return node;
|
|
956
|
-
}
|
|
957
|
-
const { returning } = node;
|
|
958
|
-
if (returning !== void 0 && returning.length > 0) text += ` RETURNING${compileProjections(context, scope, returning)}`;
|
|
959
|
-
return text;
|
|
960
|
-
}
|
|
961
|
-
/** Compiles the columns and the rows of `VALUES` of an insert, or `DEFAULT VALUES` for one row of the defaults of every column. */
|
|
962
|
-
function compileValues(context, node) {
|
|
963
|
-
const { columns, values } = node;
|
|
964
|
-
if (columns.length === 0) {
|
|
965
|
-
if (values.length !== 1) throw new Error("An insert of several rows must list a column");
|
|
966
|
-
return " DEFAULT VALUES";
|
|
967
|
-
}
|
|
968
|
-
let text = " (";
|
|
969
|
-
for (let index = 0; index < columns.length; index++) text += `${index === 0 ? "" : ", "}${compileColumnName(node.target, columns[index])}`;
|
|
970
|
-
text += ") VALUES";
|
|
971
|
-
for (let row = 0; row < values.length; row++) {
|
|
972
|
-
const items = values[row];
|
|
973
|
-
text += row === 0 ? " (" : ", (";
|
|
974
|
-
for (let index = 0; index < items.length; index++) {
|
|
975
|
-
const item = items[index];
|
|
976
|
-
text += `${index === 0 ? "" : ", "}${item === void 0 ? "DEFAULT" : compileExpression(context, void 0, item)}`;
|
|
977
|
-
}
|
|
978
|
-
text += ")";
|
|
979
|
-
}
|
|
980
|
-
return text;
|
|
981
|
-
}
|
|
982
|
-
/** Compiles the physical name of the column of the table of `target` under `key`, as `SET` and the column list of an insert name it, without an alias. */
|
|
983
|
-
function compileColumnName(target, key) {
|
|
984
|
-
return quoteIdentifier(requireColumn(target.table, key).attributes.name ?? key);
|
|
985
|
-
}
|
|
986
|
-
/** Compiles a query at a position of the statement where the sources of `outer` are visible. */
|
|
987
|
-
function compileQuery(context, outer, node) {
|
|
988
|
-
const { from } = node;
|
|
989
|
-
if (from === void 0 && node.joins.length > 0) throw new Error("A query without FROM cannot join sources");
|
|
990
|
-
const fromScope = from === void 0 ? void 0 : declareSource(context, outer, from);
|
|
991
|
-
let scope = fromScope ?? outer;
|
|
992
|
-
const joinScopes = [];
|
|
993
|
-
for (const join of node.joins) {
|
|
994
|
-
scope = declareSource(context, scope, join.source);
|
|
995
|
-
joinScopes.push(scope);
|
|
996
|
-
}
|
|
997
|
-
let text = `SELECT${compileProjections(context, scope, node.projections)}`;
|
|
998
|
-
if (fromScope !== void 0) {
|
|
999
|
-
text += ` FROM ${compileSource(context, outer, fromScope)}`;
|
|
1000
|
-
for (let index = 0; index < node.joins.length; index++) {
|
|
1001
|
-
const joinScope = joinScopes[index];
|
|
1002
|
-
text += ` LEFT JOIN ${compileSource(context, outer, joinScope)} ON ${compileExpression(context, joinScope, node.joins[index].on)}`;
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
text += compileWhere(context, scope, node.filters);
|
|
1006
|
-
if (node.orderBy.length > 0) text += ` ${compileOrderBy(context, scope, node.orderBy)}`;
|
|
1007
|
-
if (node.limit) text += ` LIMIT ${compileExpression(context, outer, node.limit)}`;
|
|
1008
|
-
if (node.offset) text += ` OFFSET ${compileExpression(context, outer, node.offset)}`;
|
|
1009
|
-
return text;
|
|
1010
|
-
}
|
|
1011
|
-
/** Compiles the projections of a select list or of `RETURNING`, each after a space or a comma. */
|
|
1012
|
-
function compileProjections(context, scope, projections) {
|
|
1013
|
-
let text = "";
|
|
1014
|
-
for (let index = 0; index < projections.length; index++) {
|
|
1015
|
-
const projection = projections[index];
|
|
1016
|
-
text += `${index === 0 ? " " : ", "}${compileExpression(context, scope, projection.expression)} AS ${quoteIdentifier(nameProjection(projection.key, index))}`;
|
|
1017
|
-
}
|
|
1018
|
-
return text;
|
|
1019
|
-
}
|
|
1020
|
-
/** Compiles the `WHERE` clause of the effective predicate of `filters` after a space, or nothing when they restrict no row. */
|
|
1021
|
-
function compileWhere(context, scope, filters) {
|
|
1022
|
-
const where = combineFilters(filters);
|
|
1023
|
-
return where.kind === "boolean" && where.value ? "" : ` WHERE ${compileExpression(context, scope, where)}`;
|
|
1024
|
-
}
|
|
1025
|
-
/** Compiles the `ORDER BY` clause of a query or an aggregate, which has at least one sort key. */
|
|
1026
|
-
function compileOrderBy(context, scope, orderBy) {
|
|
1027
|
-
let text = "ORDER BY";
|
|
1028
|
-
for (let index = 0; index < orderBy.length; index++) {
|
|
1029
|
-
const order = orderBy[index];
|
|
1030
|
-
text += `${index === 0 ? " " : ", "}${compileExpression(context, scope, order.expression)}`;
|
|
1031
|
-
if (order.direction === "desc") text += " DESC";
|
|
1032
|
-
if (order.nulls !== void 0) text += order.nulls === "first" ? " NULLS FIRST" : " NULLS LAST";
|
|
1033
|
-
}
|
|
1034
|
-
return text;
|
|
1035
|
-
}
|
|
1036
|
-
/** Compiles a declared source of a query at a position of the statement where the sources of `outer` are visible. */
|
|
1037
|
-
function compileSource(context, outer, declared) {
|
|
1038
|
-
const { source, alias } = declared;
|
|
1039
|
-
if (source.kind === "table") return `${compileTableName(context, source.table)} AS ${quoteIdentifier(alias)}`;
|
|
1040
|
-
const query = compileQuery(context, source.lateral ? declared.outer : outer, source.query);
|
|
1041
|
-
return `${source.lateral ? "LATERAL " : ""}(${query}) AS ${quoteIdentifier(alias)}`;
|
|
1042
|
-
}
|
|
1043
|
-
/** Compiles the name of `table`, qualified by the PostgreSQL schema of the statement, rejecting a table outside the tables of the statement. */
|
|
1044
|
-
function compileTableName(context, table) {
|
|
1045
|
-
if (context.tables !== void 0 && !context.tables.has(table)) throw new Error(`Table "${table.name}" is not part of the schema`);
|
|
1046
|
-
return context.schemaName === void 0 ? quoteIdentifier(table.name) : `${quoteIdentifier(context.schemaName)}.${quoteIdentifier(table.name)}`;
|
|
1047
|
-
}
|
|
1048
|
-
/** Compiles the name of a table interpolated into a SQL fragment, rejecting a table of a query bound to a `Db` other than the one that runs the statement. */
|
|
1049
|
-
function compileInterpolatedTable(context, node) {
|
|
1050
|
-
const { db, table } = node;
|
|
1051
|
-
if (db !== void 0 && db !== context.db) throw new Error(context.db === void 0 ? `Table "${table.name}" interpolated into SQL is bound to a Db, so only that Db can compile the query` : `Table "${table.name}" interpolated into SQL is bound to another Db`);
|
|
1052
|
-
return compileTableName(context, table);
|
|
1053
|
-
}
|
|
1054
|
-
function compileColumn(scope, node) {
|
|
1055
|
-
const { key } = node;
|
|
1056
|
-
const declared = resolveSource(scope, node);
|
|
1057
|
-
const { source } = declared;
|
|
1058
|
-
let name;
|
|
1059
|
-
if (source.kind === "table") name = requireColumn(source.table, key).attributes.name ?? key;
|
|
1060
|
-
else {
|
|
1061
|
-
const index = source.query.projections.findIndex((projection) => projection.key === key);
|
|
1062
|
-
if (index === -1) throw new Error(`Unknown field "${key}" of a subquery`);
|
|
1063
|
-
name = nameProjection(key, index);
|
|
1064
|
-
}
|
|
1065
|
-
return `${quoteIdentifier(declared.alias)}.${quoteIdentifier(name)}`;
|
|
1066
|
-
}
|
|
1067
|
-
function compileExpression(context, scope, node) {
|
|
1068
|
-
switch (node.kind) {
|
|
1069
|
-
case "column": return compileColumn(scope, node);
|
|
1070
|
-
case "param": return context.bind(encodeParameter(node));
|
|
1071
|
-
case "cast": return `${compileOperand(context, scope, node.operand, "value")}::${node.type.castType}`;
|
|
1072
|
-
case "boolean": return node.value ? "TRUE" : "FALSE";
|
|
1073
|
-
case "and":
|
|
1074
|
-
case "or": return node.operands.map((operand) => compileOperand(context, scope, operand, "condition")).join(node.kind === "and" ? " AND " : " OR ");
|
|
1075
|
-
case "not": return `NOT ${compileOperand(context, scope, node.operand, "condition")}`;
|
|
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");
|
|
1078
|
-
case "comparison": return `${compileOperand(context, scope, node.left, "value")} ${node.operator} ${compileOperand(context, scope, node.right, "value")}`;
|
|
1079
|
-
case "in": {
|
|
1080
|
-
let text = `${compileOperand(context, scope, node.operand, "value")} IN (${compileOperand(context, scope, node.list[0], "value")}`;
|
|
1081
|
-
for (let index = 1; index < node.list.length; index++) text += `, ${compileOperand(context, scope, node.list[index], "value")}`;
|
|
1082
|
-
return `${text})`;
|
|
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
|
-
}
|
|
1089
|
-
case "isNull": return `${compileOperand(context, scope, node.operand, "value")} IS NULL`;
|
|
1090
|
-
case "raw": {
|
|
1091
|
-
let text = node.fragments[0];
|
|
1092
|
-
for (let index = 0; index < node.values.length; index++) text += compileOperand(context, scope, node.values[index], "interpolation") + node.fragments[index + 1];
|
|
1093
|
-
return text;
|
|
1094
|
-
}
|
|
1095
|
-
case "tableName": return compileInterpolatedTable(context, node);
|
|
1096
|
-
case "subquery": return compileSubquery(context, scope, node.query, "A scalar subquery");
|
|
1097
|
-
case "exists": return `EXISTS (${compileQuery(context, scope, node.query)})`;
|
|
1098
|
-
case "textSubquery": return `(${compileQuery(context, scope, node.query)})`;
|
|
1099
|
-
case "aggregate": {
|
|
1100
|
-
let text = `${node.name}(${node.distinct ? "DISTINCT " : ""}`;
|
|
1101
|
-
for (let index = 0; index < node.arguments.length; index++) text += `${index === 0 ? "" : ", "}${compileExpression(context, scope, node.arguments[index])}`;
|
|
1102
|
-
if (node.orderBy.length > 0) text += ` ${compileOrderBy(context, scope, node.orderBy)}`;
|
|
1103
|
-
return `${text})`;
|
|
1104
|
-
}
|
|
1105
|
-
default: return node;
|
|
1106
|
-
}
|
|
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
|
-
}
|
|
1114
|
-
/** Encodes the value of a parameter node with its type, or by its JavaScript type without one. */
|
|
1115
|
-
function encodeParameter(node) {
|
|
1116
|
-
if (node.value === void 0) throw new TypeError("Query parameter is undefined; use null for NULL");
|
|
1117
|
-
if (node.value === null) return null;
|
|
1118
|
-
return node.type ? node.type.encode(node.value) : encodeUntyped(node.value);
|
|
1119
|
-
}
|
|
1120
|
-
/** Compiles an operand at `position` of its parent, parenthesizing it where its own operators could regroup with the parent. */
|
|
1121
|
-
function compileOperand(context, scope, node, position) {
|
|
1122
|
-
const sql = compileExpression(context, scope, node);
|
|
1123
|
-
return needsParentheses(node.kind, position) ? `(${sql})` : sql;
|
|
1124
|
-
}
|
|
1125
|
-
// src/execution/driver.ts
|
|
1126
|
-
/** Creates a statement from SQL text and the text representations of its parameters, such as `createStatement("BEGIN")`. */
|
|
1127
|
-
function createStatement(text, parameters = []) {
|
|
1128
|
-
return {
|
|
1129
|
-
text,
|
|
1130
|
-
parameters
|
|
1131
|
-
};
|
|
1132
|
-
}
|
|
1133
|
-
// src/ast/references.ts
|
|
1134
|
-
/** Adds to `keys` the key of each column reference to `source` in `node` at any depth, including the queries nested in it. */
|
|
1135
|
-
function collectColumnKeys(node, source, keys) {
|
|
1136
|
-
visitNodes(node, true, (child) => {
|
|
1137
|
-
if (child.kind === "column" && child.source === source) keys.add(child.key);
|
|
1138
|
-
});
|
|
1139
|
-
}
|
|
1140
|
-
/**
|
|
1141
|
-
* Tells whether `node` holds, at any depth, a SQL fragment that refers to a column of `source` outside the queries nested in the fragment.
|
|
1142
|
-
*
|
|
1143
|
-
* PostgreSQL assigns an aggregate whose arguments refer only to the sources of an enclosing query to that query ([PostgreSQL](https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-AGGREGATES)).
|
|
1144
|
-
* Lowering does not parse SQL fragments to identify their aggregates, so such a fragment in a nested query, such as `sum` of a column of the parent row, may aggregate the rows of the query of `source`.
|
|
1145
|
-
*/
|
|
1146
|
-
function holdsFragmentOfSource(node, source) {
|
|
1147
|
-
let found = false;
|
|
1148
|
-
visitNodes(node, true, (child) => {
|
|
1149
|
-
if (found || child.kind !== "raw") return;
|
|
1150
|
-
visitNodes(child, false, (value) => {
|
|
1151
|
-
if (value.kind === "column" && value.source === source) found = true;
|
|
1152
|
-
});
|
|
1153
|
-
});
|
|
1154
|
-
return found;
|
|
1155
|
-
}
|
|
1156
|
-
/** Calls `visit` for `node` and each expression node in it, also in the queries nested in it when `deep` is set. */
|
|
1157
|
-
function visitNodes(node, deep, visit) {
|
|
1158
|
-
visit(node);
|
|
1159
|
-
switch (node.kind) {
|
|
1160
|
-
case "column":
|
|
1161
|
-
case "param":
|
|
1162
|
-
case "boolean":
|
|
1163
|
-
case "tableName":
|
|
1164
|
-
case "values":
|
|
1165
|
-
case "nestedQuery": return;
|
|
1166
|
-
case "cast":
|
|
1167
|
-
case "not":
|
|
1168
|
-
case "isNull":
|
|
1169
|
-
visitNodes(node.operand, deep, visit);
|
|
1170
|
-
return;
|
|
1171
|
-
case "and":
|
|
1172
|
-
case "or":
|
|
1173
|
-
for (const operand of node.operands) visitNodes(operand, deep, visit);
|
|
1174
|
-
return;
|
|
1175
|
-
case "comparison":
|
|
1176
|
-
visitNodes(node.left, deep, visit);
|
|
1177
|
-
visitNodes(node.right, deep, visit);
|
|
1178
|
-
return;
|
|
1179
|
-
case "in":
|
|
1180
|
-
visitNodes(node.operand, deep, visit);
|
|
1181
|
-
for (const item of node.list) visitNodes(item, deep, visit);
|
|
1182
|
-
return;
|
|
1183
|
-
case "inQuery":
|
|
1184
|
-
visitNodes(node.operand, deep, visit);
|
|
1185
|
-
if (deep && node.query.kind === "select") visitQuery(node.query, visit);
|
|
1186
|
-
return;
|
|
1187
|
-
case "raw":
|
|
1188
|
-
for (const value of node.values) visitNodes(value, deep, visit);
|
|
1189
|
-
return;
|
|
1190
|
-
case "subquery":
|
|
1191
|
-
case "exists":
|
|
1192
|
-
case "textSubquery":
|
|
1193
|
-
if (deep) visitQuery(node.query, visit);
|
|
1194
|
-
return;
|
|
1195
|
-
case "aggregate":
|
|
1196
|
-
for (const argument of node.arguments) visitNodes(argument, deep, visit);
|
|
1197
|
-
for (const order of node.orderBy) visitNodes(order.expression, deep, visit);
|
|
1198
|
-
return;
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
/** Calls `visit` for each expression node in the clauses and the derived sources of `query` at any depth. */
|
|
1202
|
-
function visitQuery(query, visit) {
|
|
1203
|
-
const { from, filters } = query;
|
|
1204
|
-
if (from?.kind === "derived") visitQuery(from.query, visit);
|
|
1205
|
-
for (const join of query.joins) {
|
|
1206
|
-
if (join.source.kind === "derived") visitQuery(join.source.query, visit);
|
|
1207
|
-
visitNodes(join.on, true, visit);
|
|
1208
|
-
}
|
|
1209
|
-
for (const projection of query.projections) visitNodes(projection.expression, true, visit);
|
|
1210
|
-
if (filters.relation !== void 0) visitNodes(filters.relation, true, visit);
|
|
1211
|
-
if (filters.scope !== void 0) visitNodes(filters.scope, true, visit);
|
|
1212
|
-
if (filters.user !== void 0) visitNodes(filters.user, true, visit);
|
|
1213
|
-
for (const order of query.orderBy) visitNodes(order.expression, true, visit);
|
|
1214
|
-
if (query.limit !== void 0) visitNodes(query.limit, true, visit);
|
|
1215
|
-
if (query.offset !== void 0) visitNodes(query.offset, true, visit);
|
|
1216
|
-
}
|
|
1217
|
-
// src/query/rows.ts
|
|
1218
|
-
/** Returns the rows mode of a query of the records of `relation`, as `RelationRows` gives it in types. */
|
|
1219
|
-
function getRelationRows(relation) {
|
|
1220
|
-
if (relation.kind === "many") return "many";
|
|
1221
|
-
return relation.required ? "one" : "nullable";
|
|
1222
|
-
}
|
|
1223
|
-
/** Returns the rows mode after a filter of a query of rows mode `rows`, of values when `value`, whose row is `guaranteed`, as `FilteredStateRows` gives it in types. */
|
|
1224
|
-
function toFilteredRows(rows, value, guaranteed) {
|
|
1225
|
-
if (!guaranteed || rows !== "one") return rows;
|
|
1226
|
-
return value ? "optional" : "nullable";
|
|
1227
|
-
}
|
|
1228
|
-
/** Returns the limit of a query of many rows that becomes a query of one row, given its limit `limit`: one row, or none under a limit of zero. */
|
|
1229
|
-
function limitToOne(limit) {
|
|
1230
|
-
return limit?.kind === "param" && limit.value === 0 ? limit : oneNode;
|
|
1231
|
-
}
|
|
1232
|
-
/** Returns the rows mode after `pluck` of a query of rows mode `rows`, as `ValueRows` gives it in types. */
|
|
1233
|
-
function toValueRows(rows) {
|
|
1234
|
-
return rows === "nullable" ? "optional" : rows;
|
|
1235
|
-
}
|
|
1236
|
-
// src/query/node.ts
|
|
1237
|
-
/** Creates a query of all rows of `from`, or of the records of `relation` with `filters` in the rows mode of the relation. */
|
|
1238
|
-
function createQueryNode(from, relation, filters = noFilters) {
|
|
1239
|
-
return Object.freeze({
|
|
1240
|
-
kind: "query",
|
|
1241
|
-
from,
|
|
1242
|
-
relation,
|
|
1243
|
-
projections: [],
|
|
1244
|
-
filters,
|
|
1245
|
-
orderBy: [],
|
|
1246
|
-
limit: void 0,
|
|
1247
|
-
offset: void 0,
|
|
1248
|
-
rows: relation === void 0 ? "many" : getRelationRows(relation),
|
|
1249
|
-
value: false,
|
|
1250
|
-
guaranteed: relation?.required ?? false,
|
|
1251
|
-
all: false
|
|
1252
|
-
});
|
|
1253
|
-
}
|
|
1254
|
-
/** Tells whether `node` is a query of a table without clauses, such as a table of a `Db`: no relation, filters, selected fields, order, limit, offset, rows other than `many`, or `all`. */
|
|
1255
|
-
function isTableQuery(node) {
|
|
1256
|
-
return node.relation === void 0 && node.projections.length === 0 && node.filters.relation === void 0 && node.filters.scope === void 0 && node.filters.user === void 0 && node.orderBy.length === 0 && node.limit === void 0 && node.offset === void 0 && node.rows === "many" && !node.all;
|
|
1257
|
-
}
|
|
1258
|
-
/** Tells whether a selected field is the rows of a nested query. */
|
|
1259
|
-
function isNestedSelection(node) {
|
|
1260
|
-
return "query" in node;
|
|
1261
|
-
}
|
|
1262
|
-
/** Tells whether a selected field is a literal. */
|
|
1263
|
-
function isLiteralSelection(node) {
|
|
1264
|
-
return "literal" in node;
|
|
1265
|
-
}
|
|
1266
|
-
// src/schema/relation.ts
|
|
1267
|
-
/** Runtime mark of the declarations of `many` and `one`. */
|
|
1268
|
-
const declarationBrand = Symbol("kern-orm.relation");
|
|
1269
|
-
/** Returns the name of relation `name` of the table under key `key` in messages and errors, such as `field.form`. */
|
|
1270
|
-
function formatRelationName(key, name) {
|
|
1271
|
-
return `${key}.${name}`;
|
|
1272
|
-
}
|
|
1273
|
-
/**
|
|
1274
|
-
* Declares a relation of a table to the records of `target` whose columns `to` equal its columns `from`, for `$relations` of a schema.
|
|
1275
|
-
*
|
|
1276
|
-
* A composite key lists its columns in arrays of the same length.
|
|
1277
|
-
*/
|
|
1278
|
-
function many(target, keys) {
|
|
1279
|
-
return declareRelation("many", target, keys, false);
|
|
1280
|
-
}
|
|
1281
|
-
/**
|
|
1282
|
-
* Declares a relation of a table to the record of `target` whose columns `to` equal its columns `from`, for `$relations` of a schema.
|
|
1283
|
-
*
|
|
1284
|
-
* A composite key lists its columns in arrays of the same length.
|
|
1285
|
-
* The columns `to` must include a key of `target`, primary or unique, so that they match at most one record.
|
|
1286
|
-
* Without the record, a query of the relation gives `null`, and a query of a relation declared with `required: true` rejects unless it filters the record.
|
|
1287
|
-
* Set `required: true` only where the database guarantees the record, such as by a foreign key on columns `from` that are not nullable.
|
|
1288
|
-
*/
|
|
1289
|
-
function one(target, keys) {
|
|
1290
|
-
return declareRelation("one", target, keys, keys.required === true);
|
|
1291
|
-
}
|
|
1292
|
-
function declareRelation(kind, target, keys, required) {
|
|
1293
|
-
return Object.freeze({
|
|
1294
|
-
[declarationBrand]: true,
|
|
1295
|
-
kind,
|
|
1296
|
-
target,
|
|
1297
|
-
from: toKeyList(keys.from),
|
|
1298
|
-
to: toKeyList(keys.to),
|
|
1299
|
-
required
|
|
1300
|
-
});
|
|
1301
|
-
}
|
|
1302
|
-
function toKeyList(key) {
|
|
1303
|
-
return Object.freeze(Array.isArray(key) ? key.slice() : [key]);
|
|
1304
|
-
}
|
|
1305
|
-
/**
|
|
1306
|
-
* Defines relation `name` of table `source`, declared under its key `key` in a schema, checking its target and its keys.
|
|
1307
|
-
*
|
|
1308
|
-
* `findKey` returns the key of a table in the schema, or `undefined` for a table outside it.
|
|
1309
|
-
* Each pair of columns of the keys must satisfy `isEqualityCompatible`, the columns `to` of a relation `one` must include a key of the target, and the columns `from` of a required relation must not be nullable by `isNullable` of their types.
|
|
1310
|
-
*/
|
|
1311
|
-
function defineRelation(source, key, name, declaration, findKey) {
|
|
1312
|
-
const subject = `Relation "${formatRelationName(key, name)}"`;
|
|
1313
|
-
if (!isRelationDeclaration(declaration)) throw new TypeError(`${subject} must be declared with many or one`);
|
|
1314
|
-
if (name.startsWith("$")) throw new Error(`${subject}: a relation name must not start with "$"`);
|
|
1315
|
-
if (findColumn(source, name)) throw new Error(`${subject}: a relation name must differ from the column keys of table "${source.name}"`);
|
|
1316
|
-
const { kind, target, from, to, required } = declaration;
|
|
1317
|
-
const targetKey = findKey(target);
|
|
1318
|
-
if (targetKey === void 0) throw new Error(`${subject} refers to table "${target.name}", which is not part of the schema`);
|
|
1319
|
-
if (from.length === 0 || from.length !== to.length) throw new Error(`${subject} must list as many columns in "to" as in "from", at least one`);
|
|
1320
|
-
for (let index = 0; index < from.length; index++) {
|
|
1321
|
-
const fromColumn = findColumn(source, from[index]);
|
|
1322
|
-
if (!fromColumn) throw new Error(`${subject}: unknown column "${from[index]}" of table "${source.name}"`);
|
|
1323
|
-
if (required && fromColumn.type.isNullable) throw new Error(`${subject} cannot be required: column "${from[index]}" of table "${source.name}" is nullable`);
|
|
1324
|
-
const toColumn = findColumn(target, to[index]);
|
|
1325
|
-
if (!toColumn) throw new Error(`${subject}: unknown column "${to[index]}" of table "${target.name}"`);
|
|
1326
|
-
if (!isEqualityCompatible(fromColumn.type, toColumn.type)) throw new Error(`${subject} cannot compare column "${from[index]}" (${fromColumn.type.sqlType}) with column "${to[index]}" (${toColumn.type.sqlType}) of table "${target.name}" by SQL =`);
|
|
1327
|
-
}
|
|
1328
|
-
if (kind === "one" && !containsKey(target, to)) throw new Error(`${subject}: columns "to" must include a key of table "${target.name}", primary or unique`);
|
|
1329
|
-
return Object.freeze({
|
|
1330
|
-
kind,
|
|
1331
|
-
name,
|
|
1332
|
-
key,
|
|
1333
|
-
source,
|
|
1334
|
-
target,
|
|
1335
|
-
targetKey,
|
|
1336
|
-
from,
|
|
1337
|
-
to,
|
|
1338
|
-
required
|
|
1339
|
-
});
|
|
1340
|
-
}
|
|
1341
|
-
/** Adds `definition` of relation `name` of the table under `key` to `relations`, rejecting another definition of the relation there. */
|
|
1342
|
-
function addRelation(relations, key, name, definition) {
|
|
1343
|
-
let named = relations.get(key);
|
|
1344
|
-
if (named === void 0) {
|
|
1345
|
-
named = /* @__PURE__ */ new Map();
|
|
1346
|
-
relations.set(key, named);
|
|
1347
|
-
}
|
|
1348
|
-
const existing = named.get(name);
|
|
1349
|
-
if (existing !== void 0 && existing !== definition) throw new Error(`Relation "${formatRelationName(key, name)}" has two different definitions`);
|
|
1350
|
-
named.set(name, definition);
|
|
1351
|
-
}
|
|
1352
|
-
/** Adds the relation definitions of a schema to `relations`, rejecting another definition of a relation there. */
|
|
1353
|
-
function addRelations(relations, added) {
|
|
1354
|
-
for (const key of Object.keys(added)) {
|
|
1355
|
-
const named = added[key];
|
|
1356
|
-
for (const name of Object.keys(named)) addRelation(relations, key, name, named[name]);
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
/** Collects the relation definitions of a schema, which a `Db` of the schema accepts in its queries. */
|
|
1360
|
-
function collectDefinitions(relations) {
|
|
1361
|
-
const definitions = /* @__PURE__ */ new Set();
|
|
1362
|
-
for (const key of Object.keys(relations)) {
|
|
1363
|
-
const named = relations[key];
|
|
1364
|
-
for (const name of Object.keys(named)) definitions.add(named[name]);
|
|
1365
|
-
}
|
|
1366
|
-
return definitions;
|
|
1367
|
-
}
|
|
1368
|
-
/** Creates frozen relations of a schema from `relations`; objects without a prototype keep any key, such as `__proto__`, an own property. */
|
|
1369
|
-
function freezeRelations(relations) {
|
|
1370
|
-
const result = Object.create(null);
|
|
1371
|
-
for (const [key, named] of relations) {
|
|
1372
|
-
const table = Object.create(null);
|
|
1373
|
-
for (const [name, definition] of named) table[name] = definition;
|
|
1374
|
-
result[key] = Object.freeze(table);
|
|
1375
|
-
}
|
|
1376
|
-
return Object.freeze(result);
|
|
1377
|
-
}
|
|
1378
|
-
function isRelationDeclaration(value) {
|
|
1379
|
-
return typeof value === "object" && value !== null && declarationBrand in value;
|
|
1380
|
-
}
|
|
1381
|
-
// src/lowering/order.ts
|
|
1382
|
-
/**
|
|
1383
|
-
* Returns the position of the projection that has the value of the sort expression `expression`: a projection of the same column or of the same expression node, or `-1`.
|
|
1384
|
-
*
|
|
1385
|
-
* Only the same node or column guarantees one value: PostgreSQL may or may not evaluate equal expressions built apart once, and gives each its own parameters.
|
|
1386
|
-
*/
|
|
1387
|
-
function findSortProjection(projections, expression) {
|
|
1388
|
-
return projections.findIndex((projection) => {
|
|
1389
|
-
const selected = projection.expression;
|
|
1390
|
-
return expression.kind === "column" && selected.kind === "column" ? selected.source === expression.source && selected.key === expression.key : selected === expression;
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1393
|
-
/** Creates the number of the projection at `position`, by which `ORDER BY` sorts by its value instead of evaluating an expression again. */
|
|
1394
|
-
function createPositionNode(position) {
|
|
1395
|
-
return createNode({
|
|
1396
|
-
kind: "raw",
|
|
1397
|
-
fragments: [String(position + 1)],
|
|
1398
|
-
values: []
|
|
1399
|
-
});
|
|
1400
|
-
}
|
|
1401
|
-
/** Creates the order of a statement that sorts by the positions of the projections among `projections` that have the values of its sort expressions, and by the other expressions as they are. */
|
|
1402
|
-
function sortByPositions(orderBy, projections) {
|
|
1403
|
-
const result = [];
|
|
1404
|
-
for (const order of orderBy) {
|
|
1405
|
-
const position = findSortProjection(projections, order.expression);
|
|
1406
|
-
result.push(position === -1 ? order : Object.freeze({
|
|
1407
|
-
expression: createPositionNode(position),
|
|
1408
|
-
direction: order.direction,
|
|
1409
|
-
nulls: order.nulls
|
|
1410
|
-
}));
|
|
1411
|
-
}
|
|
1412
|
-
return result;
|
|
1413
|
-
}
|
|
1414
|
-
/**
|
|
1415
|
-
* Creates the derived source of the rows of `query`, and, when `ordered`, the order of the query over the projections of the source, in which its reader takes the rows, such as an aggregate.
|
|
1416
|
-
*
|
|
1417
|
-
* The order of a derived source does not carry into the query that reads it, so the reader sorts by sort keys: the projections that have the values of the sort expressions, as for an executed query, or projections of the sort expressions added to the source.
|
|
1418
|
-
* With a limit or an offset, the source sorts by the positions of the sort keys, so that the rows it chooses, their values, and their order in the reader come from one evaluation; without them, it has no order.
|
|
1419
|
-
* `offset` replaces the offset of `query` in the source, such as `OFFSET 0` that keeps PostgreSQL from merging it, and the source replaces the occurrence of a table `replaces` for its reader.
|
|
1420
|
-
*/
|
|
1421
|
-
function deriveOrderedSource(query, ordered, offset = query.offset, replaces) {
|
|
1422
|
-
const { orderBy } = query;
|
|
1423
|
-
const projections = query.projections.slice();
|
|
1424
|
-
const paginated = query.limit !== void 0 || query.offset !== void 0;
|
|
1425
|
-
const sortKeys = [];
|
|
1426
|
-
const positions = [];
|
|
1427
|
-
if (orderBy.length > 0 && (ordered || paginated)) for (let index = 0; index < orderBy.length; index++) {
|
|
1428
|
-
const { expression, direction, nulls } = orderBy[index];
|
|
1429
|
-
const position = projectSortKey(projections, expression, index);
|
|
1430
|
-
if (ordered) sortKeys.push({
|
|
1431
|
-
key: projections[position].key,
|
|
1432
|
-
direction,
|
|
1433
|
-
nulls
|
|
1434
|
-
});
|
|
1435
|
-
if (paginated) positions.push(Object.freeze({
|
|
1436
|
-
expression: createPositionNode(position),
|
|
1437
|
-
direction,
|
|
1438
|
-
nulls
|
|
1439
|
-
}));
|
|
1440
|
-
}
|
|
1441
|
-
const derivedQuery = Object.freeze({
|
|
1442
|
-
...query,
|
|
1443
|
-
projections,
|
|
1444
|
-
orderBy: positions,
|
|
1445
|
-
offset
|
|
1446
|
-
});
|
|
1447
|
-
const source = Object.freeze(replaces === void 0 ? {
|
|
1448
|
-
kind: "derived",
|
|
1449
|
-
query: derivedQuery,
|
|
1450
|
-
lateral: false
|
|
1451
|
-
} : {
|
|
1452
|
-
kind: "derived",
|
|
1453
|
-
query: derivedQuery,
|
|
1454
|
-
lateral: false,
|
|
1455
|
-
replaces
|
|
1456
|
-
});
|
|
1457
|
-
const order = [];
|
|
1458
|
-
for (const { key, direction, nulls } of sortKeys) order.push(Object.freeze({
|
|
1459
|
-
expression: createNode({
|
|
1460
|
-
kind: "column",
|
|
1461
|
-
source,
|
|
1462
|
-
key
|
|
1463
|
-
}),
|
|
1464
|
-
direction,
|
|
1465
|
-
nulls
|
|
1466
|
-
}));
|
|
1467
|
-
return {
|
|
1468
|
-
source,
|
|
1469
|
-
order
|
|
1470
|
-
};
|
|
1471
|
-
}
|
|
1472
|
-
/**
|
|
1473
|
-
* Returns the position of the projection that sorts by `expression`: the projection of `findSortProjection`, or one added to `projections`.
|
|
1474
|
-
*
|
|
1475
|
-
* `index` is the position of the expression in the order.
|
|
1476
|
-
*/
|
|
1477
|
-
function projectSortKey(projections, expression, index) {
|
|
1478
|
-
const position = findSortProjection(projections, expression);
|
|
1479
|
-
if (position !== -1) return position;
|
|
1480
|
-
projections.push(Object.freeze({
|
|
1481
|
-
key: createSortProjectionKey(projections, index),
|
|
1482
|
-
expression,
|
|
1483
|
-
type: text
|
|
1484
|
-
}));
|
|
1485
|
-
return projections.length - 1;
|
|
1486
|
-
}
|
|
1487
|
-
/** Creates the key of the sort key projection at `index` of the sort keys, which no other projection has. */
|
|
1488
|
-
function createSortProjectionKey(projections, index) {
|
|
1489
|
-
const base = `$order${index + 1}`;
|
|
1490
|
-
let key = base;
|
|
1491
|
-
for (let number = 2; projections.some((projection) => projection.key === key); number++) key = `${base}_${number}`;
|
|
1492
|
-
return key;
|
|
1493
|
-
}
|
|
1494
|
-
// src/lowering/nested.ts
|
|
1495
|
-
/** Maximum number of arguments of a PostgreSQL function, `FUNC_MAX_ARGS` of its default build. */
|
|
1496
|
-
const maxFunctionArguments = 100;
|
|
1497
|
-
/** Number `0`, the offset that keeps PostgreSQL from merging a derived source into its query. */
|
|
1498
|
-
const zeroNode = /* @__PURE__ */ createNode({
|
|
1499
|
-
kind: "raw",
|
|
1500
|
-
fragments: ["0"],
|
|
1501
|
-
values: []
|
|
1502
|
-
});
|
|
1503
|
-
/**
|
|
1504
|
-
* Creates the projection of the rows of `query` packed as JSON under `key`, selected as a scalar subquery and decoded by the result plan of `query`.
|
|
1505
|
-
*
|
|
1506
|
-
* Rows `many` are one array, and the other rows modes are one packed row or `NULL` without a row; a scalar subquery fails with several rows.
|
|
1507
|
-
*/
|
|
1508
|
-
function projectRows(key, query) {
|
|
1509
|
-
const packed = packRows(query);
|
|
1510
|
-
const { nested } = packed.projections[0];
|
|
1511
|
-
return Object.freeze({
|
|
1512
|
-
key,
|
|
1513
|
-
expression: createNode({
|
|
1514
|
-
kind: "subquery",
|
|
1515
|
-
query: packed
|
|
1516
|
-
}),
|
|
1517
|
-
nested
|
|
1518
|
-
});
|
|
1519
|
-
}
|
|
1520
|
-
/**
|
|
1521
|
-
* Creates a query whose only projection is the rows of `query` packed as JSON, a field decoded by the result plan of `query`.
|
|
1522
|
-
*
|
|
1523
|
-
* Rows `many` are packed as an array in the order of `query`, and the created query returns one row.
|
|
1524
|
-
* Otherwise the created query returns a packed row for each row of `query`, so a scalar subquery of it gives the packed row, `NULL` without a row, and fails with several rows.
|
|
1525
|
-
*/
|
|
1526
|
-
function packRows(query) {
|
|
1527
|
-
const { projections } = query;
|
|
1528
|
-
const many = query.rows === "many";
|
|
1529
|
-
const { source, order } = deriveOrderedSource(query, many, query.limit === void 0 && query.offset === void 0 && projections.some((projection) => projection.nested === void 0 && !isTableColumn(projection.expression)) ? zeroNode : query.offset);
|
|
1530
|
-
const wide = projections.length > maxFunctionArguments;
|
|
1531
|
-
const values = [];
|
|
1532
|
-
for (const projection of projections) {
|
|
1533
|
-
const value = createRef(source, projection.key);
|
|
1534
|
-
if (projection.nested !== void 0) values.push(value);
|
|
1535
|
-
else values.push(wide ? createList("to_json(", [packScalar(value)], ")") : packScalar(value));
|
|
1536
|
-
}
|
|
1537
|
-
const row = wide ? createList("array_to_json(ARRAY[", values, "])") : createList("json_build_array(", values, ")");
|
|
1538
|
-
let packed = row;
|
|
1539
|
-
if (many) packed = createNode({
|
|
1540
|
-
kind: "raw",
|
|
1541
|
-
fragments: ["coalesce(", ", '[]'::json)"],
|
|
1542
|
-
values: [createNode({
|
|
1543
|
-
kind: "aggregate",
|
|
1544
|
-
name: "json_agg",
|
|
1545
|
-
distinct: false,
|
|
1546
|
-
arguments: [row],
|
|
1547
|
-
orderBy: order
|
|
1548
|
-
})]
|
|
1549
|
-
});
|
|
1550
|
-
const plan = Object.freeze({
|
|
1551
|
-
fields: projections,
|
|
1552
|
-
rows: query.rows,
|
|
1553
|
-
value: query.value,
|
|
1554
|
-
guaranteedBy: query.guaranteedBy,
|
|
1555
|
-
recordTemplate: query.recordTemplate
|
|
1556
|
-
});
|
|
1557
|
-
const projection = Object.freeze({
|
|
1558
|
-
key: "rows",
|
|
1559
|
-
expression: packed,
|
|
1560
|
-
nested: plan
|
|
1561
|
-
});
|
|
1562
|
-
return createSelectNode(source, [projection]);
|
|
1563
|
-
}
|
|
1564
|
-
/** Tells whether `node` is a column of a table, which PostgreSQL reads rather than evaluates, so it can be repeated. */
|
|
1565
|
-
function isTableColumn(node) {
|
|
1566
|
-
return node.kind === "column" && node.source.kind === "table";
|
|
1567
|
-
}
|
|
1568
|
-
/**
|
|
1569
|
-
* Packs a scalar as its PostgreSQL text representation, or `NULL`.
|
|
1570
|
-
*
|
|
1571
|
-
* `format` uses the output function of the type, as the driver receives the values of a row; a cast to `text` differs from it for types such as `boolean`, `inet`, and `char`.
|
|
1572
|
-
*/
|
|
1573
|
-
function packScalar(value) {
|
|
1574
|
-
return createNode({
|
|
1575
|
-
kind: "raw",
|
|
1576
|
-
fragments: [
|
|
1577
|
-
"CASE WHEN ",
|
|
1578
|
-
" IS NOT DISTINCT FROM NULL THEN NULL ELSE format('%s', ",
|
|
1579
|
-
") END"
|
|
1580
|
-
],
|
|
1581
|
-
values: [value, value]
|
|
1582
|
-
});
|
|
1583
|
-
}
|
|
1584
|
-
function createRef(source, key) {
|
|
1585
|
-
return createNode({
|
|
1586
|
-
kind: "column",
|
|
1587
|
-
source,
|
|
1588
|
-
key
|
|
1589
|
-
});
|
|
1590
|
-
}
|
|
1591
|
-
// src/lowering/lower.ts
|
|
1592
|
-
const noJoins = /* @__PURE__ */ Object.freeze([]);
|
|
1593
|
-
const noOrder = /* @__PURE__ */ Object.freeze([]);
|
|
1594
|
-
const noProjections = /* @__PURE__ */ Object.freeze([]);
|
|
1595
|
-
function lowerStatement(node, definitions, db) {
|
|
1596
|
-
const context = {
|
|
1597
|
-
definitions,
|
|
1598
|
-
db
|
|
1599
|
-
};
|
|
1600
|
-
switch (node.kind) {
|
|
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);
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
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);
|
|
1624
|
-
switch (node.kind) {
|
|
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
|
-
}
|
|
1667
|
-
default: return node;
|
|
1668
|
-
}
|
|
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
|
-
}
|
|
1684
|
-
/** Lowers a query as `lowerQuery` does, or as `lowerStatement` does when `positions` is set. */
|
|
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);
|
|
1694
|
-
const { relation } = node;
|
|
1695
|
-
const guaranteedBy = node.guaranteed && relation !== void 0 ? formatRelationName(relation.key, relation.name) : 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;
|
|
1697
|
-
if (page !== void 0) return page;
|
|
1698
|
-
return Object.freeze({
|
|
1699
|
-
kind: "select",
|
|
1700
|
-
from: node.from,
|
|
1701
|
-
joins: noJoins,
|
|
1702
|
-
projections,
|
|
1703
|
-
filters,
|
|
1704
|
-
orderBy: positions && orderBy.length > 0 ? sortByPositions(orderBy, projections) : orderBy,
|
|
1705
|
-
limit: node.limit,
|
|
1706
|
-
offset: node.offset,
|
|
1707
|
-
rows: node.rows,
|
|
1708
|
-
value: node.value,
|
|
1709
|
-
guaranteedBy,
|
|
1710
|
-
recordTemplate
|
|
1711
|
-
});
|
|
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
|
-
}
|
|
1750
|
-
/**
|
|
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.
|
|
1753
|
-
* The derived source applies the filters, the order, the limit, and the offset, and replaces the occurrence of the table.
|
|
1754
|
-
*
|
|
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.
|
|
1756
|
-
* Such a fragment may hold an aggregate of the query, which the limit and the offset apply after.
|
|
1757
|
-
*
|
|
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.
|
|
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(*)`.
|
|
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.
|
|
1762
|
-
*/
|
|
1763
|
-
function lowerPage(node, queryFields, projections, filters, orderBy, positions, guaranteedBy, recordTemplate) {
|
|
1764
|
-
const { from } = node;
|
|
1765
|
-
const columnKeys = /* @__PURE__ */ new Set();
|
|
1766
|
-
const fields = [];
|
|
1767
|
-
const fieldKeys = [];
|
|
1768
|
-
for (let index = 0; index < projections.length; index++) {
|
|
1769
|
-
const { expression } = projections[index];
|
|
1770
|
-
let key;
|
|
1771
|
-
if (isPagedField(queryFields[index], projections[index], orderBy)) {
|
|
1772
|
-
if (holdsFragmentOfSource(expression, from)) return;
|
|
1773
|
-
collectColumnKeys(expression, from, columnKeys);
|
|
1774
|
-
} else if (expression.kind === "column" && expression.source === from) columnKeys.add(expression.key);
|
|
1775
|
-
else {
|
|
1776
|
-
key = `$field${index + 1}`;
|
|
1777
|
-
fields.push(Object.freeze({
|
|
1778
|
-
key,
|
|
1779
|
-
expression,
|
|
1780
|
-
type: text
|
|
1781
|
-
}));
|
|
1782
|
-
}
|
|
1783
|
-
fieldKeys.push(key);
|
|
1784
|
-
}
|
|
1785
|
-
const sourced = [];
|
|
1786
|
-
for (const key of columnKeys) sourced.push(Object.freeze({
|
|
1787
|
-
key,
|
|
1788
|
-
expression: createNode({
|
|
1789
|
-
kind: "column",
|
|
1790
|
-
source: from,
|
|
1791
|
-
key
|
|
1792
|
-
}),
|
|
1793
|
-
type: text
|
|
1794
|
-
}));
|
|
1795
|
-
for (const field of fields) sourced.push(field);
|
|
1796
|
-
const page = deriveOrderedSource({
|
|
1797
|
-
kind: "select",
|
|
1798
|
-
from,
|
|
1799
|
-
joins: noJoins,
|
|
1800
|
-
projections: sourced,
|
|
1801
|
-
filters,
|
|
1802
|
-
orderBy,
|
|
1803
|
-
limit: node.limit,
|
|
1804
|
-
offset: node.offset,
|
|
1805
|
-
rows: "many",
|
|
1806
|
-
value: false,
|
|
1807
|
-
guaranteedBy: void 0
|
|
1808
|
-
}, true, node.offset, from);
|
|
1809
|
-
const selected = [];
|
|
1810
|
-
for (let index = 0; index < projections.length; index++) {
|
|
1811
|
-
const projection = projections[index];
|
|
1812
|
-
const key = fieldKeys[index];
|
|
1813
|
-
selected.push(key === void 0 ? projection : Object.freeze({
|
|
1814
|
-
key: projection.key,
|
|
1815
|
-
expression: createNode({
|
|
1816
|
-
kind: "column",
|
|
1817
|
-
source: page.source,
|
|
1818
|
-
key
|
|
1819
|
-
}),
|
|
1820
|
-
type: projection.type
|
|
1821
|
-
}));
|
|
1822
|
-
}
|
|
1823
|
-
return Object.freeze({
|
|
1824
|
-
kind: "select",
|
|
1825
|
-
from: page.source,
|
|
1826
|
-
joins: noJoins,
|
|
1827
|
-
projections: selected,
|
|
1828
|
-
filters: noFilters,
|
|
1829
|
-
orderBy: positions ? sortByPositions(page.order, selected) : page.order,
|
|
1830
|
-
limit: void 0,
|
|
1831
|
-
offset: void 0,
|
|
1832
|
-
rows: node.rows,
|
|
1833
|
-
value: node.value,
|
|
1834
|
-
guaranteedBy,
|
|
1835
|
-
recordTemplate
|
|
1836
|
-
});
|
|
1837
|
-
}
|
|
1838
|
-
/** Lowers an aggregate query to a statement of the value of its aggregate over the rows that its filters, limit, and offset choose. */
|
|
1839
|
-
function lowerAggregate(node, context) {
|
|
1840
|
-
const field = node.projections[0];
|
|
1841
|
-
const aggregate = field.expression;
|
|
1842
|
-
const orderBy = lowerOrder(node.orderBy, context);
|
|
1843
|
-
let from = node.from;
|
|
1844
|
-
let filters = lowerFilters(node.filters, context);
|
|
1845
|
-
let args = lowerEach(aggregate.arguments, context, lowerExpression);
|
|
1846
|
-
let order = field.ordered ? orderBy : noOrder;
|
|
1847
|
-
const counted = aggregate === countProjection.expression;
|
|
1848
|
-
if (node.limit !== void 0 || node.offset !== void 0 || !counted && !refersToSource(args[0], node.from)) {
|
|
1849
|
-
const projections = [];
|
|
1850
|
-
if (!counted) projections.push(Object.freeze({
|
|
1851
|
-
key: "value",
|
|
1852
|
-
expression: args[0],
|
|
1853
|
-
type: field.type
|
|
1854
|
-
}));
|
|
1855
|
-
const rows = deriveOrderedSource({
|
|
1856
|
-
kind: "select",
|
|
1857
|
-
from: node.from,
|
|
1858
|
-
joins: noJoins,
|
|
1859
|
-
projections,
|
|
1860
|
-
filters,
|
|
1861
|
-
orderBy: counted ? noOrder : orderBy,
|
|
1862
|
-
limit: node.limit,
|
|
1863
|
-
offset: node.offset,
|
|
1864
|
-
rows: "many",
|
|
1865
|
-
value: false,
|
|
1866
|
-
guaranteedBy: void 0
|
|
1867
|
-
}, field.ordered);
|
|
1868
|
-
from = rows.source;
|
|
1869
|
-
filters = noFilters;
|
|
1870
|
-
if (!counted) {
|
|
1871
|
-
const sourceArgs = [createNode({
|
|
1872
|
-
kind: "column",
|
|
1873
|
-
source: from,
|
|
1874
|
-
key: "value"
|
|
1875
|
-
})];
|
|
1876
|
-
for (let index = 1; index < args.length; index++) sourceArgs.push(args[index]);
|
|
1877
|
-
args = sourceArgs;
|
|
1878
|
-
}
|
|
1879
|
-
order = rows.order;
|
|
1880
|
-
}
|
|
1881
|
-
const call = args === aggregate.arguments && order.length === 0 ? aggregate : createNode({
|
|
1882
|
-
kind: "aggregate",
|
|
1883
|
-
name: aggregate.name,
|
|
1884
|
-
distinct: aggregate.distinct,
|
|
1885
|
-
arguments: args,
|
|
1886
|
-
orderBy: order
|
|
1887
|
-
});
|
|
1888
|
-
const fallbacks = lowerEach(field.fallbacks, context, lowerExpression);
|
|
1889
|
-
let value = call;
|
|
1890
|
-
if (fallbacks.length > 0) {
|
|
1891
|
-
const values = [call];
|
|
1892
|
-
for (const fallback of fallbacks) values.push(fallback);
|
|
1893
|
-
value = createList("coalesce(", values, ")");
|
|
1894
|
-
}
|
|
1895
|
-
return Object.freeze({
|
|
1896
|
-
kind: "select",
|
|
1897
|
-
from,
|
|
1898
|
-
joins: noJoins,
|
|
1899
|
-
projections: [value === aggregate ? field : Object.freeze({
|
|
1900
|
-
key: field.key,
|
|
1901
|
-
expression: value,
|
|
1902
|
-
type: field.type
|
|
1903
|
-
})],
|
|
1904
|
-
filters,
|
|
1905
|
-
orderBy: noOrder,
|
|
1906
|
-
limit: void 0,
|
|
1907
|
-
offset: void 0,
|
|
1908
|
-
rows: node.rows,
|
|
1909
|
-
value: true,
|
|
1910
|
-
guaranteedBy: void 0
|
|
1911
|
-
});
|
|
1912
|
-
}
|
|
1913
|
-
/**
|
|
1914
|
-
* Tells whether `node` refers to `source` outside the queries nested in it.
|
|
1915
|
-
*
|
|
1916
|
-
* A reference inside a nested query is not counted, so an argument that refers to the source only there is read from a derived source, which is correct either way.
|
|
1917
|
-
*/
|
|
1918
|
-
function refersToSource(node, source) {
|
|
1919
|
-
switch (node.kind) {
|
|
1920
|
-
case "column": return node.source === source;
|
|
1921
|
-
case "param":
|
|
1922
|
-
case "boolean":
|
|
1923
|
-
case "values":
|
|
1924
|
-
case "nestedQuery":
|
|
1925
|
-
case "tableName":
|
|
1926
|
-
case "subquery":
|
|
1927
|
-
case "exists":
|
|
1928
|
-
case "textSubquery": return false;
|
|
1929
|
-
case "cast":
|
|
1930
|
-
case "not":
|
|
1931
|
-
case "isNull":
|
|
1932
|
-
case "inQuery": return refersToSource(node.operand, source);
|
|
1933
|
-
case "and":
|
|
1934
|
-
case "or": return node.operands.some((operand) => refersToSource(operand, source));
|
|
1935
|
-
case "comparison": return refersToSource(node.left, source) || refersToSource(node.right, source);
|
|
1936
|
-
case "in": return refersToSource(node.operand, source) || node.list.some((item) => refersToSource(item, source));
|
|
1937
|
-
case "raw": return node.values.some((value) => refersToSource(value, source));
|
|
1938
|
-
case "aggregate": return node.arguments.some((argument) => refersToSource(argument, source));
|
|
1939
|
-
default: return node;
|
|
1940
|
-
}
|
|
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
|
-
}
|
|
1986
|
-
/** Lowers a query of `$select` to a statement without a source, which returns one row of its fields. */
|
|
1987
|
-
function lowerRecord(node, context) {
|
|
1988
|
-
return Object.freeze({
|
|
1989
|
-
kind: "select",
|
|
1990
|
-
from: void 0,
|
|
1991
|
-
joins: noJoins,
|
|
1992
|
-
projections: lowerProjections(dropLiterals(node.projections), context),
|
|
1993
|
-
filters: noFilters,
|
|
1994
|
-
orderBy: noOrder,
|
|
1995
|
-
limit: void 0,
|
|
1996
|
-
offset: void 0,
|
|
1997
|
-
rows: "one",
|
|
1998
|
-
value: false,
|
|
1999
|
-
guaranteedBy: void 0,
|
|
2000
|
-
recordTemplate: createRecordTemplate(node.projections)
|
|
2001
|
-
});
|
|
2002
|
-
}
|
|
2003
|
-
/**
|
|
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.
|
|
2005
|
-
*
|
|
2006
|
-
* Without queries, it returns `selected` itself.
|
|
2007
|
-
*/
|
|
2008
|
-
function lowerProjections(selected, context) {
|
|
2009
|
-
let lowered;
|
|
2010
|
-
for (let index = 0; index < selected.length; index++) {
|
|
2011
|
-
const field = selected[index];
|
|
2012
|
-
let projection;
|
|
2013
|
-
if (isNestedSelection(field)) {
|
|
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
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
return lowered ?? selected;
|
|
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
|
-
}
|
|
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. */
|
|
2039
|
-
function projectAggregateValue(key, query) {
|
|
2040
|
-
return Object.freeze({
|
|
2041
|
-
key,
|
|
2042
|
-
expression: createNode({
|
|
2043
|
-
kind: "subquery",
|
|
2044
|
-
query
|
|
2045
|
-
}),
|
|
2046
|
-
type: query.projections[0].type
|
|
2047
|
-
});
|
|
2048
|
-
}
|
|
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);
|
|
2061
|
-
}
|
|
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
|
-
}));
|
|
2082
|
-
}
|
|
2083
|
-
return lowered ?? orderBy;
|
|
2084
|
-
}
|
|
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;
|
|
2088
|
-
}
|
|
2089
|
-
/**
|
|
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.
|
|
2092
|
-
*
|
|
2093
|
-
* It returns `node` itself when it holds no query, and creates only the nodes that hold one.
|
|
2094
|
-
*/
|
|
2095
|
-
function lowerExpression(node, context) {
|
|
2096
|
-
switch (node.kind) {
|
|
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
|
-
});
|
|
2127
|
-
}
|
|
2128
|
-
case "and":
|
|
2129
|
-
case "or": {
|
|
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
|
-
});
|
|
2182
|
-
}
|
|
2183
|
-
default: return node;
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
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)
|
|
2198
|
-
});
|
|
2199
|
-
}
|
|
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);
|
|
2204
|
-
}
|
|
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
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
return lowered ?? items;
|
|
2218
|
-
}
|
|
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);
|
|
2223
|
-
}
|
|
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);
|
|
2230
|
-
}
|
|
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);
|
|
2233
|
-
}
|
|
2234
|
-
// src/query/record.ts
|
|
2235
|
-
/**
|
|
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.
|
|
2237
|
-
*
|
|
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`.
|
|
2239
|
-
* A bound query executes on its `Db` when awaited.
|
|
2240
|
-
*/
|
|
2241
|
-
var RecordQuery = class {
|
|
2242
|
-
node;
|
|
2243
|
-
/** `Db` that executes the query when it is awaited; `undefined` for an unbound query. */
|
|
2244
|
-
db;
|
|
2245
|
-
/** Executes a bound query on its `Db`; `undefined` for an unbound query, so that it is not thenable. */
|
|
2246
|
-
then;
|
|
2247
|
-
constructor(node, db) {
|
|
2248
|
-
this.node = node;
|
|
2249
|
-
this.db = db;
|
|
2250
|
-
this.then = db === void 0 ? void 0 : executeOnAwait;
|
|
2251
|
-
Object.freeze(this);
|
|
2252
|
-
}
|
|
2253
|
-
toSQL(options) {
|
|
2254
|
-
return compileToSQL(this, options);
|
|
2255
|
-
}
|
|
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");
|
|
2259
|
-
}
|
|
2260
|
-
};
|
|
2261
|
-
/**
|
|
2262
|
-
* Starts the query of `$select` of `selection`, an object of expressions, queries, and literals by key, bound to `db` when given.
|
|
2263
|
-
*
|
|
2264
|
-
* It reads the keys and values of `selection` when it is called.
|
|
2265
|
-
*/
|
|
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);
|
|
2273
|
-
}
|
|
2274
|
-
// src/query/sql-query.ts
|
|
2275
|
-
/**
|
|
2276
|
-
* Immutable query of a statement of SQL text, bound to the `Db` that executes it when awaited.
|
|
2277
|
-
*
|
|
2278
|
-
* Without a selection, it resolves to the number of the rows of the command tag of its statement, such as the rows that an `UPDATE` changes; `select`, `get`, and `pluck` return the fields of the rows of the statement instead, decoded by types.
|
|
2279
|
-
*/
|
|
2280
|
-
var SqlQuery = class SqlQuery {
|
|
2281
|
-
node;
|
|
2282
|
-
db;
|
|
2283
|
-
/** Executes the query on its `Db`. */
|
|
2284
|
-
then;
|
|
2285
|
-
constructor(node, db) {
|
|
2286
|
-
this.node = node;
|
|
2287
|
-
this.db = db;
|
|
2288
|
-
this.then = executeOnAwait;
|
|
2289
|
-
Object.freeze(this);
|
|
2290
|
-
}
|
|
2291
|
-
select(fields) {
|
|
2292
|
-
const { node } = this;
|
|
2293
|
-
if (node.fields !== void 0 && node.value) throw new Error("select cannot be used after get or pluck; call get or pluck to replace the value");
|
|
2294
|
-
if (typeof fields !== "object" || fields === null) throw new TypeError("select takes an object of the types of the fields by their columns");
|
|
2295
|
-
const added = Reflect.ownKeys(fields);
|
|
2296
|
-
const selected = [];
|
|
2297
|
-
for (const field of node.fields ?? []) if (!added.includes(field.key)) selected.push(field);
|
|
2298
|
-
for (const key of added) {
|
|
2299
|
-
if (typeof key === "symbol") throw new TypeError("A selected field must have a string key");
|
|
2300
|
-
if (!isRecordKey(key)) throw new Error(`Field "${key}" cannot be a key of a record`);
|
|
2301
|
-
selected.push(Object.freeze({
|
|
2302
|
-
key,
|
|
2303
|
-
type: requireType(fields[key], `field "${key}"`)
|
|
2304
|
-
}));
|
|
2305
|
-
}
|
|
2306
|
-
return this.derive({
|
|
2307
|
-
fields: selected,
|
|
2308
|
-
value: false
|
|
2309
|
-
});
|
|
2310
|
-
}
|
|
2311
|
-
get(type) {
|
|
2312
|
-
const { rows } = this.node;
|
|
2313
|
-
return this.deriveValue(type, "get", rows === "many" ? "one" : toValueRows(rows));
|
|
2314
|
-
}
|
|
2315
|
-
pluck(type) {
|
|
2316
|
-
return this.deriveValue(type, "pluck", toValueRows(this.node.rows));
|
|
2317
|
-
}
|
|
2318
|
-
take() {
|
|
2319
|
-
const { rows } = this.node;
|
|
2320
|
-
return this.derive({ rows: rows === "many" ? "one" : rows });
|
|
2321
|
-
}
|
|
2322
|
-
optional() {
|
|
2323
|
-
return this.changeOneRow("optional");
|
|
2324
|
-
}
|
|
2325
|
-
nullable() {
|
|
2326
|
-
const { node } = this;
|
|
2327
|
-
if (node.fields !== void 0 && node.value) throw new Error("nullable applies only to a query of records; use optional after get or pluck");
|
|
2328
|
-
return this.changeOneRow("nullable");
|
|
2329
|
-
}
|
|
2330
|
-
/** Compiles the query into SQL text and parameters, as its `Db` executes it. */
|
|
2331
|
-
toSQL() {
|
|
2332
|
-
return this.db.$compile(this);
|
|
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
|
-
}
|
|
2338
|
-
/** Creates a query of the same `Db` with the node changes applied. */
|
|
2339
|
-
derive(changes) {
|
|
2340
|
-
return new SqlQuery(Object.freeze({
|
|
2341
|
-
...this.node,
|
|
2342
|
-
...changes
|
|
2343
|
-
}), this.db);
|
|
2344
|
-
}
|
|
2345
|
-
/** Creates the query that returns the value of the only column of each row in rows mode `rows`, decoded by `type` of `method`. */
|
|
2346
|
-
deriveValue(type, method, rows) {
|
|
2347
|
-
const field = Object.freeze({
|
|
2348
|
-
key: "value",
|
|
2349
|
-
type: requireType(type, method)
|
|
2350
|
-
});
|
|
2351
|
-
return this.derive({
|
|
2352
|
-
fields: [field],
|
|
2353
|
-
value: true,
|
|
2354
|
-
rows
|
|
2355
|
-
});
|
|
2356
|
-
}
|
|
2357
|
-
/** Creates the query of one row in rows mode `rows`, rejecting a query of many rows. */
|
|
2358
|
-
changeOneRow(rows) {
|
|
2359
|
-
if (this.node.rows === "many") throw new Error(`${rows} applies only to a query of one row, such as one made by take`);
|
|
2360
|
-
return this.derive({ rows });
|
|
2361
|
-
}
|
|
2362
|
-
};
|
|
2363
|
-
/** Starts the query of the statement of `sql` bound to `db`, which resolves to the number of the rows of its command tag. */
|
|
2364
|
-
function startSqlQuery(sql, db) {
|
|
2365
|
-
return new SqlQuery(Object.freeze({
|
|
2366
|
-
kind: "text",
|
|
2367
|
-
sql,
|
|
2368
|
-
fields: void 0,
|
|
2369
|
-
rows: "many",
|
|
2370
|
-
value: false
|
|
2371
|
-
}), db);
|
|
2372
|
-
}
|
|
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);
|
|
2479
|
-
}
|
|
2480
|
-
// src/query/values.ts
|
|
2481
|
-
/**
|
|
2482
|
-
* Returns the columns and the rows of `VALUES` of an insert of `records`, at least one, into `table`, reading the keys and values of each record when it is called.
|
|
2483
|
-
*
|
|
2484
|
-
* The columns are those that some record gives a value other than `undefined`, in the order that the records first give them, and a record that omits one inserts its `DEFAULT`.
|
|
2485
|
-
* Several records without values insert the `DEFAULT` of the first column of the table, since `DEFAULT VALUES` inserts one row.
|
|
2486
|
-
* It rejects a key that is not a column of `table`, and a query as a value.
|
|
2487
|
-
*/
|
|
2488
|
-
function toInsertRows(table, records, method) {
|
|
2489
|
-
if (records.length === 0) throw new Error(`${method} takes at least one record`);
|
|
2490
|
-
const columns = [];
|
|
2491
|
-
const rows = [];
|
|
2492
|
-
for (const record of records) {
|
|
2493
|
-
if (typeof record !== "object" || record === null) throw new TypeError(`${method} takes records of the values of the columns of table "${table.name}"`);
|
|
2494
|
-
const row = [];
|
|
2495
|
-
for (const key of Object.keys(record)) {
|
|
2496
|
-
const value = record[key];
|
|
2497
|
-
if (value === void 0) continue;
|
|
2498
|
-
let index = columns.indexOf(key);
|
|
2499
|
-
if (index === -1) index = columns.push(key) - 1;
|
|
2500
|
-
row[index] = resolveValue(table, key, value);
|
|
2501
|
-
}
|
|
2502
|
-
rows.push(row);
|
|
2503
|
-
}
|
|
2504
|
-
if (columns.length === 0 && rows.length > 1) {
|
|
2505
|
-
const first = Object.keys(table.columns)[0];
|
|
2506
|
-
if (first !== void 0) columns.push(first);
|
|
2507
|
-
}
|
|
2508
|
-
for (const row of rows) while (row.length < columns.length) row.push(void 0);
|
|
2509
|
-
return {
|
|
2510
|
-
columns,
|
|
2511
|
-
values: rows
|
|
2512
|
-
};
|
|
2513
|
-
}
|
|
2514
|
-
/**
|
|
2515
|
-
* Returns the assignments of an update of the rows of `source` that `set` gives: the values of its columns, or a callback that receives the columns and returns them, read when it is called.
|
|
2516
|
-
*
|
|
2517
|
-
* A value of `undefined` keeps its column, and an update must set at least one column.
|
|
2518
|
-
* It rejects a key that is not a column of the table of `source`, and a query as a value.
|
|
2519
|
-
*/
|
|
2520
|
-
function toAssignments(source, set) {
|
|
2521
|
-
const { table } = source;
|
|
2522
|
-
const values = typeof set === "function" ? set(createColumnRefs(source)) : set;
|
|
2523
|
-
if (typeof values !== "object" || values === null) throw new TypeError(`update takes the values of the columns of table "${table.name}" or a callback that returns them`);
|
|
2524
|
-
const assignments = [];
|
|
2525
|
-
for (const key of Object.keys(values)) {
|
|
2526
|
-
const value = values[key];
|
|
2527
|
-
if (value !== void 0) assignments.push(Object.freeze({
|
|
2528
|
-
key,
|
|
2529
|
-
value: resolveValue(table, key, value)
|
|
2530
|
-
}));
|
|
2531
|
-
}
|
|
2532
|
-
if (assignments.length === 0) throw new Error(`update takes the value of at least one column of table "${table.name}"; a column given as undefined keeps its value`);
|
|
2533
|
-
return assignments;
|
|
2534
|
-
}
|
|
2535
|
-
/** Returns the node of `value` of the column of `table` under `key`: an expression, or a parameter encoded with the type of the column. */
|
|
2536
|
-
function resolveValue(table, key, value) {
|
|
2537
|
-
const column = requireColumn(table, key);
|
|
2538
|
-
if (value instanceof SelectQuery || value instanceof RecordQuery || value instanceof MutationQuery || value instanceof SqlQuery) throw new TypeError(`Column "${key}" of table "${table.name}" takes a value or an expression, not a query`);
|
|
2539
|
-
return toValueNode(value, column.type);
|
|
2540
|
-
}
|
|
2541
|
-
// src/query/mutation.ts
|
|
2542
|
-
/**
|
|
2543
|
-
* Immutable query of state `S` that inserts, updates, or deletes rows, unbound or bound to a `Db`.
|
|
2544
|
-
*
|
|
2545
|
-
* Without a selection, it resolves to the number of the rows it changes; `select`, `get`, and `pluck` return the fields of those rows instead.
|
|
2546
|
-
* An unbound query is not thenable, so it can be returned from async functions; it is compiled with `toSQL` or executed by a `Db`.
|
|
2547
|
-
* A bound query executes on its `Db` when awaited.
|
|
2548
|
-
*/
|
|
2549
|
-
var MutationQuery = class MutationQuery {
|
|
2550
|
-
table;
|
|
2551
|
-
node;
|
|
2552
|
-
context;
|
|
2553
|
-
/** `Db` that executes the query when it is awaited; `undefined` for an unbound query. */
|
|
2554
|
-
db;
|
|
2555
|
-
/** Executes a bound query on its `Db`; `undefined` for an unbound query, so that it is not thenable. */
|
|
2556
|
-
then;
|
|
2557
|
-
constructor(table, node, context, db) {
|
|
2558
|
-
this.table = table;
|
|
2559
|
-
this.node = node;
|
|
2560
|
-
this.context = context;
|
|
2561
|
-
this.db = db;
|
|
2562
|
-
this.then = db === void 0 ? void 0 : executeOnAwait;
|
|
2563
|
-
Object.freeze(this);
|
|
2564
|
-
}
|
|
2565
|
-
select(...args) {
|
|
2566
|
-
const { node } = this;
|
|
2567
|
-
const { returning } = node;
|
|
2568
|
-
if (returning !== void 0 && node.value) throw new Error("select cannot be used after get or pluck; call get or pluck to replace the value");
|
|
2569
|
-
return this.derive({
|
|
2570
|
-
returning: mergeProjections(returning ?? [], node.target, args, this.context),
|
|
2571
|
-
value: false
|
|
2572
|
-
});
|
|
2573
|
-
}
|
|
2574
|
-
get(arg) {
|
|
2575
|
-
if (this.node.rows === "many") throw new Error("get applies only to a query that changes one row, such as of insert or find; use pluck to return the values of several rows");
|
|
2576
|
-
return this.deriveValue(arg, "get");
|
|
2577
|
-
}
|
|
2578
|
-
pluck(arg) {
|
|
2579
|
-
return this.deriveValue(arg, "pluck");
|
|
2580
|
-
}
|
|
2581
|
-
toSQL(options) {
|
|
2582
|
-
return compileToSQL(this, options);
|
|
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
|
-
}
|
|
2588
|
-
/** Creates a mutation of the same kind and `Db` with the node changes applied. */
|
|
2589
|
-
derive(changes) {
|
|
2590
|
-
return new MutationQuery(this.table, Object.freeze({
|
|
2591
|
-
...this.node,
|
|
2592
|
-
...changes
|
|
2593
|
-
}), this.context, this.db);
|
|
2594
|
-
}
|
|
2595
|
-
/** Creates the mutation that returns the value of `arg` of `method`, a column key or a callback that receives the columns and returns an expression, of each changed row. */
|
|
2596
|
-
deriveValue(arg, method) {
|
|
2597
|
-
const { node } = this;
|
|
2598
|
-
return this.derive({
|
|
2599
|
-
returning: [projectValue(node.target, arg, method)],
|
|
2600
|
-
value: true,
|
|
2601
|
-
rows: toValueRows(node.rows)
|
|
2602
|
-
});
|
|
2603
|
-
}
|
|
2604
|
-
};
|
|
2605
|
-
/**
|
|
2606
|
-
* Creates the insert of `records` into the table of `query`, which must have no clauses, in rows mode `rows`: `one` for `insert` and `many` for `insertMany`, named by `method`.
|
|
2607
|
-
*
|
|
2608
|
-
* It returns the number of the inserted rows, until a selection returns their fields.
|
|
2609
|
-
*/
|
|
2610
|
-
function createInsert(query, records, rows, method) {
|
|
2611
|
-
const { node } = query;
|
|
2612
|
-
if (!isTableQuery(node)) throw new Error(`${method} applies only to a query of a table without clauses, such as filters or selected fields; select the fields that it returns after it`);
|
|
2613
|
-
const { columns, values } = toInsertRows(node.from.table, records, method);
|
|
2614
|
-
return new MutationQuery(query.table, Object.freeze({
|
|
2615
|
-
kind: "insert",
|
|
2616
|
-
target: node.from,
|
|
2617
|
-
columns,
|
|
2618
|
-
values,
|
|
2619
|
-
returning: void 0,
|
|
2620
|
-
rows,
|
|
2621
|
-
value: false
|
|
2622
|
-
}), query.context, query.db);
|
|
2623
|
-
}
|
|
2624
|
-
/** Creates the update of the rows of `query` that `set` gives, as `update` takes it, in the rows mode of the query. */
|
|
2625
|
-
function createUpdate(query, set) {
|
|
2626
|
-
const { node } = query;
|
|
2627
|
-
checkChangedRows(node, "update");
|
|
2628
|
-
return new MutationQuery(query.table, Object.freeze({
|
|
2629
|
-
kind: "update",
|
|
2630
|
-
target: node.from,
|
|
2631
|
-
set: toAssignments(node.from, set),
|
|
2632
|
-
filters: node.filters,
|
|
2633
|
-
returning: void 0,
|
|
2634
|
-
rows: node.rows,
|
|
2635
|
-
value: false
|
|
2636
|
-
}), query.context, query.db);
|
|
2637
|
-
}
|
|
2638
|
-
/** Creates the delete of the rows of `query`, in the rows mode of the query. */
|
|
2639
|
-
function createDelete(query) {
|
|
2640
|
-
const { node } = query;
|
|
2641
|
-
checkChangedRows(node, "delete");
|
|
2642
|
-
return new MutationQuery(query.table, Object.freeze({
|
|
2643
|
-
kind: "delete",
|
|
2644
|
-
target: node.from,
|
|
2645
|
-
filters: node.filters,
|
|
2646
|
-
returning: void 0,
|
|
2647
|
-
rows: node.rows,
|
|
2648
|
-
value: false
|
|
2649
|
-
}), query.context, query.db);
|
|
2650
|
-
}
|
|
2651
|
-
/**
|
|
2652
|
-
* Rejects a query whose rows `method`, `update` or `delete`, cannot change: one of a relation, with selected fields, which a mutation selects after it, with an order, a limit, or an offset, or without a filter that restricts its rows unless `all` allows it.
|
|
2653
|
-
*
|
|
2654
|
-
* A filter simplified to `TRUE`, such as `where({})`, restricts no row.
|
|
2655
|
-
*/
|
|
2656
|
-
function checkChangedRows(node, method) {
|
|
2657
|
-
if (node.relation !== void 0) throw new Error(`${method} applies only to a query of a table, not of a relation`);
|
|
2658
|
-
if (node.projections.length > 0) throw new Error(`${method} returns the fields selected after it, not before; call select, get, or pluck after ${method}`);
|
|
2659
|
-
if (node.orderBy.length > 0 || node.limit !== void 0 || node.offset !== void 0) throw new Error(`${method} cannot sort or limit the rows it changes; choose them with where, find, or findBy instead of take, limit, offset, or orderBy`);
|
|
2660
|
-
const predicate = combineFilters(node.filters);
|
|
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`);
|
|
2662
|
-
}
|
|
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"
|
|
2689
|
-
}
|
|
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);
|
|
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));
|
|
2712
|
-
}
|
|
2713
|
-
/**
|
|
2714
|
-
* Creates the order of `node` with the sort keys of the arguments of `orderBy` after its own.
|
|
2715
|
-
*
|
|
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.
|
|
2717
|
-
*/
|
|
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;
|
|
2733
|
-
}
|
|
2734
|
-
/**
|
|
2735
|
-
* Creates the sort key node of a sort key returned by the callback of `orderBy`.
|
|
2736
|
-
*
|
|
2737
|
-
* A query is converted by the method of `operandNode`, which rejects a query other than of one value.
|
|
2738
|
-
*/
|
|
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)}`);
|
|
2748
|
-
return Object.freeze({
|
|
2749
|
-
expression,
|
|
2750
|
-
direction: placement.direction,
|
|
2751
|
-
nulls: placement.nulls
|
|
2752
|
-
});
|
|
2753
|
-
}
|
|
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
|
|
2761
|
-
});
|
|
2762
|
-
}
|
|
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;
|
|
2792
|
-
}
|
|
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
|
|
2816
|
-
});
|
|
2817
|
-
}
|
|
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";
|
|
2824
|
-
}
|
|
2825
|
-
// src/query/select.ts
|
|
2826
|
-
/**
|
|
2827
|
-
* Immutable select query of state `S`, unbound or bound to a `Db`.
|
|
2828
|
-
*
|
|
2829
|
-
* Every method returns a query of the same kind and leaves this query unchanged.
|
|
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`.
|
|
2831
|
-
* A bound query executes on its `Db` when awaited.
|
|
2832
|
-
*/
|
|
2833
|
-
var SelectQuery = class SelectQuery {
|
|
2834
|
-
table;
|
|
2835
|
-
node;
|
|
2836
|
-
context;
|
|
2837
|
-
/** `Db` that executes the query when it is awaited; `undefined` for an unbound query. */
|
|
2838
|
-
db;
|
|
2839
|
-
/** Executes a bound query on its `Db`; `undefined` for an unbound query, so that it is not thenable. */
|
|
2840
|
-
then;
|
|
2841
|
-
constructor(table, node, context, db) {
|
|
2842
|
-
this.table = table;
|
|
2843
|
-
this.node = node;
|
|
2844
|
-
this.context = context;
|
|
2845
|
-
this.db = db;
|
|
2846
|
-
this.then = db === void 0 ? void 0 : executeOnAwait;
|
|
2847
|
-
Object.freeze(this);
|
|
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
|
-
}
|
|
2966
|
-
toSQL(options) {
|
|
2967
|
-
return compileToSQL(this, options);
|
|
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
|
-
}
|
|
3065
|
-
};
|
|
3066
|
-
/**
|
|
3067
|
-
* Returns `result`, which a callback named by `subject` returned for `query`, after checking that it is `query` or a query derived from it.
|
|
3068
|
-
*
|
|
3069
|
-
* An ancestor of `query` also passes, since only the source occurrence is compared.
|
|
3070
|
-
*/
|
|
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
8
|
// src/expression/functions.ts
|
|
3091
9
|
function coalesce(first, ...rest) {
|
|
3092
10
|
return callFunction("coalesce", first, rest);
|
|
@@ -3136,899 +54,24 @@ function toTypedExpression(value, name) {
|
|
|
3136
54
|
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
55
|
return expression;
|
|
3138
56
|
}
|
|
3139
|
-
// src/query/
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
const contexts = Object.create(null);
|
|
3143
|
-
for (const key of keys) {
|
|
3144
|
-
const named = relations[key];
|
|
3145
|
-
if (named === void 0) {
|
|
3146
|
-
contexts[key] = noContext;
|
|
3147
|
-
continue;
|
|
3148
|
-
}
|
|
3149
|
-
const getters = Object.create(null);
|
|
3150
|
-
for (const name of Object.keys(named)) {
|
|
3151
|
-
const definition = named[name];
|
|
3152
|
-
getters[name] = Object.freeze({
|
|
3153
|
-
get() {
|
|
3154
|
-
return startRelated(this, definition, contexts[definition.targetKey]);
|
|
3155
|
-
},
|
|
3156
|
-
enumerable: true
|
|
3157
|
-
});
|
|
3158
|
-
}
|
|
3159
|
-
contexts[key] = Object.freeze({ relations: Object.freeze(getters) });
|
|
3160
|
-
}
|
|
3161
|
-
return Object.freeze(contexts);
|
|
57
|
+
// src/query/reusable-expression.ts
|
|
58
|
+
function defineExpression(_table, expression) {
|
|
59
|
+
return expression;
|
|
3162
60
|
}
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
* The relation filter compares the key columns of the target with those of `parent` with SQL `=` directly rather than with the `eq` operators of their types, which a custom type can replace.
|
|
3167
|
-
*/
|
|
3168
|
-
function startRelated(parent, definition, context) {
|
|
3169
|
-
const { target, from, to } = definition;
|
|
3170
|
-
const source = Object.freeze({
|
|
3171
|
-
kind: "table",
|
|
3172
|
-
table: target
|
|
3173
|
-
});
|
|
61
|
+
// src/query/matches.ts
|
|
62
|
+
function matches(columns, values) {
|
|
63
|
+
if (!isColumnValues(values)) throw new TypeError("The values of matches must be an object of column values");
|
|
3174
64
|
const conditions = [];
|
|
3175
|
-
for (
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
source,
|
|
3181
|
-
key: to[index]
|
|
3182
|
-
}),
|
|
3183
|
-
right: toNode(parent[from[index]])
|
|
3184
|
-
}));
|
|
3185
|
-
return new SelectQuery(target, createQueryNode(source, definition, Object.freeze({ relation: combineNodes("and", conditions) })), context);
|
|
3186
|
-
}
|
|
3187
|
-
// src/schema/schema.ts
|
|
3188
|
-
const schemaBrand = Symbol("kern-orm.schema");
|
|
3189
|
-
/** Key of the relation definitions of a schema, which composition reads. */
|
|
3190
|
-
const schemaRelations = Symbol("kern-orm.schema.relations");
|
|
3191
|
-
/**
|
|
3192
|
-
* Defines a schema of the tables of `parts`, keyed by their names in queries.
|
|
3193
|
-
*
|
|
3194
|
-
* A part is an object of tables by their keys or another schema, whose tables and relations the new schema includes as they are, so queries built from that schema run on the new one.
|
|
3195
|
-
* A key of two different tables, a table under two keys, and two different definitions of a relation of a table are rejected; the same table under the same key or the same relation in several parts is one.
|
|
3196
|
-
*/
|
|
3197
|
-
function defineSchema(...parts) {
|
|
3198
|
-
const tables = /* @__PURE__ */ new Map();
|
|
3199
|
-
const keys = /* @__PURE__ */ new Map();
|
|
3200
|
-
const relations = /* @__PURE__ */ new Map();
|
|
3201
|
-
for (const part of parts) {
|
|
3202
|
-
const partTables = isSchema(part) ? part.$tables : part;
|
|
3203
|
-
for (const key of Object.keys(partTables)) {
|
|
3204
|
-
if (key.startsWith("$")) throw new Error(`Table key "${key}" must not start with "$"`);
|
|
3205
|
-
const table = partTables[key];
|
|
3206
|
-
const keyTable = tables.get(key);
|
|
3207
|
-
if (keyTable !== void 0 && keyTable !== table) throw new Error(`Table key "${key}" refers to two different tables`);
|
|
3208
|
-
const tableKey = keys.get(table);
|
|
3209
|
-
if (tableKey !== void 0 && tableKey !== key) throw new Error(`Table "${table.name}" is included under two keys, "${tableKey}" and "${key}"`);
|
|
3210
|
-
tables.set(key, table);
|
|
3211
|
-
keys.set(table, key);
|
|
3212
|
-
}
|
|
3213
|
-
if (isSchema(part)) addRelations(relations, part[schemaRelations]);
|
|
3214
|
-
}
|
|
3215
|
-
return createSchema(Object.freeze(Object.fromEntries(tables)), keys, freezeRelations(relations));
|
|
3216
|
-
}
|
|
3217
|
-
/** Creates a schema of tables and relation definitions that are already checked; `keys` gives the key of each table. */
|
|
3218
|
-
function createSchema(tables, keys, relations) {
|
|
3219
|
-
const methods = {
|
|
3220
|
-
[schemaBrand]: true,
|
|
3221
|
-
$tables: tables,
|
|
3222
|
-
[schemaRelations]: relations,
|
|
3223
|
-
$relations(declarations) {
|
|
3224
|
-
const added = /* @__PURE__ */ new Map();
|
|
3225
|
-
addRelations(added, relations);
|
|
3226
|
-
for (const key of Object.keys(declarations)) {
|
|
3227
|
-
if (!Object.hasOwn(tables, key)) throw new Error(`Relations are declared for table key "${key}", which is not part of the schema`);
|
|
3228
|
-
const named = declarations[key];
|
|
3229
|
-
if (named === void 0) continue;
|
|
3230
|
-
for (const name of Object.keys(named)) addRelation(added, key, name, defineRelation(tables[key], key, name, named[name], (table) => keys.get(table)));
|
|
3231
|
-
}
|
|
3232
|
-
return createSchema(tables, keys, freezeRelations(added));
|
|
3233
|
-
},
|
|
3234
|
-
$select: (selection) => startRecordQuery(selection)
|
|
3235
|
-
};
|
|
3236
|
-
const contexts = createQueryContexts(Object.keys(tables), relations);
|
|
3237
|
-
return defineEntries(methods, tables, (key) => startQuery(tables[key], contexts[key]));
|
|
3238
|
-
}
|
|
3239
|
-
function isSchema(part) {
|
|
3240
|
-
return schemaBrand in part;
|
|
3241
|
-
}
|
|
3242
|
-
/** Adds a getter per table key to `methods` that starts a query with `start`, and freezes the result. */
|
|
3243
|
-
function defineEntries(methods, tables, start) {
|
|
3244
|
-
const result = Object.defineProperties(methods, Object.fromEntries(Object.keys(tables).map((key) => [key, {
|
|
3245
|
-
get: () => start(key),
|
|
3246
|
-
enumerable: true
|
|
3247
|
-
}])));
|
|
3248
|
-
return Object.freeze(result);
|
|
3249
|
-
}
|
|
3250
|
-
// src/expression/sql.ts
|
|
3251
|
-
function sql(first, ...values) {
|
|
3252
|
-
if (isTemplateStrings(first)) return createFragment(first, values, void 0);
|
|
3253
|
-
if (!isType(first)) throw new TypeError("sql() accepts a type, not a column");
|
|
3254
|
-
return (strings, ...values) => createFragment(strings, values, first);
|
|
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
|
-
}
|
|
3276
|
-
function param(type, value) {
|
|
3277
|
-
if (!isType(type) && !isExpression(type)) throw new TypeError("param() accepts a type or an expression, not a column");
|
|
3278
|
-
if (isExpression(type)) {
|
|
3279
|
-
if (isExpression(value)) return value;
|
|
3280
|
-
const valueType = getType(type);
|
|
3281
|
-
return toExpression(toValueNode(value, valueType), valueType);
|
|
3282
|
-
}
|
|
3283
|
-
return toExpression(toCastValueNode(value, type), type);
|
|
3284
|
-
}
|
|
3285
|
-
function isTemplateStrings(value) {
|
|
3286
|
-
return Array.isArray(value);
|
|
3287
|
-
}
|
|
3288
|
-
function createFragment(strings, values, type) {
|
|
3289
|
-
return toExpression(toRawSqlNode(strings, values), type);
|
|
3290
|
-
}
|
|
3291
|
-
/**
|
|
3292
|
-
* Creates the node of the SQL of a template, whose interpolations stay structured nodes.
|
|
3293
|
-
*
|
|
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.
|
|
3298
|
-
*/
|
|
3299
|
-
function toRawSqlNode(strings, values) {
|
|
3300
|
-
const nodes = [];
|
|
3301
|
-
for (let index = 0; index < values.length; index++) nodes.push(toInterpolatedNode(values[index]));
|
|
3302
|
-
return createNode({
|
|
3303
|
-
kind: "raw",
|
|
3304
|
-
fragments: [...strings],
|
|
3305
|
-
values: nodes
|
|
3306
|
-
});
|
|
3307
|
-
}
|
|
3308
|
-
/** Returns the node of a value interpolated into SQL, as `toRawSqlNode` takes it. */
|
|
3309
|
-
function toInterpolatedNode(value) {
|
|
3310
|
-
if (value instanceof SelectQuery) {
|
|
3311
|
-
const { node } = value;
|
|
3312
|
-
return isTableQuery(node) ? createNode({
|
|
3313
|
-
kind: "tableName",
|
|
3314
|
-
table: value.table,
|
|
3315
|
-
db: value.db
|
|
3316
|
-
}) : createNode({
|
|
3317
|
-
kind: "nestedQuery",
|
|
3318
|
-
query: node,
|
|
3319
|
-
db: value.db
|
|
3320
|
-
});
|
|
3321
|
-
}
|
|
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");
|
|
3323
|
-
return toValueNode(value);
|
|
3324
|
-
}
|
|
3325
|
-
// src/execution/db.ts
|
|
3326
|
-
/** Binds `schema` to `database`; see `Database.bind`. */
|
|
3327
|
-
function bindSchema(schema, database, options = {}) {
|
|
3328
|
-
const tables = new Set(Object.values(schema.$tables));
|
|
3329
|
-
const relations = schema[schemaRelations];
|
|
3330
|
-
const definitions = collectDefinitions(relations);
|
|
3331
|
-
const { schemaName } = options;
|
|
3332
|
-
const resolveSchemaName = () => typeof schemaName === "function" ? schemaName() : schemaName;
|
|
3333
|
-
function compile(query) {
|
|
3334
|
-
if (query.db !== void 0 && query.db !== db) throw new Error("Cannot run a query bound to another Db");
|
|
3335
|
-
return compileStatement(lowerStatement(query.node, definitions, db), { schemaName: resolveSchemaName() }, tables, db);
|
|
3336
|
-
}
|
|
3337
|
-
const methods = {
|
|
3338
|
-
$schema: schema,
|
|
3339
|
-
$database: database,
|
|
3340
|
-
get $schemaName() {
|
|
3341
|
-
return resolveSchemaName();
|
|
3342
|
-
},
|
|
3343
|
-
$compile(query) {
|
|
3344
|
-
const { text, parameters } = compile(query);
|
|
3345
|
-
return createStatement(text, parameters);
|
|
3346
|
-
},
|
|
3347
|
-
async $execute(query) {
|
|
3348
|
-
const compiled = compile(query);
|
|
3349
|
-
return decodeStatement(compiled, await database.execute(compiled));
|
|
3350
|
-
},
|
|
3351
|
-
$select: (selection) => startRecordQuery(selection, db),
|
|
3352
|
-
$query: (strings, ...values) => startSqlQuery(toRawSqlNode(strings, values), db),
|
|
3353
|
-
async $script(script, ...values) {
|
|
3354
|
-
if (typeof script === "string") return database.script(script);
|
|
3355
|
-
const { text, parameters } = compile(startSqlQuery(toRawSqlNode(script, values), db));
|
|
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");
|
|
3357
|
-
return database.script(text);
|
|
3358
|
-
},
|
|
3359
|
-
$transaction: (callback, options) => database.transaction(callback, options),
|
|
3360
|
-
$ensureTransaction: (callback) => database.ensureTransaction(callback),
|
|
3361
|
-
$isInTransaction: () => database.isInTransaction(),
|
|
3362
|
-
$lock: (key) => database.lock(key)
|
|
3363
|
-
};
|
|
3364
|
-
const contexts = createQueryContexts(Object.keys(schema.$tables), relations);
|
|
3365
|
-
const db = defineEntries(methods, schema.$tables, (key) => bindQuery(startQuery(schema.$tables[key], contexts[key]), db));
|
|
3366
|
-
return db;
|
|
3367
|
-
}
|
|
3368
|
-
// src/execution/transaction.ts
|
|
3369
|
-
/**
|
|
3370
|
-
* Routes statements and scripts to the ambient transaction of the current async context or to the pool.
|
|
3371
|
-
*
|
|
3372
|
-
* This is the only place that decides which connection executes a statement.
|
|
3373
|
-
*/
|
|
3374
|
-
var TransactionCoordinator = class {
|
|
3375
|
-
driver;
|
|
3376
|
-
test;
|
|
3377
|
-
#storage = new AsyncLocalStorage();
|
|
3378
|
-
/** Open test fixtures, the innermost last. */
|
|
3379
|
-
#fixtures = [];
|
|
3380
|
-
/** Changes of the fixture stack, run one after another so that each one sees the stack the previous one left. */
|
|
3381
|
-
#transitions = Promise.resolve();
|
|
3382
|
-
/**
|
|
3383
|
-
* The `close` functions of the test fixtures that are still opening, from the call that starts the opening until the fixture enters the stack or the opening ends otherwise.
|
|
3384
|
-
*
|
|
3385
|
-
* An opening holds the queue of the fixture stack while its seed runs, so a closing of the stack queued behind it would wait for a seed that may never settle.
|
|
3386
|
-
* Closing the stack therefore calls these first: each one tells its opening to stop waiting for the seed.
|
|
3387
|
-
*/
|
|
3388
|
-
#openings = /* @__PURE__ */ new Set();
|
|
3389
|
-
/**
|
|
3390
|
-
* Outermost fixture in the stack whose closing is queued but has not finished.
|
|
3391
|
-
*
|
|
3392
|
-
* Openings that start after that closing is requested nest outside of the fixture once it closes, so a repeated closing of it, or a closing of a fixture nested in it, must not close them.
|
|
3393
|
-
*/
|
|
3394
|
-
#closing;
|
|
3395
|
-
/** Closing of the database, set by the first `close`; from then on, operations outside a transaction reject. */
|
|
3396
|
-
#closed;
|
|
3397
|
-
constructor(driver, test = false) {
|
|
3398
|
-
this.driver = driver;
|
|
3399
|
-
this.test = test;
|
|
3400
|
-
}
|
|
3401
|
-
execute(statement) {
|
|
3402
|
-
const target = this.#route("execute a query");
|
|
3403
|
-
return target instanceof Error ? Promise.reject(target) : target.execute(statement);
|
|
3404
|
-
}
|
|
3405
|
-
/** Executes a script of statements without parameters where `execute` executes a statement. */
|
|
3406
|
-
executeScript(text) {
|
|
3407
|
-
const target = this.#route("execute a script");
|
|
3408
|
-
return target instanceof Error ? Promise.reject(target) : target.executeScript(text);
|
|
3409
|
-
}
|
|
3410
|
-
/** Returns the connection of the ambient transaction or the driver of the pool that runs `operation`, or the error that rejects it. */
|
|
3411
|
-
#route(operation) {
|
|
3412
|
-
const level = this.#storage.getStore();
|
|
3413
|
-
if (!level) {
|
|
3414
|
-
if (this.#closed) return createDatabaseClosedError(operation);
|
|
3415
|
-
return this.test ? new Error(describeOutsideTest(operation)) : this.driver;
|
|
3416
|
-
}
|
|
3417
|
-
return createUnavailableError(level, operation) ?? level.connection;
|
|
3418
|
-
}
|
|
3419
|
-
/**
|
|
3420
|
-
* Runs `callback` in a transaction that commits when it resolves and rolls back when it rejects, with the settings of `options`.
|
|
3421
|
-
*
|
|
3422
|
-
* Inside a transaction, it runs `callback` in a savepoint nested in it, which restores the settings it changed when it ends.
|
|
3423
|
-
* A failed cleanup after an error rejects with a `SuppressedError` that keeps the original error in its `suppressed` chain.
|
|
3424
|
-
*/
|
|
3425
|
-
transaction(callback, options) {
|
|
3426
|
-
const settings = getLevelSettings(options);
|
|
3427
|
-
const parent = this.#storage.getStore();
|
|
3428
|
-
if (parent) return this.#runNested(parent, callback, true, void 0, settings);
|
|
3429
|
-
if (this.#closed) return Promise.reject(createDatabaseClosedError("start a transaction"));
|
|
3430
|
-
return this.test ? Promise.reject(new Error(describeOutsideTest("start a transaction"))) : this.#runRoot(callback, true, void 0, settings);
|
|
3431
|
-
}
|
|
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
|
-
/**
|
|
3454
|
-
* Runs `callback` in a transaction that always rolls back, on a coordinator created for tests.
|
|
3455
|
-
*
|
|
3456
|
-
* Inside a transaction, it runs `callback` in a savepoint nested in it that always rolls back.
|
|
3457
|
-
* When `signal` aborts, the level stops accepting statements at once and ends without waiting for `callback`.
|
|
3458
|
-
* Once the level ends, the transaction rejects with the reason of the signal.
|
|
3459
|
-
* A failure to start or end the level after the abort suppresses the reason in a `SuppressedError`.
|
|
3460
|
-
* Aborted while it waits for its turn in the fixture stack or for a connection, it rejects at once; after the abort, it never calls `callback`.
|
|
3461
|
-
*/
|
|
3462
|
-
testTransaction(callback, { signal } = {}) {
|
|
3463
|
-
if (!this.test) return Promise.reject(/* @__PURE__ */ new Error("testTransaction requires a database created with test: true"));
|
|
3464
|
-
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
3465
|
-
const parent = this.#storage.getStore();
|
|
3466
|
-
if (parent) return this.#runNested(parent, callback, false, signal);
|
|
3467
|
-
if (this.#closed) return Promise.reject(createDatabaseClosedError("start a test transaction"));
|
|
3468
|
-
return this.#startTestLevel(signal).then((level) => level ? this.#runStarted(level, callback, false, signal) : this.#runRoot(callback, false, signal));
|
|
3469
|
-
}
|
|
3470
|
-
/**
|
|
3471
|
-
* Opens a test fixture: a transaction on a reserved connection, or a savepoint in the innermost open fixture, and runs `seed` in it.
|
|
3472
|
-
*
|
|
3473
|
-
* Test transactions started outside any transaction run in the innermost fixture.
|
|
3474
|
-
* A fixture whose `seed` rejects rolls back and closes.
|
|
3475
|
-
*/
|
|
3476
|
-
createTestFixture(seed) {
|
|
3477
|
-
return this.startTestFixture(seed).fixture;
|
|
3478
|
-
}
|
|
3479
|
-
/**
|
|
3480
|
-
* Starts opening a test fixture, see `createTestFixture`, and returns it with a `close` that can be called before it opens.
|
|
3481
|
-
*
|
|
3482
|
-
* Closing before the fixture opens rejects the opening without waiting for `seed` and rolls the fixture back, so a fixture or test transaction started after `close` never nests in it, even when its `seed` never settles.
|
|
3483
|
-
* Closing an open fixture also closes the fixtures opening in it.
|
|
3484
|
-
*/
|
|
3485
|
-
startTestFixture(seed) {
|
|
3486
|
-
let level;
|
|
3487
|
-
let closeRequested = false;
|
|
3488
|
-
const closing = Promise.withResolvers();
|
|
3489
|
-
let closedRejection;
|
|
3490
|
-
const close = () => {
|
|
3491
|
-
if (this.#storage.getStore()) return Promise.reject(/* @__PURE__ */ new Error("Cannot close a test fixture inside a transaction"));
|
|
3492
|
-
closeRequested = true;
|
|
3493
|
-
closing.resolve();
|
|
3494
|
-
if (level && this.#fixtures.includes(level)) {
|
|
3495
|
-
const opened = level;
|
|
3496
|
-
if (this.#closing && this.#closing.depth <= opened.depth) return this.#enqueue(() => this.#closeFixture(opened));
|
|
3497
|
-
this.#closing = opened;
|
|
3498
|
-
return this.#closeAfterOpenings(() => this.#enqueue(async () => {
|
|
3499
|
-
try {
|
|
3500
|
-
await this.#closeFixture(opened);
|
|
3501
|
-
} finally {
|
|
3502
|
-
if (this.#closing === opened) this.#closing = void 0;
|
|
3503
|
-
}
|
|
3504
|
-
}));
|
|
3505
|
-
}
|
|
3506
|
-
return this.#enqueue(() => {
|
|
3507
|
-
const rejection = closedRejection;
|
|
3508
|
-
closedRejection = void 0;
|
|
3509
|
-
if (rejection) throw rejection.error;
|
|
3510
|
-
});
|
|
3511
|
-
};
|
|
3512
|
-
if (!this.test) return {
|
|
3513
|
-
fixture: Promise.reject(/* @__PURE__ */ new Error("createTestFixture requires a database created with test: true")),
|
|
3514
|
-
close
|
|
3515
|
-
};
|
|
3516
|
-
if (this.#storage.getStore()) return {
|
|
3517
|
-
fixture: Promise.reject(/* @__PURE__ */ new Error("Cannot create a test fixture inside a transaction")),
|
|
3518
|
-
close
|
|
3519
|
-
};
|
|
3520
|
-
if (this.#closed) return {
|
|
3521
|
-
fixture: Promise.reject(createDatabaseClosedError("create a test fixture")),
|
|
3522
|
-
close
|
|
3523
|
-
};
|
|
3524
|
-
/**
|
|
3525
|
-
* Runs stages 2 and 3 of the fixture and puts it in the stack, returning `undefined` when closing was requested and the fixture rolled back cleanly.
|
|
3526
|
-
*
|
|
3527
|
-
* It rejects with a failure to start the fixture, with the rejection of `seed`, or with a failure to roll back, which suppresses the rejection of `seed`.
|
|
3528
|
-
*/
|
|
3529
|
-
const open = async () => {
|
|
3530
|
-
if (closeRequested) return;
|
|
3531
|
-
const parent = this.#fixtures.at(-1);
|
|
3532
|
-
const started = parent ? await this.#startInFixture(parent) : await this.#beginFixture();
|
|
3533
|
-
started.state = "open";
|
|
3534
|
-
const outcome = closeRequested ? void 0 : await Promise.race([this.#seedFixture(started, seed), closing.promise]);
|
|
3535
|
-
if (outcome && !outcome.rejection && !closeRequested) {
|
|
3536
|
-
this.#fixtures.push(started);
|
|
3537
|
-
level = started;
|
|
3538
|
-
this.#openings.delete(close);
|
|
3539
|
-
const { result } = outcome;
|
|
3540
|
-
return {
|
|
3541
|
-
get data() {
|
|
3542
|
-
if (started.state !== "open") throw new Error("Test fixture is closed");
|
|
3543
|
-
return result;
|
|
3544
|
-
},
|
|
3545
|
-
close,
|
|
3546
|
-
[Symbol.asyncDispose]: close
|
|
3547
|
-
};
|
|
3548
|
-
}
|
|
3549
|
-
const rejection = combineRejections(outcome?.rejection, { rejection: await this.#endFixture(started) });
|
|
3550
|
-
if (rejection) throw rejection.error;
|
|
3551
|
-
};
|
|
3552
|
-
this.#openings.add(close);
|
|
3553
|
-
return {
|
|
3554
|
-
fixture: this.#enqueue(async () => {
|
|
3555
|
-
let opened;
|
|
3556
|
-
try {
|
|
3557
|
-
opened = await open();
|
|
3558
|
-
} catch (error) {
|
|
3559
|
-
if (!closeRequested) throw error;
|
|
3560
|
-
closedRejection = { error };
|
|
3561
|
-
} finally {
|
|
3562
|
-
this.#openings.delete(close);
|
|
3563
|
-
}
|
|
3564
|
-
if (!opened) throw new Error("Test fixture was closed before it opened");
|
|
3565
|
-
return opened;
|
|
3566
|
-
}),
|
|
3567
|
-
close
|
|
3568
|
-
};
|
|
3569
|
-
}
|
|
3570
|
-
/**
|
|
3571
|
-
* Closes the test fixtures, including those still opening, and then the driver, which waits for the running transactions to end.
|
|
3572
|
-
*
|
|
3573
|
-
* A driver waits for every reserved connection, so closing it under an open fixture would wait forever.
|
|
3574
|
-
* A failed closing of the driver suppresses the failures of closing the fixtures.
|
|
3575
|
-
* From the first call, statements, transactions, and fixtures started outside a transaction reject, while the levels already running can still end; a repeated call returns the same closing.
|
|
3576
|
-
*/
|
|
3577
|
-
close() {
|
|
3578
|
-
if (this.#storage.getStore()) return Promise.reject(/* @__PURE__ */ new Error("Cannot close the database inside a transaction"));
|
|
3579
|
-
this.#closed ??= this.#close();
|
|
3580
|
-
return this.#closed;
|
|
3581
|
-
}
|
|
3582
|
-
/** Closes the test fixtures and then the driver, see `close`. */
|
|
3583
|
-
async #close() {
|
|
3584
|
-
let rejection;
|
|
3585
|
-
try {
|
|
3586
|
-
await this.#closeAfterOpenings(() => this.#enqueue(() => {
|
|
3587
|
-
const root = this.#fixtures[0];
|
|
3588
|
-
return root && this.#closeFixture(root);
|
|
3589
|
-
}));
|
|
3590
|
-
} catch (error) {
|
|
3591
|
-
rejection = { error };
|
|
3592
|
-
}
|
|
3593
|
-
try {
|
|
3594
|
-
await this.driver.close();
|
|
3595
|
-
} catch (error) {
|
|
3596
|
-
rejection = suppress(error, rejection, "Closing the driver failed");
|
|
3597
|
-
}
|
|
3598
|
-
if (rejection) throw rejection.error;
|
|
3599
|
-
}
|
|
3600
|
-
/**
|
|
3601
|
-
* Closes the fixtures still opening and runs `closeStack`, rejecting with the failures of all of them.
|
|
3602
|
-
*
|
|
3603
|
-
* A seed that never settles holds the queue of the fixture stack, so closing is requested for every opening before `closeStack` queues its change, and the rollback does not wait for the seeds.
|
|
3604
|
-
*/
|
|
3605
|
-
async #closeAfterOpenings(closeStack) {
|
|
3606
|
-
const closings = [];
|
|
3607
|
-
for (const closeOpening of this.#openings) closings.push(closeOpening());
|
|
3608
|
-
closings.push(closeStack());
|
|
3609
|
-
let rejection;
|
|
3610
|
-
for (const closing of await Promise.allSettled(closings)) if (closing.status === "rejected") rejection = suppress(closing.reason, rejection, "Closing a test fixture failed");
|
|
3611
|
-
if (rejection) throw rejection.error;
|
|
3612
|
-
}
|
|
3613
|
-
/**
|
|
3614
|
-
* Runs `seed` in a savepoint of a fixture that is released when it resolves, returning its result or rejection.
|
|
3615
|
-
*
|
|
3616
|
-
* The savepoint has its own context, so late code of `seed` cannot reach the fixture after it ends.
|
|
3617
|
-
*/
|
|
3618
|
-
async #seedFixture(fixture, seed) {
|
|
3619
|
-
try {
|
|
3620
|
-
return await this.#run(await this.#startNested(fixture, false), seed, true);
|
|
3621
|
-
} catch (error) {
|
|
3622
|
-
return { rejection: { error } };
|
|
3623
|
-
}
|
|
3624
|
-
}
|
|
3625
|
-
/** Closes an opened fixture as a change of the fixture stack. */
|
|
3626
|
-
async #closeFixture(fixture) {
|
|
3627
|
-
const index = this.#fixtures.indexOf(fixture);
|
|
3628
|
-
if (index < 0) return;
|
|
3629
|
-
let rejection;
|
|
3630
|
-
try {
|
|
3631
|
-
rejection = await this.#endFixture(fixture);
|
|
3632
|
-
} finally {
|
|
3633
|
-
this.#fixtures.length = index;
|
|
3634
|
-
}
|
|
3635
|
-
if (rejection) throw rejection.error;
|
|
3636
|
-
}
|
|
3637
|
-
/** Rolls back a fixture and releases the connection of the first one, returning the error to reject with. */
|
|
3638
|
-
async #endFixture(fixture) {
|
|
3639
|
-
const cleanup = await this.#end(fixture, false);
|
|
3640
|
-
return fixture.parent ? cleanup.rejection : releaseConnection(fixture.connection, cleanup.failure, cleanup.rejection);
|
|
3641
|
-
}
|
|
3642
|
-
/**
|
|
3643
|
-
* Starts the level of a test transaction in the innermost test fixture as a change of the fixture stack, resolving to `undefined` when no fixture is open.
|
|
3644
|
-
*
|
|
3645
|
-
* Aborted while it waits for its turn, it rejects at once and its change starts nothing, so that a test that has already ended never preempts the active level of the fixture.
|
|
3646
|
-
* From its turn on, it settles with its change.
|
|
3647
|
-
*/
|
|
3648
|
-
#startTestLevel(signal) {
|
|
3649
|
-
let waiting = true;
|
|
3650
|
-
const start = () => this.#enqueue(() => {
|
|
3651
|
-
if (signal?.aborted) return;
|
|
3652
|
-
waiting = false;
|
|
3653
|
-
const fixture = this.#fixtures.at(-1);
|
|
3654
|
-
return fixture && this.#startInFixture(fixture, signal);
|
|
3655
|
-
});
|
|
3656
|
-
return signal ? settleUnlessAborted(start, signal, () => waiting) : start();
|
|
3657
|
-
}
|
|
3658
|
-
/** Runs a change of the fixture stack after the earlier ones. */
|
|
3659
|
-
#enqueue(transition) {
|
|
3660
|
-
const result = this.#transitions.then(transition);
|
|
3661
|
-
this.#transitions = result.catch(() => {});
|
|
3662
|
-
return result;
|
|
3663
|
-
}
|
|
3664
|
-
/** Starts the first test fixture: a transaction on a reserved connection. */
|
|
3665
|
-
async #beginFixture() {
|
|
3666
|
-
const connection = await this.driver.acquire();
|
|
3667
|
-
try {
|
|
3668
|
-
await connection.execute(beginStatement);
|
|
3669
|
-
} catch (error) {
|
|
3670
|
-
throw (await releaseConnection(connection, { error }, { error })).error;
|
|
3671
|
-
}
|
|
3672
|
-
return createLevel(connection, void 0, false);
|
|
3673
|
-
}
|
|
3674
|
-
/**
|
|
3675
|
-
* Starts a nested level of a test fixture, preempting its active one.
|
|
3676
|
-
*
|
|
3677
|
-
* A fixture runs one nested level at a time, so a new one ends the previous one, such as a test that timed out.
|
|
3678
|
-
* A level whose `signal` aborts while it preempts rejects with its reason instead of starting, and a failure to start after the abort suppresses the reason.
|
|
3679
|
-
*/
|
|
3680
|
-
async #startInFixture(fixture, signal) {
|
|
3681
|
-
if (fixture.child) {
|
|
3682
|
-
const cleanup = await this.#end(fixture.child, false, "preemption");
|
|
3683
|
-
if (cleanup.rejection) throw suppressAbortReason(new Error("Cannot start a level of the test fixture: ending its active level failed", { cause: cleanup.rejection.error }), signal);
|
|
3684
|
-
}
|
|
3685
|
-
const unavailable = createUnavailableError(fixture, "start a level of the test fixture");
|
|
3686
|
-
if (unavailable) throw suppressAbortReason(unavailable, signal);
|
|
3687
|
-
signal?.throwIfAborted();
|
|
3688
|
-
return this.#startNested(fixture, false, signal);
|
|
3689
|
-
}
|
|
3690
|
-
async #runRoot(callback, commits, signal, settings) {
|
|
3691
|
-
const connection = await (signal ? acquireUnlessAborted(this.driver, signal) : this.driver.acquire());
|
|
3692
|
-
if (signal?.aborted) throw (await releaseConnection(connection, void 0, { error: signal.reason })).error;
|
|
3693
|
-
const level = createLevel(connection, void 0, commits);
|
|
3694
|
-
let outcome;
|
|
3695
|
-
try {
|
|
3696
|
-
await connection.execute(beginStatement);
|
|
3697
|
-
} catch (error) {
|
|
3698
|
-
level.state = "closed";
|
|
3699
|
-
outcome = {
|
|
3700
|
-
rejection: { error: suppressAbortReason(error, signal) },
|
|
3701
|
-
failure: { error }
|
|
3702
|
-
};
|
|
3703
|
-
}
|
|
3704
|
-
if (!outcome && settings) try {
|
|
3705
|
-
await applySettings(level, settings);
|
|
3706
|
-
} catch (error) {
|
|
3707
|
-
outcome = await getEndedOutcome(this.#end(level, false), error);
|
|
3708
|
-
}
|
|
3709
|
-
outcome ??= await this.#run(level, callback, commits, signal);
|
|
3710
|
-
const rejection = await releaseConnection(connection, outcome.failure, outcome.rejection);
|
|
3711
|
-
if (rejection) throw rejection.error;
|
|
3712
|
-
return outcome.result;
|
|
3713
|
-
}
|
|
3714
|
-
async #runNested(parent, callback, commits, signal, settings) {
|
|
3715
|
-
const unavailable = createUnavailableError(parent, "start a nested transaction");
|
|
3716
|
-
if (unavailable) throw unavailable;
|
|
3717
|
-
return this.#runStarted(await this.#startNested(parent, commits, signal, settings), callback, commits, signal);
|
|
3718
|
-
}
|
|
3719
|
-
/**
|
|
3720
|
-
* Reserves a nested level of `parent`, sends its `SAVEPOINT`, and applies its `settings`, rejecting when one of them fails.
|
|
3721
|
-
*
|
|
3722
|
-
* `transaction` tells whether the level is a transaction of `transaction` rather than a level of the test helpers.
|
|
3723
|
-
* A level whose settings fail rolls back to its savepoint first.
|
|
3724
|
-
* The failure suppresses the reason of `signal` when the signal aborted before it.
|
|
3725
|
-
*/
|
|
3726
|
-
async #startNested(parent, transaction, signal, settings) {
|
|
3727
|
-
const level = createLevel(parent.connection, parent, transaction);
|
|
3728
|
-
parent.child = level;
|
|
3729
|
-
try {
|
|
3730
|
-
await level.connection.execute(createStatement(`SAVEPOINT ${getSavepointName(level)}`));
|
|
3731
|
-
} catch (error) {
|
|
3732
|
-
if (!level.cleanup) {
|
|
3733
|
-
closeStarting(level);
|
|
3734
|
-
throw suppressAbortReason(error, signal);
|
|
3735
|
-
}
|
|
3736
|
-
throw suppressAbortReason(combineRejections({ error }, await level.cleanup).error, signal);
|
|
3737
|
-
}
|
|
3738
|
-
if (settings) try {
|
|
3739
|
-
await applySettings(level, settings);
|
|
3740
|
-
} catch (error) {
|
|
3741
|
-
throw suppressAbortReason(combineRejections({ error }, await this.#end(level, false)).error, signal);
|
|
3742
|
-
}
|
|
3743
|
-
return level;
|
|
3744
|
-
}
|
|
3745
|
-
/** Runs `callback` in a started level, returning its result or throwing its rejection. */
|
|
3746
|
-
async #runStarted(level, callback, commits, signal) {
|
|
3747
|
-
const { result, rejection } = await this.#run(level, callback, commits, signal);
|
|
3748
|
-
if (rejection) throw rejection.error;
|
|
3749
|
-
return result;
|
|
3750
|
-
}
|
|
3751
|
-
/**
|
|
3752
|
-
* Runs `callback` in the context of a started `level`, then ends the level.
|
|
3753
|
-
*
|
|
3754
|
-
* A level that `commits` commits only when the callback resolves with no nested level running and no nested level that failed to end.
|
|
3755
|
-
* When `signal` aborts, the level ends at once without waiting for the callback, and its outcome is a rejection with the reason of the signal.
|
|
3756
|
-
*/
|
|
3757
|
-
async #run(level, callback, commits, signal) {
|
|
3758
|
-
if (level.cleanup) return getEndedOutcome(level.cleanup, createClosedError(level, void 0));
|
|
3759
|
-
let result;
|
|
3760
|
-
let rejection;
|
|
3761
|
-
if (signal?.aborted) this.#end(level, false, "abort");
|
|
3762
|
-
else {
|
|
3763
|
-
level.state = "open";
|
|
3764
|
-
try {
|
|
3765
|
-
result = await (signal ? settleUnlessAborted(() => this.#storage.run(level, callback), signal, () => {
|
|
3766
|
-
this.#end(level, false, "abort");
|
|
3767
|
-
return true;
|
|
3768
|
-
}) : this.#storage.run(level, callback));
|
|
3769
|
-
} catch (error) {
|
|
3770
|
-
rejection = { error };
|
|
3771
|
-
}
|
|
3772
|
-
}
|
|
3773
|
-
if (level.cleanup) return getEndedOutcome(level.cleanup, level.endedBy === "abort" ? signal?.reason : createClosedError(level, rejection));
|
|
3774
|
-
if (!rejection && level.child) rejection = { error: /* @__PURE__ */ new Error("Transaction was rolled back: a nested transaction was still running when its callback finished") };
|
|
3775
|
-
if (!rejection && level.rollbackOnly) rejection = { error: new Error("Transaction was rolled back: a nested transaction failed to end", { cause: level.rollbackOnly.error }) };
|
|
3776
|
-
const cleanup = await this.#end(level, commits && !rejection);
|
|
3777
|
-
return {
|
|
3778
|
-
result,
|
|
3779
|
-
rejection: combineRejections(rejection, cleanup),
|
|
3780
|
-
failure: cleanup.failure
|
|
3781
|
-
};
|
|
3782
|
-
}
|
|
3783
|
-
/**
|
|
3784
|
-
* Ends `level` once, whoever ends it: later calls return the cleanup already started.
|
|
3785
|
-
*
|
|
3786
|
-
* Statements of the level and of all levels nested in it fail from the start of the cleanup, and the nested levels end before it.
|
|
3787
|
-
*/
|
|
3788
|
-
#end(level, commit, endedBy) {
|
|
3789
|
-
if (level.cleanup) return level.cleanup;
|
|
3790
|
-
level.endedBy = endedBy;
|
|
3791
|
-
level.state = "closing";
|
|
3792
|
-
const nested = level.child && this.#end(level.child, false, "enclosing level");
|
|
3793
|
-
level.cleanup = this.#cleanUp(level, commit, nested);
|
|
3794
|
-
return level.cleanup;
|
|
3795
|
-
}
|
|
3796
|
-
async #cleanUp(level, commit, nested) {
|
|
3797
|
-
const nestedCleanup = await nested;
|
|
3798
|
-
const { parent } = level;
|
|
3799
|
-
const cleanup = parent ? await endSavepoint(level, commit && !level.rollbackOnly) : await endTransaction(level.connection, commit);
|
|
3800
|
-
level.state = "closed";
|
|
3801
|
-
if (parent) {
|
|
3802
|
-
if (parent.child === level) parent.child = void 0;
|
|
3803
|
-
parent.rollbackOnly ??= cleanup.rejection;
|
|
3804
|
-
}
|
|
3805
|
-
return {
|
|
3806
|
-
rejection: combineRejections(nestedCleanup?.rejection, cleanup),
|
|
3807
|
-
failure: cleanup.failure
|
|
3808
|
-
};
|
|
3809
|
-
}
|
|
3810
|
-
};
|
|
3811
|
-
const beginStatement = createStatement("BEGIN");
|
|
3812
|
-
const commitStatement = createStatement("COMMIT");
|
|
3813
|
-
const rollbackStatement = createStatement("ROLLBACK");
|
|
3814
|
-
function createLevel(connection, parent, transaction) {
|
|
3815
|
-
return {
|
|
3816
|
-
connection,
|
|
3817
|
-
parent,
|
|
3818
|
-
depth: parent ? parent.depth + 1 : 0,
|
|
3819
|
-
inTransaction: transaction || (parent?.inTransaction ?? false),
|
|
3820
|
-
state: "starting",
|
|
3821
|
-
child: void 0,
|
|
3822
|
-
rollbackOnly: void 0,
|
|
3823
|
-
cleanup: void 0,
|
|
3824
|
-
endedBy: void 0,
|
|
3825
|
-
restore: void 0
|
|
3826
|
-
};
|
|
3827
|
-
}
|
|
3828
|
-
/** Settings of a level of `transaction` with `options`, or `undefined` when it has none. */
|
|
3829
|
-
function getLevelSettings(options) {
|
|
3830
|
-
return options?.searchPath ? [{
|
|
3831
|
-
name: "search_path",
|
|
3832
|
-
value: options.searchPath.map(quoteIdentifier).join(", ")
|
|
3833
|
-
}] : void 0;
|
|
3834
|
-
}
|
|
3835
|
-
/**
|
|
3836
|
-
* Applies `settings` to a level whose `BEGIN` or `SAVEPOINT` completed, unless its cleanup has started.
|
|
3837
|
-
*
|
|
3838
|
-
* A nested level first reads the values they replace, which it restores before its `RELEASE`: PostgreSQL keeps a setting of a released savepoint until the transaction ends, while `ROLLBACK TO` restores it itself.
|
|
3839
|
-
*/
|
|
3840
|
-
async function applySettings(level, settings) {
|
|
3841
|
-
if (level.cleanup) return;
|
|
3842
|
-
if (level.parent) {
|
|
3843
|
-
let text = "SELECT";
|
|
3844
|
-
const names = [];
|
|
3845
|
-
for (const { name } of settings) {
|
|
3846
|
-
names.push(name);
|
|
3847
|
-
text += `${names.length > 1 ? "," : ""} current_setting($${names.length})`;
|
|
3848
|
-
}
|
|
3849
|
-
const values = (await level.connection.execute(createStatement(text, names))).rows[0];
|
|
3850
|
-
level.restore = createSettingStatement(settings.map(({ name }, index) => ({
|
|
3851
|
-
name,
|
|
3852
|
-
value: values[index]
|
|
3853
|
-
})));
|
|
3854
|
-
if (level.cleanup) return;
|
|
3855
|
-
}
|
|
3856
|
-
await level.connection.execute(createSettingStatement(settings));
|
|
3857
|
-
}
|
|
3858
|
-
/** Creates the statement that sets `settings` until the transaction ends or rolls back to an earlier savepoint, as `SET LOCAL` does. */
|
|
3859
|
-
function createSettingStatement(settings) {
|
|
3860
|
-
let text = "SELECT";
|
|
3861
|
-
const parameters = [];
|
|
3862
|
-
for (const { name, value } of settings) {
|
|
3863
|
-
parameters.push(name, value);
|
|
3864
|
-
text += `${parameters.length > 2 ? "," : ""} set_config($${parameters.length - 1}, $${parameters.length}, true)`;
|
|
3865
|
-
}
|
|
3866
|
-
return createStatement(text, parameters);
|
|
3867
|
-
}
|
|
3868
|
-
/** Name of the savepoint of a nested level; a level has at most one nested level, so the depth makes it unique. */
|
|
3869
|
-
function getSavepointName(level) {
|
|
3870
|
-
return `kern_${level.depth}`;
|
|
3871
|
-
}
|
|
3872
|
-
/**
|
|
3873
|
-
* Creates the error of `operation` on a level that rejects statements and nested levels, or returns `undefined` when it accepts them.
|
|
3874
|
-
*
|
|
3875
|
-
* A level that can only roll back keeps the failure of its nested level as the cause.
|
|
3876
|
-
*/
|
|
3877
|
-
function createUnavailableError(level, operation) {
|
|
3878
|
-
if (level.state !== "open") return /* @__PURE__ */ new Error(`Cannot ${operation}: its transaction is already finished`);
|
|
3879
|
-
if (level.child) return /* @__PURE__ */ new Error(`Cannot ${operation}: a nested transaction is running; await it first`);
|
|
3880
|
-
if (level.rollbackOnly) return new Error(`Cannot ${operation}: a nested transaction failed to end, so the transaction can only roll back`, { cause: level.rollbackOnly.error });
|
|
3881
|
-
}
|
|
3882
|
-
/** Error message of an operation outside a test transaction on a coordinator created for tests. */
|
|
3883
|
-
function describeOutsideTest(operation) {
|
|
3884
|
-
return `Cannot ${operation} outside a test transaction: the database was created with test: true, so wrap the test in testTransaction`;
|
|
3885
|
-
}
|
|
3886
|
-
/** Error of `operation` started outside a transaction after closing the database was requested. */
|
|
3887
|
-
function createDatabaseClosedError(operation) {
|
|
3888
|
-
return /* @__PURE__ */ new Error(`Cannot ${operation}: the database is closed`);
|
|
3889
|
-
}
|
|
3890
|
-
/** Closes a nested level whose `SAVEPOINT` failed, freeing its parent for other statements. */
|
|
3891
|
-
function closeStarting(level) {
|
|
3892
|
-
level.state = "closed";
|
|
3893
|
-
if (level.parent?.child === level) level.parent.child = void 0;
|
|
3894
|
-
}
|
|
3895
|
-
/** Error of a level that another level ended, keeping the rejection of its callback as the cause. */
|
|
3896
|
-
function createClosedError(level, rejection) {
|
|
3897
|
-
return new Error(level.endedBy === "preemption" ? "Transaction was rolled back: its test fixture started another level before it finished" : "Transaction was rolled back: its enclosing transaction ended first", rejection && { cause: rejection.error });
|
|
3898
|
-
}
|
|
3899
|
-
/** Error of a failure to start a test transaction, which suppresses the reason of `signal` when the signal aborted before the failure. */
|
|
3900
|
-
function suppressAbortReason(error, signal) {
|
|
3901
|
-
return signal?.aborted ? suppress(error, { error: signal.reason }, "Starting the test transaction failed").error : error;
|
|
3902
|
-
}
|
|
3903
|
-
/** Outcome of a level that something other than its callback ended: a rejection with `error`, which a failed cleanup suppresses. */
|
|
3904
|
-
async function getEndedOutcome(cleanup, error) {
|
|
3905
|
-
const ended = await cleanup;
|
|
3906
|
-
return {
|
|
3907
|
-
rejection: combineRejections({ error }, ended),
|
|
3908
|
-
failure: ended.failure
|
|
3909
|
-
};
|
|
3910
|
-
}
|
|
3911
|
-
/**
|
|
3912
|
-
* Runs `start` and settles as its promise, or rejects with the reason of `signal` when it aborts first and `abort`, called on the abort, returns `true`.
|
|
3913
|
-
*
|
|
3914
|
-
* It listens from before `start`, which may abort the signal before it returns, and stops once the promise of `start` settles, such as for the signal that `node:test` aborts after every test.
|
|
3915
|
-
* A later rejection of that promise stays handled, and a value it resolves with after the rejection goes to `discard`, which handles its own failures.
|
|
3916
|
-
*/
|
|
3917
|
-
function settleUnlessAborted(start, signal, abort, discard) {
|
|
3918
|
-
const settled = Promise.withResolvers();
|
|
3919
|
-
let abandoned = false;
|
|
3920
|
-
const onAbort = () => {
|
|
3921
|
-
if (abort()) {
|
|
3922
|
-
abandoned = true;
|
|
3923
|
-
settled.reject(signal.reason);
|
|
3924
|
-
}
|
|
3925
|
-
};
|
|
3926
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
3927
|
-
let running;
|
|
3928
|
-
try {
|
|
3929
|
-
running = start();
|
|
3930
|
-
} catch (error) {
|
|
3931
|
-
signal.removeEventListener("abort", onAbort);
|
|
3932
|
-
settled.reject(error);
|
|
3933
|
-
return settled.promise;
|
|
3934
|
-
}
|
|
3935
|
-
running.then((value) => {
|
|
3936
|
-
signal.removeEventListener("abort", onAbort);
|
|
3937
|
-
if (abandoned) discard?.(value);
|
|
3938
|
-
else settled.resolve(value);
|
|
3939
|
-
}, (error) => {
|
|
3940
|
-
signal.removeEventListener("abort", onAbort);
|
|
3941
|
-
settled.reject(error);
|
|
3942
|
-
});
|
|
3943
|
-
return settled.promise;
|
|
3944
|
-
}
|
|
3945
|
-
/**
|
|
3946
|
-
* Acquires a connection for a test transaction, rejecting with the reason of `signal` when it aborts first.
|
|
3947
|
-
*
|
|
3948
|
-
* A connection acquired after the abort returns to the pool as best-effort cleanup: the test transaction has already rejected, so nothing awaits the release, and its failure is ignored.
|
|
3949
|
-
*/
|
|
3950
|
-
function acquireUnlessAborted(driver, signal) {
|
|
3951
|
-
if (signal.aborted) return Promise.reject(signal.reason);
|
|
3952
|
-
return settleUnlessAborted(() => driver.acquire(), signal, () => true, (connection) => {
|
|
3953
|
-
Promise.resolve().then(() => connection.release()).catch(() => {});
|
|
3954
|
-
});
|
|
3955
|
-
}
|
|
3956
|
-
/** Returns the connection of a transaction to the driver, keeping a failed release as a rejection that suppresses `rejection`. */
|
|
3957
|
-
async function releaseConnection(connection, failure, rejection) {
|
|
3958
|
-
try {
|
|
3959
|
-
await connection.release(failure);
|
|
3960
|
-
return rejection;
|
|
3961
|
-
} catch (error) {
|
|
3962
|
-
return suppress(error, rejection, "Releasing the transaction connection failed");
|
|
3963
|
-
}
|
|
3964
|
-
}
|
|
3965
|
-
/** Ends a transaction with `COMMIT` or `ROLLBACK`. */
|
|
3966
|
-
async function endTransaction(connection, commit) {
|
|
3967
|
-
try {
|
|
3968
|
-
const { command } = await connection.execute(commit ? commitStatement : rollbackStatement);
|
|
3969
|
-
if (commit && command !== "COMMIT") return { rejection: { error: /* @__PURE__ */ new Error("Transaction was rolled back: a statement in it failed") } };
|
|
3970
|
-
return {};
|
|
3971
|
-
} catch (error) {
|
|
3972
|
-
return {
|
|
3973
|
-
rejection: { error },
|
|
3974
|
-
failure: { error }
|
|
3975
|
-
};
|
|
3976
|
-
}
|
|
3977
|
-
}
|
|
3978
|
-
/**
|
|
3979
|
-
* Ends the savepoint of a nested level, releasing it on commit and rolling back to it otherwise.
|
|
3980
|
-
*
|
|
3981
|
-
* Before a release, it restores the settings that the level changed; a rollback restores them itself.
|
|
3982
|
-
*/
|
|
3983
|
-
async function endSavepoint(level, commit) {
|
|
3984
|
-
const name = getSavepointName(level);
|
|
3985
|
-
try {
|
|
3986
|
-
if (!commit) await level.connection.execute(createStatement(`ROLLBACK TO SAVEPOINT ${name}`));
|
|
3987
|
-
else if (level.restore) await level.connection.execute(level.restore);
|
|
3988
|
-
await level.connection.execute(createStatement(`RELEASE SAVEPOINT ${name}`));
|
|
3989
|
-
return {};
|
|
3990
|
-
} catch (error) {
|
|
3991
|
-
return { rejection: { error } };
|
|
65
|
+
for (const key of Object.keys(values)) {
|
|
66
|
+
const reference = columns[key];
|
|
67
|
+
const column = isExpression(reference) ? toNode(reference) : void 0;
|
|
68
|
+
if (column?.kind !== "column" || column.source.kind !== "table") throw new TypeError(`The references of matches have no column "${key}"`);
|
|
69
|
+
conditions.push(matchColumn(column.source, column.key, values[key]));
|
|
3992
70
|
}
|
|
3993
|
-
|
|
3994
|
-
/** Rejection with the error of a cleanup, which suppresses the `earlier` rejection if there is one. */
|
|
3995
|
-
function combineRejections(earlier, cleanup) {
|
|
3996
|
-
return cleanup.rejection ? suppress(cleanup.rejection.error, earlier, "Transaction rollback failed") : earlier;
|
|
3997
|
-
}
|
|
3998
|
-
/** Rejection with `error`, which suppresses the `earlier` rejection if there is one. */
|
|
3999
|
-
function suppress(error, earlier, message) {
|
|
4000
|
-
return { error: earlier ? new SuppressedError(error, earlier.error, message) : error };
|
|
4001
|
-
}
|
|
4002
|
-
// src/execution/database.ts
|
|
4003
|
-
/** Creates a database over a driver; its transactions are shared by every schema bound to it. */
|
|
4004
|
-
function createDatabase({ driver, test }) {
|
|
4005
|
-
const coordinator = new TransactionCoordinator(driver, test);
|
|
4006
|
-
let tablelessDb;
|
|
4007
|
-
const getTablelessDb = () => tablelessDb ??= bindSchema(defineSchema(), database);
|
|
4008
|
-
const database = {
|
|
4009
|
-
coordinator,
|
|
4010
|
-
bind: (schema, options) => bindSchema(schema, database, options),
|
|
4011
|
-
query: (strings, ...values) => getTablelessDb().$query(strings, ...values),
|
|
4012
|
-
script: (script, ...values) => typeof script === "string" ? coordinator.executeScript(script) : getTablelessDb().$script(script, ...values),
|
|
4013
|
-
execute: (statement) => coordinator.execute(statement),
|
|
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
|
-
},
|
|
4021
|
-
close: () => coordinator.close(),
|
|
4022
|
-
[Symbol.asyncDispose]: () => coordinator.close()
|
|
4023
|
-
};
|
|
4024
|
-
return database;
|
|
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]);
|
|
71
|
+
return toBooleanExpression(combineNodes("and", conditions));
|
|
4029
72
|
}
|
|
4030
73
|
// src/query/helper.ts
|
|
4031
74
|
function defineHelper(_table, helper) {
|
|
4032
75
|
return (query, ...args) => requireDerived(query, helper(query, ...args), "The callback of a helper");
|
|
4033
76
|
}
|
|
4034
|
-
export { FALSE, NotFoundError, RequiredRelationError, ResultDecodeError, TRUE, and, asc, caseWhen, coalesce, compare, createDatabase, createStatement,
|
|
77
|
+
export { FALSE, NotFoundError, RequiredRelationError, ResultDecodeError, TRUE, and, asc, caseWhen, coalesce, compare, createDatabase, createStatement, defineExpression, defineGenericOperator, defineHelper, defineSchema, defineTable, defineType, desc, equalityOperators, from, greatest, identifier, least, literal, many, matches, not, one, or, orderBy, orderingOperators, param, sql, textOperators };
|